From d1064deab4fcb65519fda168052576c3b44eb45e Mon Sep 17 00:00:00 2001 From: Peter Donald Date: Sat, 15 Dec 2001 12:06:33 +0000 Subject: [PATCH] Add in a clone of the main ant source tree so that it can undergo some heavy refactoring. Initial stages involves just making the Ant1.x tasks implement Ant2 interface and refactoring BuildException such that it can be easily be replaced by TaskException etc. git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@270153 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/tools/ant/AntClassLoader.java | 1088 ++++++++++ .../main/org/apache/tools/ant/BuildEvent.java | 138 ++ .../org/apache/tools/ant/BuildException.java | 103 + .../org/apache/tools/ant/BuildListener.java | 80 + .../org/apache/tools/ant/BuildLogger.java | 54 + .../main/org/apache/tools/ant/Constants.java | 17 + .../org/apache/tools/ant/DefaultLogger.java | 220 ++ .../apache/tools/ant/DemuxOutputStream.java | 127 ++ .../org/apache/tools/ant/DesirableFilter.java | 96 + .../apache/tools/ant/DirectoryScanner.java | 1177 +++++++++++ .../org/apache/tools/ant/ExitException.java | 39 + .../org/apache/tools/ant/FileScanner.java | 127 ++ .../apache/tools/ant/IntrospectionHelper.java | 848 ++++++++ .../main/org/apache/tools/ant/Launcher.java | 182 ++ .../main/org/apache/tools/ant/Location.java | 80 + .../src/main/org/apache/tools/ant/Main.java | 842 ++++++++ .../org/apache/tools/ant/NoBannerLogger.java | 50 + .../org/apache/tools/ant/PathTokenizer.java | 90 + .../main/org/apache/tools/ant/Project.java | 1575 ++++++++++++++ .../apache/tools/ant/ProjectComponent.java | 71 + .../org/apache/tools/ant/ProjectHelper.java | 1061 ++++++++++ .../apache/tools/ant/RuntimeConfigurable.java | 159 ++ .../src/main/org/apache/tools/ant/Target.java | 232 +++ .../src/main/org/apache/tools/ant/Task.java | 281 +++ .../org/apache/tools/ant/TaskAdapter.java | 127 ++ .../org/apache/tools/ant/TaskContainer.java | 28 + .../org/apache/tools/ant/UnknownElement.java | 256 +++ .../org/apache/tools/ant/defaultManifest.mf | 3 + .../org/apache/tools/ant/taskdefs/Ant.java | 550 +++++ .../tools/ant/taskdefs/AntStructure.java | 394 ++++ .../apache/tools/ant/taskdefs/Available.java | 418 ++++ .../apache/tools/ant/taskdefs/BUnzip2.java | 114 ++ .../org/apache/tools/ant/taskdefs/BZip2.java | 56 + .../apache/tools/ant/taskdefs/CVSPass.java | 165 ++ .../apache/tools/ant/taskdefs/CallTarget.java | 125 ++ .../apache/tools/ant/taskdefs/Checksum.java | 489 +++++ .../org/apache/tools/ant/taskdefs/Chmod.java | 193 ++ .../tools/ant/taskdefs/CompileTask.java | 73 + .../tools/ant/taskdefs/ConditionTask.java | 76 + .../org/apache/tools/ant/taskdefs/Copy.java | 526 +++++ .../apache/tools/ant/taskdefs/Copydir.java | 137 ++ .../apache/tools/ant/taskdefs/Copyfile.java | 90 + .../org/apache/tools/ant/taskdefs/Cvs.java | 338 +++ .../apache/tools/ant/taskdefs/Definer.java | 220 ++ .../org/apache/tools/ant/taskdefs/Delete.java | 456 +++++ .../apache/tools/ant/taskdefs/Deltree.java | 103 + .../apache/tools/ant/taskdefs/DependSet.java | 308 +++ .../org/apache/tools/ant/taskdefs/Ear.java | 114 ++ .../org/apache/tools/ant/taskdefs/Echo.java | 159 ++ .../org/apache/tools/ant/taskdefs/Exec.java | 252 +++ .../apache/tools/ant/taskdefs/ExecTask.java | 456 +++++ .../apache/tools/ant/taskdefs/Execute.java | 947 +++++++++ .../tools/ant/taskdefs/ExecuteJava.java | 122 ++ .../apache/tools/ant/taskdefs/ExecuteOn.java | 473 +++++ .../ant/taskdefs/ExecuteStreamHandler.java | 62 + .../tools/ant/taskdefs/ExecuteWatchdog.java | 209 ++ .../org/apache/tools/ant/taskdefs/Exit.java | 83 + .../org/apache/tools/ant/taskdefs/Expand.java | 303 +++ .../org/apache/tools/ant/taskdefs/Filter.java | 74 + .../apache/tools/ant/taskdefs/FixCRLF.java | 1159 +++++++++++ .../org/apache/tools/ant/taskdefs/GUnzip.java | 93 + .../org/apache/tools/ant/taskdefs/GZip.java | 53 + .../tools/ant/taskdefs/GenerateKey.java | 350 ++++ .../org/apache/tools/ant/taskdefs/Get.java | 410 ++++ .../org/apache/tools/ant/taskdefs/Input.java | 153 ++ .../org/apache/tools/ant/taskdefs/Jar.java | 397 ++++ .../org/apache/tools/ant/taskdefs/Java.java | 456 +++++ .../org/apache/tools/ant/taskdefs/Javac.java | 941 +++++++++ .../tools/ant/taskdefs/JavacOutputStream.java | 95 + .../apache/tools/ant/taskdefs/Javadoc.java | 1473 ++++++++++++++ .../org/apache/tools/ant/taskdefs/Jikes.java | 128 ++ .../tools/ant/taskdefs/JikesOutputParser.java | 174 ++ .../apache/tools/ant/taskdefs/KeySubst.java | 202 ++ .../tools/ant/taskdefs/LogOutputStream.java | 116 ++ .../tools/ant/taskdefs/LogStreamHandler.java | 48 + .../apache/tools/ant/taskdefs/Manifest.java | 950 +++++++++ .../tools/ant/taskdefs/ManifestException.java | 29 + .../tools/ant/taskdefs/MatchingTask.java | 207 ++ .../org/apache/tools/ant/taskdefs/Mkdir.java | 55 + .../org/apache/tools/ant/taskdefs/Move.java | 302 +++ .../org/apache/tools/ant/taskdefs/Pack.java | 93 + .../apache/tools/ant/taskdefs/Parallel.java | 170 ++ .../org/apache/tools/ant/taskdefs/Patch.java | 157 ++ .../tools/ant/taskdefs/PathConvert.java | 397 ++++ .../tools/ant/taskdefs/ProcessDestroyer.java | 90 + .../apache/tools/ant/taskdefs/Property.java | 394 ++++ .../tools/ant/taskdefs/PumpStreamHandler.java | 130 ++ .../apache/tools/ant/taskdefs/Recorder.java | 239 +++ .../tools/ant/taskdefs/RecorderEntry.java | 208 ++ .../org/apache/tools/ant/taskdefs/Rename.java | 92 + .../apache/tools/ant/taskdefs/Replace.java | 591 ++++++ .../org/apache/tools/ant/taskdefs/Rmic.java | 688 +++++++ .../apache/tools/ant/taskdefs/SQLExec.java | 867 ++++++++ .../apache/tools/ant/taskdefs/SendEmail.java | 406 ++++ .../apache/tools/ant/taskdefs/Sequential.java | 60 + .../apache/tools/ant/taskdefs/SignJar.java | 335 +++ .../org/apache/tools/ant/taskdefs/Sleep.java | 183 ++ .../tools/ant/taskdefs/StreamPumper.java | 68 + .../org/apache/tools/ant/taskdefs/Tar.java | 481 +++++ .../tools/ant/taskdefs/TaskOutputStream.java | 84 + .../apache/tools/ant/taskdefs/Taskdef.java | 23 + .../org/apache/tools/ant/taskdefs/Touch.java | 220 ++ .../apache/tools/ant/taskdefs/Transform.java | 17 + .../org/apache/tools/ant/taskdefs/Tstamp.java | 263 +++ .../apache/tools/ant/taskdefs/Typedef.java | 23 + .../org/apache/tools/ant/taskdefs/Unpack.java | 92 + .../org/apache/tools/ant/taskdefs/Untar.java | 65 + .../apache/tools/ant/taskdefs/UpToDate.java | 192 ++ .../apache/tools/ant/taskdefs/WaitFor.java | 178 ++ .../org/apache/tools/ant/taskdefs/War.java | 123 ++ .../tools/ant/taskdefs/XSLTLiaison.java | 74 + .../apache/tools/ant/taskdefs/XSLTLogger.java | 18 + .../tools/ant/taskdefs/XSLTLoggerAware.java | 13 + .../tools/ant/taskdefs/XSLTProcess.java | 581 ++++++ .../org/apache/tools/ant/taskdefs/Zip.java | 888 ++++++++ .../taskdefs/compilers/CompilerAdapter.java | 43 + .../compilers/CompilerAdapterFactory.java | 145 ++ .../compilers/DefaultCompilerAdapter.java | 494 +++++ .../tools/ant/taskdefs/compilers/Gcj.java | 109 + .../tools/ant/taskdefs/compilers/Javac12.java | 81 + .../tools/ant/taskdefs/compilers/Javac13.java | 62 + .../ant/taskdefs/compilers/JavacExternal.java | 42 + .../tools/ant/taskdefs/compilers/Jikes.java | 189 ++ .../tools/ant/taskdefs/compilers/Jvc.java | 103 + .../tools/ant/taskdefs/compilers/Kjc.java | 133 ++ .../tools/ant/taskdefs/compilers/Sj.java | 43 + .../tools/ant/taskdefs/condition/And.java | 39 + .../ant/taskdefs/condition/Condition.java | 29 + .../ant/taskdefs/condition/ConditionBase.java | 209 ++ .../tools/ant/taskdefs/condition/Equals.java | 41 + .../tools/ant/taskdefs/condition/Http.java | 73 + .../tools/ant/taskdefs/condition/IsSet.java | 38 + .../tools/ant/taskdefs/condition/Not.java | 35 + .../tools/ant/taskdefs/condition/Or.java | 39 + .../tools/ant/taskdefs/condition/Socket.java | 58 + .../tools/ant/taskdefs/defaults.properties | 141 ++ .../tools/ant/taskdefs/optional/ANTLR.java | 286 +++ .../ant/taskdefs/optional/AdaptxLiaison.java | 60 + .../tools/ant/taskdefs/optional/Cab.java | 349 ++++ .../ant/taskdefs/optional/IContract.java | 1105 ++++++++++ .../tools/ant/taskdefs/optional/Javah.java | 468 +++++ .../ant/taskdefs/optional/ManifestFile.java | 396 ++++ .../ant/taskdefs/optional/Native2Ascii.java | 258 +++ .../tools/ant/taskdefs/optional/NetRexxC.java | 766 +++++++ .../ant/taskdefs/optional/PropertyFile.java | 791 ++++++++ .../taskdefs/optional/RenameExtensions.java | 131 ++ .../ant/taskdefs/optional/ReplaceRegExp.java | 360 ++++ .../tools/ant/taskdefs/optional/Rpm.java | 227 +++ .../tools/ant/taskdefs/optional/Script.java | 149 ++ .../ant/taskdefs/optional/StyleBook.java | 85 + .../tools/ant/taskdefs/optional/Test.java | 84 + .../ant/taskdefs/optional/TraXLiaison.java | 217 ++ .../taskdefs/optional/XMLValidateTask.java | 648 ++++++ .../ant/taskdefs/optional/XalanLiaison.java | 110 + .../ant/taskdefs/optional/XslpLiaison.java | 71 + .../ant/taskdefs/optional/ccm/CCMCheck.java | 160 ++ .../ant/taskdefs/optional/ccm/CCMCheckin.java | 27 + .../optional/ccm/CCMCheckinDefault.java | 27 + .../taskdefs/optional/ccm/CCMCheckout.java | 24 + .../taskdefs/optional/ccm/CCMCreateTask.java | 356 ++++ .../taskdefs/optional/ccm/CCMReconfigure.java | 168 ++ .../ant/taskdefs/optional/ccm/Continuus.java | 132 ++ .../optional/clearcase/CCCheckin.java | 451 ++++ .../optional/clearcase/CCCheckout.java | 602 ++++++ .../optional/clearcase/CCUnCheckout.java | 171 ++ .../taskdefs/optional/clearcase/CCUpdate.java | 440 ++++ .../optional/clearcase/ClearCase.java | 124 ++ .../taskdefs/optional/depend/ClassFile.java | 118 ++ .../optional/depend/ClassFileIterator.java | 15 + .../optional/depend/ClassFileUtils.java | 44 + .../ant/taskdefs/optional/depend/Depend.java | 764 +++++++ .../optional/depend/DirectoryIterator.java | 185 ++ .../optional/depend/JarFileIterator.java | 94 + .../depend/constantpool/ClassCPInfo.java | 89 + .../depend/constantpool/ConstantCPInfo.java | 58 + .../depend/constantpool/ConstantPool.java | 374 ++++ .../constantpool/ConstantPoolEntry.java | 243 +++ .../depend/constantpool/DoubleCPInfo.java | 50 + .../depend/constantpool/FieldRefCPInfo.java | 111 + .../depend/constantpool/FloatCPInfo.java | 53 + .../depend/constantpool/IntegerCPInfo.java | 53 + .../InterfaceMethodRefCPInfo.java | 112 + .../depend/constantpool/LongCPInfo.java | 53 + .../depend/constantpool/MethodRefCPInfo.java | 111 + .../constantpool/NameAndTypeCPInfo.java | 95 + .../depend/constantpool/StringCPInfo.java | 71 + .../depend/constantpool/Utf8CPInfo.java | 60 + .../ant/taskdefs/optional/dotnet/CSharp.java | 965 +++++++++ .../ant/taskdefs/optional/dotnet/Ilasm.java | 475 +++++ .../taskdefs/optional/dotnet/NetCommand.java | 209 ++ .../optional/ejb/BorlandDeploymentTool.java | 513 +++++ .../optional/ejb/BorlandGenerateClient.java | 244 +++ .../ant/taskdefs/optional/ejb/DDCreator.java | 133 ++ .../optional/ejb/DDCreatorHelper.java | 158 ++ .../optional/ejb/DescriptorHandler.java | 403 ++++ .../optional/ejb/EJBDeploymentTool.java | 49 + .../ant/taskdefs/optional/ejb/EjbJar.java | 567 ++++++ .../tools/ant/taskdefs/optional/ejb/Ejbc.java | 192 ++ .../ant/taskdefs/optional/ejb/EjbcHelper.java | 286 +++ .../optional/ejb/GenericDeploymentTool.java | 970 +++++++++ .../optional/ejb/IPlanetDeploymentTool.java | 408 ++++ .../taskdefs/optional/ejb/IPlanetEjbc.java | 1690 +++++++++++++++ .../optional/ejb/IPlanetEjbcTask.java | 345 ++++ .../ejb/InnerClassFilenameFilter.java | 35 + .../optional/ejb/JbossDeploymentTool.java | 70 + .../ant/taskdefs/optional/ejb/WLRun.java | 424 ++++ .../ant/taskdefs/optional/ejb/WLStop.java | 170 ++ .../optional/ejb/WeblogicDeploymentTool.java | 852 ++++++++ .../ejb/WeblogicTOPLinkDeploymentTool.java | 104 + .../optional/ejb/WebsphereDeploymentTool.java | 1633 +++++++++++++++ .../ant/taskdefs/optional/i18n/Translate.java | 645 ++++++ .../ant/taskdefs/optional/ide/VAJAntTool.java | 118 ++ .../taskdefs/optional/ide/VAJAntToolGUI.java | 1803 ++++++++++++++++ .../taskdefs/optional/ide/VAJBuildInfo.java | 585 ++++++ .../ant/taskdefs/optional/ide/VAJExport.java | 183 ++ .../optional/ide/VAJExportServlet.java | 256 +++ .../ant/taskdefs/optional/ide/VAJImport.java | 268 +++ .../optional/ide/VAJImportServlet.java | 78 + .../ant/taskdefs/optional/ide/VAJLoad.java | 44 + .../optional/ide/VAJLoadProjects.java | 30 + .../taskdefs/optional/ide/VAJLoadServlet.java | 83 + .../taskdefs/optional/ide/VAJLocalUtil.java | 530 +++++ .../optional/ide/VAJProjectDescription.java | 68 + .../taskdefs/optional/ide/VAJRemoteUtil.java | 270 +++ .../ant/taskdefs/optional/ide/VAJTask.java | 71 + .../optional/ide/VAJToolsServlet.java | 234 +++ .../ant/taskdefs/optional/ide/VAJUtil.java | 79 + .../optional/ide/VAJWorkspaceScanner.java | 220 ++ .../ant/taskdefs/optional/ide/default.ini | 4 + .../ant/taskdefs/optional/javacc/JJTree.java | 207 ++ .../ant/taskdefs/optional/javacc/JavaCC.java | 278 +++ .../optional/jdepend/JDependTask.java | 453 +++++ .../optional/jlink/ClassNameReader.java | 116 ++ .../taskdefs/optional/jlink/JlinkTask.java | 204 ++ .../ant/taskdefs/optional/jlink/jlink.java | 464 +++++ .../tools/ant/taskdefs/optional/jsp/JspC.java | 491 +++++ .../ant/taskdefs/optional/jsp/WLJspc.java | 312 +++ .../jsp/compilers/CompilerAdapter.java | 44 + .../jsp/compilers/CompilerAdapterFactory.java | 94 + .../jsp/compilers/DefaultCompilerAdapter.java | 83 + .../optional/jsp/compilers/JasperC.java | 107 + .../optional/junit/AggregateTransformer.java | 235 +++ .../ant/taskdefs/optional/junit/BaseTest.java | 132 ++ .../taskdefs/optional/junit/BatchTest.java | 198 ++ .../junit/BriefJUnitResultFormatter.java | 264 +++ .../ant/taskdefs/optional/junit/DOMUtil.java | 249 +++ .../taskdefs/optional/junit/Enumerations.java | 191 ++ .../optional/junit/FormatterElement.java | 243 +++ .../optional/junit/JUnitResultFormatter.java | 58 + .../taskdefs/optional/junit/JUnitTask.java | 812 ++++++++ .../taskdefs/optional/junit/JUnitTest.java | 188 ++ .../optional/junit/JUnitTestRunner.java | 637 ++++++ .../optional/junit/JUnitVersionHelper.java | 65 + .../junit/PlainJUnitResultFormatter.java | 253 +++ .../junit/SummaryJUnitResultFormatter.java | 176 ++ .../taskdefs/optional/junit/XMLConstants.java | 115 ++ .../junit/XMLJUnitResultFormatter.java | 277 +++ .../optional/junit/XMLResultAggregator.java | 336 +++ .../optional/junit/Xalan1Executor.java | 37 + .../optional/junit/Xalan2Executor.java | 39 + .../optional/junit/XalanExecutor.java | 126 ++ .../metamata/AbstractMetamataTask.java | 397 ++++ .../taskdefs/optional/metamata/MAudit.java | 259 +++ .../metamata/MAuditStreamHandler.java | 267 +++ .../taskdefs/optional/metamata/MMetrics.java | 297 +++ .../metamata/MMetricsStreamHandler.java | 496 +++++ .../taskdefs/optional/metamata/MParse.java | 433 ++++ .../tools/ant/taskdefs/optional/net/FTP.java | 1106 ++++++++++ .../ant/taskdefs/optional/net/MimeMail.java | 403 ++++ .../ant/taskdefs/optional/net/TelnetTask.java | 404 ++++ .../ant/taskdefs/optional/perforce/P4Add.java | 161 ++ .../taskdefs/optional/perforce/P4Base.java | 192 ++ .../taskdefs/optional/perforce/P4Change.java | 141 ++ .../taskdefs/optional/perforce/P4Counter.java | 105 + .../taskdefs/optional/perforce/P4Delete.java | 41 + .../taskdefs/optional/perforce/P4Edit.java | 38 + .../taskdefs/optional/perforce/P4Handler.java | 26 + .../optional/perforce/P4HandlerAdapter.java | 89 + .../taskdefs/optional/perforce/P4Have.java | 26 + .../taskdefs/optional/perforce/P4Label.java | 155 ++ .../optional/perforce/P4OutputHandler.java | 22 + .../taskdefs/optional/perforce/P4Reopen.java | 38 + .../taskdefs/optional/perforce/P4Revert.java | 58 + .../taskdefs/optional/perforce/P4Submit.java | 56 + .../taskdefs/optional/perforce/P4Sync.java | 128 ++ .../perforce/SimpleP4OutputHandler.java | 46 + .../taskdefs/optional/perforce/package.html | 26 + .../ant/taskdefs/optional/pvcs/Pvcs.java | 559 +++++ .../taskdefs/optional/pvcs/PvcsProject.java | 34 + .../optional/scm/AntStarTeamCheckOut.java | 1113 ++++++++++ .../taskdefs/optional/sitraka/CovMerge.java | 251 +++ .../taskdefs/optional/sitraka/CovReport.java | 425 ++++ .../taskdefs/optional/sitraka/Coverage.java | 568 ++++++ .../taskdefs/optional/sitraka/Filters.java | 123 ++ .../optional/sitraka/ReportFilters.java | 161 ++ .../ant/taskdefs/optional/sitraka/Socket.java | 50 + .../taskdefs/optional/sitraka/StringUtil.java | 45 + .../taskdefs/optional/sitraka/Triggers.java | 122 ++ .../taskdefs/optional/sitraka/XMLReport.java | 676 ++++++ .../optional/sitraka/bytecode/ClassFile.java | 149 ++ .../sitraka/bytecode/ClassPathLoader.java | 432 ++++ .../optional/sitraka/bytecode/MethodInfo.java | 158 ++ .../optional/sitraka/bytecode/Utils.java | 486 +++++ .../bytecode/attributes/AttributeInfo.java | 40 + .../optional/sound/AntSoundPlayer.java | 265 +++ .../taskdefs/optional/sound/SoundTask.java | 188 ++ .../ant/taskdefs/optional/vss/MSVSS.java | 239 +++ .../taskdefs/optional/vss/MSVSSCHECKIN.java | 243 +++ .../taskdefs/optional/vss/MSVSSCHECKOUT.java | 278 +++ .../ant/taskdefs/optional/vss/MSVSSGET.java | 513 +++++ .../taskdefs/optional/vss/MSVSSHISTORY.java | 439 ++++ .../ant/taskdefs/optional/vss/MSVSSLABEL.java | 375 ++++ .../ant/taskdefs/rmic/DefaultRmicAdapter.java | 417 ++++ .../tools/ant/taskdefs/rmic/KaffeRmic.java | 59 + .../tools/ant/taskdefs/rmic/RmicAdapter.java | 60 + .../ant/taskdefs/rmic/RmicAdapterFactory.java | 123 ++ .../tools/ant/taskdefs/rmic/SunRmic.java | 78 + .../tools/ant/taskdefs/rmic/WLRmic.java | 75 + .../apache/tools/ant/types/Commandline.java | 431 ++++ .../tools/ant/types/CommandlineJava.java | 398 ++++ .../org/apache/tools/ant/types/DataType.java | 197 ++ .../apache/tools/ant/types/Description.java | 42 + .../tools/ant/types/EnumeratedAttribute.java | 83 + .../apache/tools/ant/types/Environment.java | 98 + .../org/apache/tools/ant/types/FileList.java | 154 ++ .../org/apache/tools/ant/types/FileSet.java | 363 ++++ .../org/apache/tools/ant/types/FilterSet.java | 470 +++++ .../tools/ant/types/FilterSetCollection.java | 78 + .../org/apache/tools/ant/types/Mapper.java | 295 +++ .../main/org/apache/tools/ant/types/Path.java | 706 +++++++ .../apache/tools/ant/types/PatternSet.java | 534 +++++ .../org/apache/tools/ant/types/Reference.java | 58 + .../tools/ant/types/RegularExpression.java | 112 + .../apache/tools/ant/types/Substitution.java | 83 + .../apache/tools/ant/types/ZipFileSet.java | 155 ++ .../apache/tools/ant/types/ZipScanner.java | 98 + .../tools/ant/types/defaults.properties | 10 + .../types/optional/depend/ClassfileSet.java | 89 + .../types/optional/depend/DependScanner.java | 190 ++ .../tools/ant/util/DOMElementWriter.java | 239 +++ .../apache/tools/ant/util/FileNameMapper.java | 52 + .../org/apache/tools/ant/util/FileUtils.java | 674 ++++++ .../tools/ant/util/FlatFileNameMapper.java | 47 + .../tools/ant/util/GlobPatternMapper.java | 128 ++ .../apache/tools/ant/util/IdentityMapper.java | 45 + .../apache/tools/ant/util/MergingMapper.java | 50 + .../tools/ant/util/RegexpPatternMapper.java | 122 ++ .../tools/ant/util/SourceFileScanner.java | 157 ++ .../apache/tools/ant/util/StringUtils.java | 40 + .../tools/ant/util/depend/Dependencies.java | 271 +++ .../apache/tools/ant/util/depend/Filter.java | 14 + .../ant/util/regexp/JakartaOroMatcher.java | 165 ++ .../ant/util/regexp/JakartaOroRegexp.java | 86 + .../ant/util/regexp/JakartaRegexpMatcher.java | 141 ++ .../ant/util/regexp/JakartaRegexpRegexp.java | 77 + .../ant/util/regexp/Jdk14RegexpMatcher.java | 158 ++ .../ant/util/regexp/Jdk14RegexpRegexp.java | 98 + .../apache/tools/ant/util/regexp/Regexp.java | 42 + .../tools/ant/util/regexp/RegexpFactory.java | 112 + .../tools/ant/util/regexp/RegexpMatcher.java | 109 + .../ant/util/regexp/RegexpMatcherFactory.java | 103 + .../tools/ant/util/regexp/RegexpUtil.java | 26 + .../apache/tools/bzip2/BZip2Constants.java | 84 + .../apache/tools/bzip2/CBZip2InputStream.java | 939 +++++++++ .../tools/bzip2/CBZip2OutputStream.java | 1807 +++++++++++++++++ .../src/main/org/apache/tools/bzip2/CRC.java | 120 ++ .../org/apache/tools/mail/MailMessage.java | 526 +++++ .../apache/tools/mail/SmtpResponseReader.java | 104 + .../main/org/apache/tools/tar/TarBuffer.java | 475 +++++ .../org/apache/tools/tar/TarConstants.java | 141 ++ .../main/org/apache/tools/tar/TarEntry.java | 654 ++++++ .../org/apache/tools/tar/TarInputStream.java | 439 ++++ .../org/apache/tools/tar/TarOutputStream.java | 335 +++ .../main/org/apache/tools/tar/TarUtils.java | 210 ++ .../org/apache/tools/zip/AsiExtraField.java | 359 ++++ .../org/apache/tools/zip/ExtraFieldUtils.java | 194 ++ .../main/org/apache/tools/zip/UnixStat.java | 67 + .../tools/zip/UnrecognizedExtraField.java | 99 + .../main/org/apache/tools/zip/ZipEntry.java | 435 ++++ .../org/apache/tools/zip/ZipExtraField.java | 80 + .../main/org/apache/tools/zip/ZipLong.java | 113 ++ .../org/apache/tools/zip/ZipOutputStream.java | 712 +++++++ .../main/org/apache/tools/zip/ZipShort.java | 109 + .../org/apache/tools/ant/AntClassLoader.java | 1088 ++++++++++ .../todo/org/apache/tools/ant/BuildEvent.java | 138 ++ .../org/apache/tools/ant/BuildException.java | 103 + .../org/apache/tools/ant/BuildListener.java | 80 + .../org/apache/tools/ant/BuildLogger.java | 54 + .../todo/org/apache/tools/ant/Constants.java | 17 + .../org/apache/tools/ant/DefaultLogger.java | 220 ++ .../apache/tools/ant/DemuxOutputStream.java | 127 ++ .../org/apache/tools/ant/DesirableFilter.java | 96 + .../apache/tools/ant/DirectoryScanner.java | 1177 +++++++++++ .../org/apache/tools/ant/ExitException.java | 39 + .../org/apache/tools/ant/FileScanner.java | 127 ++ .../apache/tools/ant/IntrospectionHelper.java | 848 ++++++++ .../todo/org/apache/tools/ant/Launcher.java | 182 ++ .../todo/org/apache/tools/ant/Location.java | 80 + .../src/todo/org/apache/tools/ant/Main.java | 842 ++++++++ .../org/apache/tools/ant/NoBannerLogger.java | 50 + .../org/apache/tools/ant/PathTokenizer.java | 90 + .../todo/org/apache/tools/ant/Project.java | 1575 ++++++++++++++ .../apache/tools/ant/ProjectComponent.java | 71 + .../org/apache/tools/ant/ProjectHelper.java | 1061 ++++++++++ .../apache/tools/ant/RuntimeConfigurable.java | 159 ++ .../src/todo/org/apache/tools/ant/Target.java | 232 +++ .../src/todo/org/apache/tools/ant/Task.java | 281 +++ .../org/apache/tools/ant/TaskAdapter.java | 127 ++ .../org/apache/tools/ant/TaskContainer.java | 28 + .../org/apache/tools/ant/UnknownElement.java | 256 +++ .../org/apache/tools/ant/defaultManifest.mf | 3 + .../org/apache/tools/ant/taskdefs/Ant.java | 550 +++++ .../tools/ant/taskdefs/AntStructure.java | 394 ++++ .../apache/tools/ant/taskdefs/Available.java | 418 ++++ .../apache/tools/ant/taskdefs/BUnzip2.java | 114 ++ .../org/apache/tools/ant/taskdefs/BZip2.java | 56 + .../apache/tools/ant/taskdefs/CVSPass.java | 165 ++ .../apache/tools/ant/taskdefs/CallTarget.java | 125 ++ .../apache/tools/ant/taskdefs/Checksum.java | 489 +++++ .../org/apache/tools/ant/taskdefs/Chmod.java | 193 ++ .../tools/ant/taskdefs/CompileTask.java | 73 + .../tools/ant/taskdefs/ConditionTask.java | 76 + .../org/apache/tools/ant/taskdefs/Copy.java | 526 +++++ .../apache/tools/ant/taskdefs/Copydir.java | 137 ++ .../apache/tools/ant/taskdefs/Copyfile.java | 90 + .../org/apache/tools/ant/taskdefs/Cvs.java | 338 +++ .../apache/tools/ant/taskdefs/Definer.java | 220 ++ .../org/apache/tools/ant/taskdefs/Delete.java | 456 +++++ .../apache/tools/ant/taskdefs/Deltree.java | 103 + .../apache/tools/ant/taskdefs/DependSet.java | 308 +++ .../org/apache/tools/ant/taskdefs/Ear.java | 114 ++ .../org/apache/tools/ant/taskdefs/Echo.java | 159 ++ .../org/apache/tools/ant/taskdefs/Exec.java | 252 +++ .../apache/tools/ant/taskdefs/ExecTask.java | 456 +++++ .../apache/tools/ant/taskdefs/Execute.java | 947 +++++++++ .../tools/ant/taskdefs/ExecuteJava.java | 122 ++ .../apache/tools/ant/taskdefs/ExecuteOn.java | 473 +++++ .../ant/taskdefs/ExecuteStreamHandler.java | 62 + .../tools/ant/taskdefs/ExecuteWatchdog.java | 209 ++ .../org/apache/tools/ant/taskdefs/Exit.java | 83 + .../org/apache/tools/ant/taskdefs/Expand.java | 303 +++ .../org/apache/tools/ant/taskdefs/Filter.java | 74 + .../apache/tools/ant/taskdefs/FixCRLF.java | 1159 +++++++++++ .../org/apache/tools/ant/taskdefs/GUnzip.java | 93 + .../org/apache/tools/ant/taskdefs/GZip.java | 53 + .../tools/ant/taskdefs/GenerateKey.java | 350 ++++ .../org/apache/tools/ant/taskdefs/Get.java | 410 ++++ .../org/apache/tools/ant/taskdefs/Input.java | 153 ++ .../org/apache/tools/ant/taskdefs/Jar.java | 397 ++++ .../org/apache/tools/ant/taskdefs/Java.java | 456 +++++ .../org/apache/tools/ant/taskdefs/Javac.java | 941 +++++++++ .../tools/ant/taskdefs/JavacOutputStream.java | 95 + .../apache/tools/ant/taskdefs/Javadoc.java | 1473 ++++++++++++++ .../org/apache/tools/ant/taskdefs/Jikes.java | 128 ++ .../tools/ant/taskdefs/JikesOutputParser.java | 174 ++ .../apache/tools/ant/taskdefs/KeySubst.java | 202 ++ .../tools/ant/taskdefs/LogOutputStream.java | 116 ++ .../tools/ant/taskdefs/LogStreamHandler.java | 48 + .../apache/tools/ant/taskdefs/Manifest.java | 950 +++++++++ .../tools/ant/taskdefs/ManifestException.java | 29 + .../tools/ant/taskdefs/MatchingTask.java | 207 ++ .../org/apache/tools/ant/taskdefs/Mkdir.java | 55 + .../org/apache/tools/ant/taskdefs/Move.java | 302 +++ .../org/apache/tools/ant/taskdefs/Pack.java | 93 + .../apache/tools/ant/taskdefs/Parallel.java | 170 ++ .../org/apache/tools/ant/taskdefs/Patch.java | 157 ++ .../tools/ant/taskdefs/PathConvert.java | 397 ++++ .../tools/ant/taskdefs/ProcessDestroyer.java | 90 + .../apache/tools/ant/taskdefs/Property.java | 394 ++++ .../tools/ant/taskdefs/PumpStreamHandler.java | 130 ++ .../apache/tools/ant/taskdefs/Recorder.java | 239 +++ .../tools/ant/taskdefs/RecorderEntry.java | 208 ++ .../org/apache/tools/ant/taskdefs/Rename.java | 92 + .../apache/tools/ant/taskdefs/Replace.java | 591 ++++++ .../org/apache/tools/ant/taskdefs/Rmic.java | 688 +++++++ .../apache/tools/ant/taskdefs/SQLExec.java | 867 ++++++++ .../apache/tools/ant/taskdefs/SendEmail.java | 406 ++++ .../apache/tools/ant/taskdefs/Sequential.java | 60 + .../apache/tools/ant/taskdefs/SignJar.java | 335 +++ .../org/apache/tools/ant/taskdefs/Sleep.java | 183 ++ .../tools/ant/taskdefs/StreamPumper.java | 68 + .../org/apache/tools/ant/taskdefs/Tar.java | 481 +++++ .../tools/ant/taskdefs/TaskOutputStream.java | 84 + .../apache/tools/ant/taskdefs/Taskdef.java | 23 + .../org/apache/tools/ant/taskdefs/Touch.java | 220 ++ .../apache/tools/ant/taskdefs/Transform.java | 17 + .../org/apache/tools/ant/taskdefs/Tstamp.java | 263 +++ .../apache/tools/ant/taskdefs/Typedef.java | 23 + .../org/apache/tools/ant/taskdefs/Unpack.java | 92 + .../org/apache/tools/ant/taskdefs/Untar.java | 65 + .../apache/tools/ant/taskdefs/UpToDate.java | 192 ++ .../apache/tools/ant/taskdefs/WaitFor.java | 178 ++ .../org/apache/tools/ant/taskdefs/War.java | 123 ++ .../tools/ant/taskdefs/XSLTLiaison.java | 74 + .../apache/tools/ant/taskdefs/XSLTLogger.java | 18 + .../tools/ant/taskdefs/XSLTLoggerAware.java | 13 + .../tools/ant/taskdefs/XSLTProcess.java | 581 ++++++ .../org/apache/tools/ant/taskdefs/Zip.java | 888 ++++++++ .../taskdefs/compilers/CompilerAdapter.java | 43 + .../compilers/CompilerAdapterFactory.java | 145 ++ .../compilers/DefaultCompilerAdapter.java | 494 +++++ .../tools/ant/taskdefs/compilers/Gcj.java | 109 + .../tools/ant/taskdefs/compilers/Javac12.java | 81 + .../tools/ant/taskdefs/compilers/Javac13.java | 62 + .../ant/taskdefs/compilers/JavacExternal.java | 42 + .../tools/ant/taskdefs/compilers/Jikes.java | 189 ++ .../tools/ant/taskdefs/compilers/Jvc.java | 103 + .../tools/ant/taskdefs/compilers/Kjc.java | 133 ++ .../tools/ant/taskdefs/compilers/Sj.java | 43 + .../tools/ant/taskdefs/condition/And.java | 39 + .../ant/taskdefs/condition/Condition.java | 29 + .../ant/taskdefs/condition/ConditionBase.java | 209 ++ .../tools/ant/taskdefs/condition/Equals.java | 41 + .../tools/ant/taskdefs/condition/Http.java | 73 + .../tools/ant/taskdefs/condition/IsSet.java | 38 + .../tools/ant/taskdefs/condition/Not.java | 35 + .../tools/ant/taskdefs/condition/Or.java | 39 + .../tools/ant/taskdefs/condition/Socket.java | 58 + .../tools/ant/taskdefs/defaults.properties | 141 ++ .../tools/ant/taskdefs/optional/ANTLR.java | 286 +++ .../ant/taskdefs/optional/AdaptxLiaison.java | 60 + .../tools/ant/taskdefs/optional/Cab.java | 349 ++++ .../ant/taskdefs/optional/IContract.java | 1105 ++++++++++ .../tools/ant/taskdefs/optional/Javah.java | 468 +++++ .../ant/taskdefs/optional/ManifestFile.java | 396 ++++ .../ant/taskdefs/optional/Native2Ascii.java | 258 +++ .../tools/ant/taskdefs/optional/NetRexxC.java | 766 +++++++ .../ant/taskdefs/optional/PropertyFile.java | 791 ++++++++ .../taskdefs/optional/RenameExtensions.java | 131 ++ .../ant/taskdefs/optional/ReplaceRegExp.java | 360 ++++ .../tools/ant/taskdefs/optional/Rpm.java | 227 +++ .../tools/ant/taskdefs/optional/Script.java | 149 ++ .../ant/taskdefs/optional/StyleBook.java | 85 + .../tools/ant/taskdefs/optional/Test.java | 84 + .../ant/taskdefs/optional/TraXLiaison.java | 217 ++ .../taskdefs/optional/XMLValidateTask.java | 648 ++++++ .../ant/taskdefs/optional/XalanLiaison.java | 110 + .../ant/taskdefs/optional/XslpLiaison.java | 71 + .../ant/taskdefs/optional/ccm/CCMCheck.java | 160 ++ .../ant/taskdefs/optional/ccm/CCMCheckin.java | 27 + .../optional/ccm/CCMCheckinDefault.java | 27 + .../taskdefs/optional/ccm/CCMCheckout.java | 24 + .../taskdefs/optional/ccm/CCMCreateTask.java | 356 ++++ .../taskdefs/optional/ccm/CCMReconfigure.java | 168 ++ .../ant/taskdefs/optional/ccm/Continuus.java | 132 ++ .../optional/clearcase/CCCheckin.java | 451 ++++ .../optional/clearcase/CCCheckout.java | 602 ++++++ .../optional/clearcase/CCUnCheckout.java | 171 ++ .../taskdefs/optional/clearcase/CCUpdate.java | 440 ++++ .../optional/clearcase/ClearCase.java | 124 ++ .../taskdefs/optional/depend/ClassFile.java | 118 ++ .../optional/depend/ClassFileIterator.java | 15 + .../optional/depend/ClassFileUtils.java | 44 + .../ant/taskdefs/optional/depend/Depend.java | 764 +++++++ .../optional/depend/DirectoryIterator.java | 185 ++ .../optional/depend/JarFileIterator.java | 94 + .../depend/constantpool/ClassCPInfo.java | 89 + .../depend/constantpool/ConstantCPInfo.java | 58 + .../depend/constantpool/ConstantPool.java | 374 ++++ .../constantpool/ConstantPoolEntry.java | 243 +++ .../depend/constantpool/DoubleCPInfo.java | 50 + .../depend/constantpool/FieldRefCPInfo.java | 111 + .../depend/constantpool/FloatCPInfo.java | 53 + .../depend/constantpool/IntegerCPInfo.java | 53 + .../InterfaceMethodRefCPInfo.java | 112 + .../depend/constantpool/LongCPInfo.java | 53 + .../depend/constantpool/MethodRefCPInfo.java | 111 + .../constantpool/NameAndTypeCPInfo.java | 95 + .../depend/constantpool/StringCPInfo.java | 71 + .../depend/constantpool/Utf8CPInfo.java | 60 + .../ant/taskdefs/optional/dotnet/CSharp.java | 965 +++++++++ .../ant/taskdefs/optional/dotnet/Ilasm.java | 475 +++++ .../taskdefs/optional/dotnet/NetCommand.java | 209 ++ .../optional/ejb/BorlandDeploymentTool.java | 513 +++++ .../optional/ejb/BorlandGenerateClient.java | 244 +++ .../ant/taskdefs/optional/ejb/DDCreator.java | 133 ++ .../optional/ejb/DDCreatorHelper.java | 158 ++ .../optional/ejb/DescriptorHandler.java | 403 ++++ .../optional/ejb/EJBDeploymentTool.java | 49 + .../ant/taskdefs/optional/ejb/EjbJar.java | 567 ++++++ .../tools/ant/taskdefs/optional/ejb/Ejbc.java | 192 ++ .../ant/taskdefs/optional/ejb/EjbcHelper.java | 286 +++ .../optional/ejb/GenericDeploymentTool.java | 970 +++++++++ .../optional/ejb/IPlanetDeploymentTool.java | 408 ++++ .../taskdefs/optional/ejb/IPlanetEjbc.java | 1690 +++++++++++++++ .../optional/ejb/IPlanetEjbcTask.java | 345 ++++ .../ejb/InnerClassFilenameFilter.java | 35 + .../optional/ejb/JbossDeploymentTool.java | 70 + .../ant/taskdefs/optional/ejb/WLRun.java | 424 ++++ .../ant/taskdefs/optional/ejb/WLStop.java | 170 ++ .../optional/ejb/WeblogicDeploymentTool.java | 852 ++++++++ .../ejb/WeblogicTOPLinkDeploymentTool.java | 104 + .../optional/ejb/WebsphereDeploymentTool.java | 1633 +++++++++++++++ .../ant/taskdefs/optional/i18n/Translate.java | 645 ++++++ .../ant/taskdefs/optional/ide/VAJAntTool.java | 118 ++ .../taskdefs/optional/ide/VAJAntToolGUI.java | 1803 ++++++++++++++++ .../taskdefs/optional/ide/VAJBuildInfo.java | 585 ++++++ .../ant/taskdefs/optional/ide/VAJExport.java | 183 ++ .../optional/ide/VAJExportServlet.java | 256 +++ .../ant/taskdefs/optional/ide/VAJImport.java | 268 +++ .../optional/ide/VAJImportServlet.java | 78 + .../ant/taskdefs/optional/ide/VAJLoad.java | 44 + .../optional/ide/VAJLoadProjects.java | 30 + .../taskdefs/optional/ide/VAJLoadServlet.java | 83 + .../taskdefs/optional/ide/VAJLocalUtil.java | 530 +++++ .../optional/ide/VAJProjectDescription.java | 68 + .../taskdefs/optional/ide/VAJRemoteUtil.java | 270 +++ .../ant/taskdefs/optional/ide/VAJTask.java | 71 + .../optional/ide/VAJToolsServlet.java | 234 +++ .../ant/taskdefs/optional/ide/VAJUtil.java | 79 + .../optional/ide/VAJWorkspaceScanner.java | 220 ++ .../ant/taskdefs/optional/ide/default.ini | 4 + .../ant/taskdefs/optional/javacc/JJTree.java | 207 ++ .../ant/taskdefs/optional/javacc/JavaCC.java | 278 +++ .../optional/jdepend/JDependTask.java | 453 +++++ .../optional/jlink/ClassNameReader.java | 116 ++ .../taskdefs/optional/jlink/JlinkTask.java | 204 ++ .../ant/taskdefs/optional/jlink/jlink.java | 464 +++++ .../tools/ant/taskdefs/optional/jsp/JspC.java | 491 +++++ .../ant/taskdefs/optional/jsp/WLJspc.java | 312 +++ .../jsp/compilers/CompilerAdapter.java | 44 + .../jsp/compilers/CompilerAdapterFactory.java | 94 + .../jsp/compilers/DefaultCompilerAdapter.java | 83 + .../optional/jsp/compilers/JasperC.java | 107 + .../optional/junit/AggregateTransformer.java | 235 +++ .../ant/taskdefs/optional/junit/BaseTest.java | 132 ++ .../taskdefs/optional/junit/BatchTest.java | 198 ++ .../junit/BriefJUnitResultFormatter.java | 264 +++ .../ant/taskdefs/optional/junit/DOMUtil.java | 249 +++ .../taskdefs/optional/junit/Enumerations.java | 191 ++ .../optional/junit/FormatterElement.java | 243 +++ .../optional/junit/JUnitResultFormatter.java | 58 + .../taskdefs/optional/junit/JUnitTask.java | 812 ++++++++ .../taskdefs/optional/junit/JUnitTest.java | 188 ++ .../optional/junit/JUnitTestRunner.java | 637 ++++++ .../optional/junit/JUnitVersionHelper.java | 65 + .../junit/PlainJUnitResultFormatter.java | 253 +++ .../junit/SummaryJUnitResultFormatter.java | 176 ++ .../taskdefs/optional/junit/XMLConstants.java | 115 ++ .../junit/XMLJUnitResultFormatter.java | 277 +++ .../optional/junit/XMLResultAggregator.java | 336 +++ .../optional/junit/Xalan1Executor.java | 37 + .../optional/junit/Xalan2Executor.java | 39 + .../optional/junit/XalanExecutor.java | 126 ++ .../metamata/AbstractMetamataTask.java | 397 ++++ .../taskdefs/optional/metamata/MAudit.java | 259 +++ .../metamata/MAuditStreamHandler.java | 267 +++ .../taskdefs/optional/metamata/MMetrics.java | 297 +++ .../metamata/MMetricsStreamHandler.java | 496 +++++ .../taskdefs/optional/metamata/MParse.java | 433 ++++ .../tools/ant/taskdefs/optional/net/FTP.java | 1106 ++++++++++ .../ant/taskdefs/optional/net/MimeMail.java | 403 ++++ .../ant/taskdefs/optional/net/TelnetTask.java | 404 ++++ .../ant/taskdefs/optional/perforce/P4Add.java | 161 ++ .../taskdefs/optional/perforce/P4Base.java | 192 ++ .../taskdefs/optional/perforce/P4Change.java | 141 ++ .../taskdefs/optional/perforce/P4Counter.java | 105 + .../taskdefs/optional/perforce/P4Delete.java | 41 + .../taskdefs/optional/perforce/P4Edit.java | 38 + .../taskdefs/optional/perforce/P4Handler.java | 26 + .../optional/perforce/P4HandlerAdapter.java | 89 + .../taskdefs/optional/perforce/P4Have.java | 26 + .../taskdefs/optional/perforce/P4Label.java | 155 ++ .../optional/perforce/P4OutputHandler.java | 22 + .../taskdefs/optional/perforce/P4Reopen.java | 38 + .../taskdefs/optional/perforce/P4Revert.java | 58 + .../taskdefs/optional/perforce/P4Submit.java | 56 + .../taskdefs/optional/perforce/P4Sync.java | 128 ++ .../perforce/SimpleP4OutputHandler.java | 46 + .../taskdefs/optional/perforce/package.html | 26 + .../ant/taskdefs/optional/pvcs/Pvcs.java | 559 +++++ .../taskdefs/optional/pvcs/PvcsProject.java | 34 + .../optional/scm/AntStarTeamCheckOut.java | 1113 ++++++++++ .../taskdefs/optional/sitraka/CovMerge.java | 251 +++ .../taskdefs/optional/sitraka/CovReport.java | 425 ++++ .../taskdefs/optional/sitraka/Coverage.java | 568 ++++++ .../taskdefs/optional/sitraka/Filters.java | 123 ++ .../optional/sitraka/ReportFilters.java | 161 ++ .../ant/taskdefs/optional/sitraka/Socket.java | 50 + .../taskdefs/optional/sitraka/StringUtil.java | 45 + .../taskdefs/optional/sitraka/Triggers.java | 122 ++ .../taskdefs/optional/sitraka/XMLReport.java | 676 ++++++ .../optional/sitraka/bytecode/ClassFile.java | 149 ++ .../sitraka/bytecode/ClassPathLoader.java | 432 ++++ .../optional/sitraka/bytecode/MethodInfo.java | 158 ++ .../optional/sitraka/bytecode/Utils.java | 486 +++++ .../bytecode/attributes/AttributeInfo.java | 40 + .../optional/sound/AntSoundPlayer.java | 265 +++ .../taskdefs/optional/sound/SoundTask.java | 188 ++ .../ant/taskdefs/optional/vss/MSVSS.java | 239 +++ .../taskdefs/optional/vss/MSVSSCHECKIN.java | 243 +++ .../taskdefs/optional/vss/MSVSSCHECKOUT.java | 278 +++ .../ant/taskdefs/optional/vss/MSVSSGET.java | 513 +++++ .../taskdefs/optional/vss/MSVSSHISTORY.java | 439 ++++ .../ant/taskdefs/optional/vss/MSVSSLABEL.java | 375 ++++ .../ant/taskdefs/rmic/DefaultRmicAdapter.java | 417 ++++ .../tools/ant/taskdefs/rmic/KaffeRmic.java | 59 + .../tools/ant/taskdefs/rmic/RmicAdapter.java | 60 + .../ant/taskdefs/rmic/RmicAdapterFactory.java | 123 ++ .../tools/ant/taskdefs/rmic/SunRmic.java | 78 + .../tools/ant/taskdefs/rmic/WLRmic.java | 75 + .../apache/tools/ant/types/Commandline.java | 431 ++++ .../tools/ant/types/CommandlineJava.java | 398 ++++ .../org/apache/tools/ant/types/DataType.java | 197 ++ .../apache/tools/ant/types/Description.java | 42 + .../tools/ant/types/EnumeratedAttribute.java | 83 + .../apache/tools/ant/types/Environment.java | 98 + .../org/apache/tools/ant/types/FileList.java | 154 ++ .../org/apache/tools/ant/types/FileSet.java | 363 ++++ .../org/apache/tools/ant/types/FilterSet.java | 470 +++++ .../tools/ant/types/FilterSetCollection.java | 78 + .../org/apache/tools/ant/types/Mapper.java | 295 +++ .../todo/org/apache/tools/ant/types/Path.java | 706 +++++++ .../apache/tools/ant/types/PatternSet.java | 534 +++++ .../org/apache/tools/ant/types/Reference.java | 58 + .../tools/ant/types/RegularExpression.java | 112 + .../apache/tools/ant/types/Substitution.java | 83 + .../apache/tools/ant/types/ZipFileSet.java | 155 ++ .../apache/tools/ant/types/ZipScanner.java | 98 + .../tools/ant/types/defaults.properties | 10 + .../types/optional/depend/ClassfileSet.java | 89 + .../types/optional/depend/DependScanner.java | 190 ++ .../tools/ant/util/DOMElementWriter.java | 239 +++ .../apache/tools/ant/util/FileNameMapper.java | 52 + .../org/apache/tools/ant/util/FileUtils.java | 674 ++++++ .../tools/ant/util/FlatFileNameMapper.java | 47 + .../tools/ant/util/GlobPatternMapper.java | 128 ++ .../apache/tools/ant/util/IdentityMapper.java | 45 + .../apache/tools/ant/util/MergingMapper.java | 50 + .../tools/ant/util/RegexpPatternMapper.java | 122 ++ .../tools/ant/util/SourceFileScanner.java | 157 ++ .../apache/tools/ant/util/StringUtils.java | 40 + .../tools/ant/util/depend/Dependencies.java | 271 +++ .../apache/tools/ant/util/depend/Filter.java | 14 + .../ant/util/regexp/JakartaOroMatcher.java | 165 ++ .../ant/util/regexp/JakartaOroRegexp.java | 86 + .../ant/util/regexp/JakartaRegexpMatcher.java | 141 ++ .../ant/util/regexp/JakartaRegexpRegexp.java | 77 + .../ant/util/regexp/Jdk14RegexpMatcher.java | 158 ++ .../ant/util/regexp/Jdk14RegexpRegexp.java | 98 + .../apache/tools/ant/util/regexp/Regexp.java | 42 + .../tools/ant/util/regexp/RegexpFactory.java | 112 + .../tools/ant/util/regexp/RegexpMatcher.java | 109 + .../ant/util/regexp/RegexpMatcherFactory.java | 103 + .../tools/ant/util/regexp/RegexpUtil.java | 26 + .../apache/tools/bzip2/BZip2Constants.java | 84 + .../apache/tools/bzip2/CBZip2InputStream.java | 939 +++++++++ .../tools/bzip2/CBZip2OutputStream.java | 1807 +++++++++++++++++ .../src/todo/org/apache/tools/bzip2/CRC.java | 120 ++ .../org/apache/tools/mail/MailMessage.java | 526 +++++ .../apache/tools/mail/SmtpResponseReader.java | 104 + .../todo/org/apache/tools/tar/TarBuffer.java | 475 +++++ .../org/apache/tools/tar/TarConstants.java | 141 ++ .../todo/org/apache/tools/tar/TarEntry.java | 654 ++++++ .../org/apache/tools/tar/TarInputStream.java | 439 ++++ .../org/apache/tools/tar/TarOutputStream.java | 335 +++ .../todo/org/apache/tools/tar/TarUtils.java | 210 ++ .../org/apache/tools/zip/AsiExtraField.java | 359 ++++ .../org/apache/tools/zip/ExtraFieldUtils.java | 194 ++ .../todo/org/apache/tools/zip/UnixStat.java | 67 + .../tools/zip/UnrecognizedExtraField.java | 99 + .../todo/org/apache/tools/zip/ZipEntry.java | 435 ++++ .../org/apache/tools/zip/ZipExtraField.java | 80 + .../todo/org/apache/tools/zip/ZipLong.java | 113 ++ .../org/apache/tools/zip/ZipOutputStream.java | 712 +++++++ .../todo/org/apache/tools/zip/ZipShort.java | 109 + 766 files changed, 202752 insertions(+) create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/AntClassLoader.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/BuildEvent.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/BuildException.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/BuildListener.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/BuildLogger.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/Constants.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/DefaultLogger.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/DemuxOutputStream.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/DesirableFilter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/DirectoryScanner.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/ExitException.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/FileScanner.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/IntrospectionHelper.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/Launcher.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/Location.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/Main.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/NoBannerLogger.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/PathTokenizer.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/Project.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/ProjectComponent.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/ProjectHelper.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/RuntimeConfigurable.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/Target.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/Task.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/TaskAdapter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/TaskContainer.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/UnknownElement.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/defaultManifest.mf create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ant.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/AntStructure.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Available.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BUnzip2.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BZip2.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CVSPass.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CallTarget.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Checksum.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Chmod.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CompileTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ConditionTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copy.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copydir.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copyfile.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Cvs.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Definer.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Delete.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Deltree.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/DependSet.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ear.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Echo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exec.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Execute.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteJava.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteOn.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exit.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Expand.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Filter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/FixCRLF.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GUnzip.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GZip.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GenerateKey.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Get.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Input.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jar.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Java.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javac.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JavacOutputStream.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javadoc.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jikes.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JikesOutputParser.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/KeySubst.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogOutputStream.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogStreamHandler.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Manifest.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ManifestException.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/MatchingTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Mkdir.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Move.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Pack.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Parallel.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Patch.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PathConvert.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ProcessDestroyer.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Property.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Recorder.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/RecorderEntry.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rename.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Replace.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rmic.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SQLExec.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SendEmail.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sequential.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SignJar.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sleep.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/StreamPumper.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tar.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/TaskOutputStream.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Taskdef.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Touch.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Transform.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tstamp.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Typedef.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Unpack.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Untar.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/UpToDate.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/WaitFor.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/War.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLiaison.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLogger.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Zip.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Gcj.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac12.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac13.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jikes.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jvc.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Kjc.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Sj.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/And.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Condition.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/ConditionBase.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Equals.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Http.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/IsSet.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Not.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Or.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Socket.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/defaults.properties create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ANTLR.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Cab.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/IContract.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Javah.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ManifestFile.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/NetRexxC.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/PropertyFile.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Rpm.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Script.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/StyleBook.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Test.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/Depend.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/default.ini create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MParse.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/net/FTP.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/net/MimeMail.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/net/TelnetTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Add.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Base.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Change.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Counter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Delete.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Edit.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Handler.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4HandlerAdapter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Have.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Label.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4OutputHandler.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Reopen.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Revert.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Submit.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/P4Sync.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/SimpleP4OutputHandler.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/perforce/package.html create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/pvcs/Pvcs.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/pvcs/PvcsProject.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/scm/AntStarTeamCheckOut.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/CovMerge.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/CovReport.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Coverage.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Filters.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/ReportFilters.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Socket.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/StringUtil.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/Triggers.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/XMLReport.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassFile.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassPathLoader.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/MethodInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/Utils.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/attributes/AttributeInfo.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sound/AntSoundPlayer.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/sound/SoundTask.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSS.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKIN.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKOUT.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSGET.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSHISTORY.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/vss/MSVSSLABEL.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/DefaultRmicAdapter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/KaffeRmic.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/RmicAdapter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/RmicAdapterFactory.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/SunRmic.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/rmic/WLRmic.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/Commandline.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/CommandlineJava.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/DataType.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/Description.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/EnumeratedAttribute.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/Environment.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/FileList.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/FileSet.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/FilterSet.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/FilterSetCollection.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/Mapper.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/Path.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/PatternSet.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/Reference.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/RegularExpression.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/Substitution.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/ZipFileSet.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/ZipScanner.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/defaults.properties create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/optional/depend/ClassfileSet.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/types/optional/depend/DependScanner.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/DOMElementWriter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/FileNameMapper.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/FileUtils.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/FlatFileNameMapper.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/GlobPatternMapper.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/IdentityMapper.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/MergingMapper.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/RegexpPatternMapper.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/SourceFileScanner.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/StringUtils.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Dependencies.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Filter.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Regexp.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpFactory.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcher.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpUtil.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/bzip2/BZip2Constants.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2InputStream.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2OutputStream.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/bzip2/CRC.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/mail/MailMessage.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/mail/SmtpResponseReader.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/tar/TarBuffer.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/tar/TarConstants.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/tar/TarEntry.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/tar/TarInputStream.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/tar/TarOutputStream.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/tar/TarUtils.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/zip/AsiExtraField.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/zip/ExtraFieldUtils.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/zip/UnixStat.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/zip/UnrecognizedExtraField.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/zip/ZipEntry.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/zip/ZipExtraField.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/zip/ZipLong.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/zip/ZipOutputStream.java create mode 100644 proposal/myrmidon/src/main/org/apache/tools/zip/ZipShort.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/AntClassLoader.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/BuildEvent.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/BuildException.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/BuildListener.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/BuildLogger.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/Constants.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/DefaultLogger.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/DemuxOutputStream.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/DesirableFilter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/DirectoryScanner.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/ExitException.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/FileScanner.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/IntrospectionHelper.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/Launcher.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/Location.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/Main.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/NoBannerLogger.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/PathTokenizer.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/Project.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectComponent.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectHelper.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/RuntimeConfigurable.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/Target.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/Task.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/TaskAdapter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/TaskContainer.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/UnknownElement.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/defaultManifest.mf create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ant.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/AntStructure.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Available.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BUnzip2.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BZip2.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CVSPass.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CallTarget.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Checksum.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Chmod.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CompileTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ConditionTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copy.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copydir.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copyfile.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Cvs.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Definer.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Delete.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Deltree.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/DependSet.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ear.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Echo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exec.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Execute.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteJava.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteOn.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exit.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Expand.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Filter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/FixCRLF.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GUnzip.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GZip.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GenerateKey.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Get.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Input.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jar.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Java.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javac.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JavacOutputStream.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javadoc.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jikes.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JikesOutputParser.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/KeySubst.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogOutputStream.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogStreamHandler.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Manifest.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ManifestException.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/MatchingTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Mkdir.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Move.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Pack.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Parallel.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Patch.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PathConvert.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ProcessDestroyer.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Property.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PumpStreamHandler.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Recorder.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/RecorderEntry.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rename.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Replace.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rmic.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SQLExec.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SendEmail.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sequential.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SignJar.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sleep.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/StreamPumper.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tar.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/TaskOutputStream.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Taskdef.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Touch.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Transform.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tstamp.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Typedef.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Unpack.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Untar.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/UpToDate.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/WaitFor.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/War.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLiaison.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLogger.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTProcess.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Zip.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Gcj.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac12.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac13.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jikes.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jvc.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Kjc.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Sj.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/And.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Condition.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/ConditionBase.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Equals.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Http.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/IsSet.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Not.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Or.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Socket.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/defaults.properties create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ANTLR.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Cab.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/IContract.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Javah.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ManifestFile.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/NetRexxC.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/PropertyFile.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Rpm.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Script.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/StyleBook.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Test.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/Depend.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/default.ini create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MAuditStreamHandler.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MMetrics.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MMetricsStreamHandler.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MParse.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/net/FTP.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/net/MimeMail.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/net/TelnetTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Add.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Base.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Change.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Counter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Delete.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Edit.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Handler.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4HandlerAdapter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Have.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Label.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4OutputHandler.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Reopen.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Revert.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Submit.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/P4Sync.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/SimpleP4OutputHandler.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/perforce/package.html create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/pvcs/Pvcs.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/pvcs/PvcsProject.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/scm/AntStarTeamCheckOut.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/CovMerge.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/CovReport.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Coverage.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Filters.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/ReportFilters.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Socket.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/StringUtil.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/Triggers.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/XMLReport.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassFile.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/ClassPathLoader.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/MethodInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/Utils.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sitraka/bytecode/attributes/AttributeInfo.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sound/AntSoundPlayer.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/sound/SoundTask.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSS.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKIN.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSCHECKOUT.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSGET.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSHISTORY.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/vss/MSVSSLABEL.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/DefaultRmicAdapter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/KaffeRmic.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/RmicAdapter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/RmicAdapterFactory.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/SunRmic.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/rmic/WLRmic.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/Commandline.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/CommandlineJava.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/DataType.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/Description.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/EnumeratedAttribute.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/Environment.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/FileList.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/FileSet.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/FilterSet.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/FilterSetCollection.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/Mapper.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/Path.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/PatternSet.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/Reference.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/RegularExpression.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/Substitution.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/ZipFileSet.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/ZipScanner.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/defaults.properties create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/optional/depend/ClassfileSet.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/types/optional/depend/DependScanner.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/DOMElementWriter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileNameMapper.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileUtils.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/FlatFileNameMapper.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/GlobPatternMapper.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/IdentityMapper.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/MergingMapper.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/RegexpPatternMapper.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/SourceFileScanner.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/StringUtils.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Dependencies.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Filter.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Regexp.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpFactory.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcher.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpUtil.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/bzip2/BZip2Constants.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2InputStream.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2OutputStream.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/bzip2/CRC.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/mail/MailMessage.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/mail/SmtpResponseReader.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/tar/TarBuffer.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/tar/TarConstants.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/tar/TarEntry.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/tar/TarInputStream.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/tar/TarOutputStream.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/tar/TarUtils.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/zip/AsiExtraField.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/zip/ExtraFieldUtils.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/zip/UnixStat.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/zip/UnrecognizedExtraField.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/zip/ZipEntry.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/zip/ZipExtraField.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/zip/ZipLong.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/zip/ZipOutputStream.java create mode 100644 proposal/myrmidon/src/todo/org/apache/tools/zip/ZipShort.java diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/AntClassLoader.java b/proposal/myrmidon/src/main/org/apache/tools/ant/AntClassLoader.java new file mode 100644 index 000000000..d2ef10528 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/AntClassLoader.java @@ -0,0 +1,1088 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.apache.tools.ant.types.Path; + +/** + * Used to load classes within ant with a different claspath from that used to + * start ant. Note that it is possible to force a class into this loader even + * when that class is on the system classpath by using the forceLoadClass + * method. Any subsequent classes loaded by that class will then use this loader + * rather than the system class loader. + * + * @author Conor MacNeill + * @author Jesse Glick + */ +public class AntClassLoader extends ClassLoader implements BuildListener +{ + + /** + * The size of buffers to be used in this classloader. + */ + private final static int BUFFER_SIZE = 8192; + + private static Method getProtectionDomain = null; + private static Method defineClassProtectionDomain = null; + private static Method getContextClassLoader = null; + private static Method setContextClassLoader = null; + + /** + * The components of the classpath that the classloader searches for classes + */ + Vector pathComponents = new Vector(); + + /** + * Indicates whether the parent class loader should be consulted before + * trying to load with this class loader. + */ + private boolean parentFirst = true; + + /** + * These are the package roots that are to be loaded by the parent class + * loader regardless of whether the parent class loader is being searched + * first or not. + */ + private Vector systemPackages = new Vector(); + + /** + * These are the package roots that are to be loaded by this class loader + * regardless of whether the parent class loader is being searched first or + * not. + */ + private Vector loaderPackages = new Vector(); + + /** + * This flag indicates that the classloader will ignore the base classloader + * if it can't find a class. + */ + private boolean ignoreBase = false; + + /** + * The parent class loader, if one is given or can be determined + */ + private ClassLoader parent = null; + + /** + * A hashtable of zip files opened by the classloader + */ + private Hashtable zipFiles = new Hashtable(); + + /** + * The context loader saved when setting the thread's current context + * loader. + */ + private ClassLoader savedContextLoader = null; + private boolean isContextLoaderSaved = false; + + /** + * The project to which this class loader belongs. + */ + private Project project; + static + { + try + { + getProtectionDomain = Class.class.getMethod( "getProtectionDomain", new Class[0] ); + Class protectionDomain = Class.forName( "java.security.ProtectionDomain" ); + Class[] args = new Class[]{String.class, byte[].class, Integer.TYPE, Integer.TYPE, protectionDomain}; + defineClassProtectionDomain = ClassLoader.class.getDeclaredMethod( "defineClass", args ); + + getContextClassLoader = Thread.class.getMethod( "getContextClassLoader", new Class[0] ); + args = new Class[]{ClassLoader.class}; + setContextClassLoader = Thread.class.getMethod( "setContextClassLoader", args ); + } + catch( Exception e ) + {} + } + + + /** + * Create a classloader for the given project using the classpath given. + * + * @param project the project to which this classloader is to belong. + * @param classpath the classpath to use to load the classes. This is + * combined with the system classpath in a manner determined by the + * value of ${build.sysclasspath} + */ + public AntClassLoader( Project project, Path classpath ) + { + parent = AntClassLoader.class.getClassLoader(); + this.project = project; + project.addBuildListener( this ); + if( classpath != null ) + { + Path actualClasspath = classpath.concatSystemClasspath( "ignore" ); + String[] pathElements = actualClasspath.list(); + for( int i = 0; i < pathElements.length; ++i ) + { + try + { + addPathElement( ( String )pathElements[i] ); + } + catch( BuildException e ) + { + // ignore path elements which are invalid relative to the project + } + } + } + } + + /** + * Create a classloader for the given project using the classpath given. + * + * @param parent the parent classloader to which unsatisfied loading + * attempts are delgated + * @param project the project to which this classloader is to belong. + * @param classpath the classpath to use to load the classes. + * @param parentFirst if true indicates that the parent classloader should + * be consulted before trying to load the a class through this loader. + */ + public AntClassLoader( ClassLoader parent, Project project, Path classpath, + boolean parentFirst ) + { + this( project, classpath ); + if( parent != null ) + { + this.parent = parent; + } + this.parentFirst = parentFirst; + addSystemPackageRoot( "java" ); + addSystemPackageRoot( "javax" ); + } + + + /** + * Create a classloader for the given project using the classpath given. + * + * @param project the project to which this classloader is to belong. + * @param classpath the classpath to use to load the classes. + * @param parentFirst if true indicates that the parent classloader should + * be consulted before trying to load the a class through this loader. + */ + public AntClassLoader( Project project, Path classpath, boolean parentFirst ) + { + this( null, project, classpath, parentFirst ); + } + + /** + * Create an empty class loader. The classloader should be configured with + * path elements to specify where the loader is to look for classes. + * + * @param parent the parent classloader to which unsatisfied loading + * attempts are delgated + * @param parentFirst if true indicates that the parent classloader should + * be consulted before trying to load the a class through this loader. + */ + public AntClassLoader( ClassLoader parent, boolean parentFirst ) + { + if( parent != null ) + { + this.parent = parent; + } + else + { + parent = AntClassLoader.class.getClassLoader(); + } + project = null; + this.parentFirst = parentFirst; + } + + /** + * Force initialization of a class in a JDK 1.1 compatible, albeit hacky way + * + * @param theClass Description of Parameter + */ + public static void initializeClass( Class theClass ) + { + // ***HACK*** We try to create an instance to force the VM to run the + // class' static initializer. We don't care if the instance can't + // be created - we are just interested in the side effect. + try + { + theClass.newInstance(); + } + catch( Throwable t ) + { + //ignore - our work is done + } + } + + /** + * Set this classloader to run in isolated mode. In isolated mode, classes + * not found on the given classpath will not be referred to the base class + * loader but will cause a classNotFoundException. + * + * @param isolated The new Isolated value + */ + public void setIsolated( boolean isolated ) + { + ignoreBase = isolated; + } + + /** + * Set the current thread's context loader to this classloader, storing the + * current loader value for later resetting + */ + public void setThreadContextLoader() + { + if( isContextLoaderSaved ) + { + throw new BuildException( "Context loader has not been reset" ); + } + if( getContextClassLoader != null && setContextClassLoader != null ) + { + try + { + savedContextLoader + = ( ClassLoader )getContextClassLoader.invoke( Thread.currentThread(), new Object[0] ); + Object[] args = new Object[]{this}; + setContextClassLoader.invoke( Thread.currentThread(), args ); + isContextLoaderSaved = true; + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + throw new BuildException( t.toString() ); + } + catch( Exception e ) + { + throw new BuildException( e.toString() ); + } + } + } + + /** + * Finds the resource with the given name. A resource is some data (images, + * audio, text, etc) that can be accessed by class code in a way that is + * independent of the location of the code. + * + * @param name the name of the resource for which a stream is required. + * @return a URL for reading the resource, or null if the resource could not + * be found or the caller doesn't have adequate privileges to get the + * resource. + */ + public URL getResource( String name ) + { + // we need to search the components of the path to see if we can find the + // class we want. + URL url = null; + if( isParentFirst( name ) ) + { + url = ( parent == null ) ? super.getResource( name ) : parent.getResource( name ); + } + + if( url != null ) + { + log( "Resource " + name + " loaded from parent loader", + Project.MSG_DEBUG ); + + } + else + { + // try and load from this loader if the parent either didn't find + // it or wasn't consulted. + for( Enumeration e = pathComponents.elements(); e.hasMoreElements() && url == null; ) + { + File pathComponent = ( File )e.nextElement(); + url = getResourceURL( pathComponent, name ); + if( url != null ) + { + log( "Resource " + name + + " loaded from ant loader", + Project.MSG_DEBUG ); + } + } + } + + if( url == null && !isParentFirst( name ) ) + { + // this loader was first but it didn't find it - try the parent + + url = ( parent == null ) ? super.getResource( name ) : parent.getResource( name ); + if( url != null ) + { + log( "Resource " + name + " loaded from parent loader", + Project.MSG_DEBUG ); + } + } + + if( url == null ) + { + log( "Couldn't load Resource " + name, Project.MSG_DEBUG ); + } + + return url; + } + + /** + * Get a stream to read the requested resource name. + * + * @param name the name of the resource for which a stream is required. + * @return a stream to the required resource or null if the resource cannot + * be found on the loader's classpath. + */ + public InputStream getResourceAsStream( String name ) + { + + InputStream resourceStream = null; + if( isParentFirst( name ) ) + { + resourceStream = loadBaseResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from parent loader", Project.MSG_DEBUG ); + + } + else + { + resourceStream = loadResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from ant loader", Project.MSG_DEBUG ); + } + } + } + else + { + resourceStream = loadResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from ant loader", Project.MSG_DEBUG ); + + } + else + { + resourceStream = loadBaseResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from parent loader", Project.MSG_DEBUG ); + } + } + } + + if( resourceStream == null ) + { + log( "Couldn't load ResourceStream for " + name, + Project.MSG_DEBUG ); + } + + return resourceStream; + } + + /** + * Add a package root to the list of packages which must be loaded using + * this loader. All subpackages are also included. + * + * @param packageRoot the root of akll packages to be included. + */ + public void addLoaderPackageRoot( String packageRoot ) + { + loaderPackages.addElement( packageRoot + "." ); + } + + + /** + * Add an element to the classpath to be searched + * + * @param pathElement The feature to be added to the PathElement attribute + * @exception BuildException Description of Exception + */ + public void addPathElement( String pathElement ) + throws BuildException + { + File pathComponent + = project != null ? project.resolveFile( pathElement ) + : new File( pathElement ); + pathComponents.addElement( pathComponent ); + } + + /** + * Add a package root to the list of packages which must be loaded on the + * parent loader. All subpackages are also included. + * + * @param packageRoot the root of all packages to be included. + */ + public void addSystemPackageRoot( String packageRoot ) + { + systemPackages.addElement( packageRoot + "." ); + } + + public void buildFinished( BuildEvent event ) + { + cleanup(); + } + + public void buildStarted( BuildEvent event ) { } + + public void cleanup() + { + pathComponents = null; + project = null; + for( Enumeration e = zipFiles.elements(); e.hasMoreElements(); ) + { + ZipFile zipFile = ( ZipFile )e.nextElement(); + try + { + zipFile.close(); + } + catch( IOException ioe ) + { + // ignore + } + } + zipFiles = new Hashtable(); + } + + /** + * Search for and load a class on the classpath of this class loader. + * + * @param name the classname to be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * this loader's classpath. + */ + public Class findClass( String name ) + throws ClassNotFoundException + { + log( "Finding class " + name, Project.MSG_DEBUG ); + + return findClassInComponents( name ); + } + + + /** + * Load a class through this class loader even if that class is available on + * the parent classpath. This ensures that any classes which are loaded by + * the returned class will use this classloader. + * + * @param classname the classname to be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * this loader's classpath. + */ + public Class forceLoadClass( String classname ) + throws ClassNotFoundException + { + log( "force loading " + classname, Project.MSG_DEBUG ); + + Class theClass = findLoadedClass( classname ); + + if( theClass == null ) + { + theClass = findClass( classname ); + } + + return theClass; + } + + /** + * Load a class through this class loader but defer to the parent class + * loader This ensures that instances of the returned class will be + * compatible with instances which which have already been loaded on the + * parent loader. + * + * @param classname the classname to be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * this loader's classpath. + */ + public Class forceLoadSystemClass( String classname ) + throws ClassNotFoundException + { + log( "force system loading " + classname, Project.MSG_DEBUG ); + + Class theClass = findLoadedClass( classname ); + + if( theClass == null ) + { + theClass = findBaseClass( classname ); + } + + return theClass; + } + + public void messageLogged( BuildEvent event ) { } + + /** + * Reset the current thread's context loader to its original value + */ + public void resetThreadContextLoader() + { + if( isContextLoaderSaved && + getContextClassLoader != null && setContextClassLoader != null ) + { + try + { + Object[] args = new Object[]{savedContextLoader}; + setContextClassLoader.invoke( Thread.currentThread(), args ); + savedContextLoader = null; + isContextLoaderSaved = false; + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + throw new BuildException( t.toString() ); + } + catch( Exception e ) + { + throw new BuildException( e.toString() ); + } + } + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) { } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + + /** + * Returns an enumeration of URLs representing all the resources with the + * given name by searching the class loader's classpath. + * + * @param name the resource name. + * @return an enumeration of URLs for the resources. + * @throws IOException if I/O errors occurs (can't happen) + */ + protected Enumeration findResources( String name ) + throws IOException + { + return new ResourceEnumeration( name ); + } + + + /** + * Load a class with this class loader. This method will load a class. This + * class attempts to load the class firstly using the parent class loader. + * For JDK 1.1 compatability, this uses the findSystemClass method. + * + * @param classname the name of the class to be loaded. + * @param resolve true if all classes upon which this class depends are to + * be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * the system classpath or this loader's classpath. + */ + protected Class loadClass( String classname, boolean resolve ) + throws ClassNotFoundException + { + + Class theClass = findLoadedClass( classname ); + if( theClass != null ) + { + return theClass; + } + + if( isParentFirst( classname ) ) + { + try + { + theClass = findBaseClass( classname ); + log( "Class " + classname + " loaded from parent loader", Project.MSG_DEBUG ); + } + catch( ClassNotFoundException cnfe ) + { + theClass = findClass( classname ); + log( "Class " + classname + " loaded from ant loader", Project.MSG_DEBUG ); + } + } + else + { + try + { + theClass = findClass( classname ); + log( "Class " + classname + " loaded from ant loader", Project.MSG_DEBUG ); + } + catch( ClassNotFoundException cnfe ) + { + if( ignoreBase ) + { + throw cnfe; + } + theClass = findBaseClass( classname ); + log( "Class " + classname + " loaded from parent loader", Project.MSG_DEBUG ); + } + } + + if( resolve ) + { + resolveClass( theClass ); + } + + return theClass; + } + + /** + * Log a message through the project object if one has been provided. + * + * @param message the message to log + * @param priority the logging priority of the message + */ + protected void log( String message, int priority ) + { + if( project != null ) + { + project.log( message, priority ); + } +// else { +// System.out.println(message); +// } + } + + /** + * Convert the class dot notation to a filesystem equivalent for searching + * purposes. + * + * @param classname the class name in dot format (ie java.lang.Integer) + * @return the classname in filesystem format (ie java/lang/Integer.class) + */ + private String getClassFilename( String classname ) + { + return classname.replace( '.', '/' ) + ".class"; + } + + /** + * Read a class definition from a stream. + * + * @param stream the stream from which the class is to be read. + * @param classname the class name of the class in the stream. + * @return the Class object read from the stream. + * @throws IOException if there is a problem reading the class from the + * stream. + */ + private Class getClassFromStream( InputStream stream, String classname ) + throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int bytesRead = -1; + byte[] buffer = new byte[BUFFER_SIZE]; + + while( ( bytesRead = stream.read( buffer, 0, BUFFER_SIZE ) ) != -1 ) + { + baos.write( buffer, 0, bytesRead ); + } + + byte[] classData = baos.toByteArray(); + + // Simply put: + // defineClass(classname, classData, 0, classData.length, Project.class.getProtectionDomain()); + // Made more elaborate to be 1.1-safe. + if( defineClassProtectionDomain != null ) + { + try + { + Object domain = getProtectionDomain.invoke( Project.class, new Object[0] ); + Object[] args = new Object[]{classname, classData, new Integer( 0 ), new Integer( classData.length ), domain}; + return ( Class )defineClassProtectionDomain.invoke( this, args ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof ClassFormatError ) + { + throw ( ClassFormatError )t; + } + else if( t instanceof NoClassDefFoundError ) + { + throw ( NoClassDefFoundError )t; + } + else + { + throw new IOException( t.toString() ); + } + } + catch( Exception e ) + { + throw new IOException( e.toString() ); + } + } + else + { + return defineClass( classname, classData, 0, classData.length ); + } + } + + /** + * Get an inputstream to a given resource in the given file which may either + * be a directory or a zip file. + * + * @param file the file (directory or jar) in which to search for the + * resource. + * @param resourceName the name of the resource for which a stream is + * required. + * @return a stream to the required resource or null if the resource cannot + * be found in the given file object + */ + private InputStream getResourceStream( File file, String resourceName ) + { + try + { + if( !file.exists() ) + { + return null; + } + + if( file.isDirectory() ) + { + File resource = new File( file, resourceName ); + + if( resource.exists() ) + { + return new FileInputStream( resource ); + } + } + else + { + // is the zip file in the cache + ZipFile zipFile = ( ZipFile )zipFiles.get( file ); + if( zipFile == null ) + { + zipFile = new ZipFile( file ); + zipFiles.put( file, zipFile ); + } + ZipEntry entry = zipFile.getEntry( resourceName ); + if( entry != null ) + { + return zipFile.getInputStream( entry ); + } + } + } + catch( Exception e ) + { + log( "Ignoring Exception " + e.getClass().getName() + ": " + e.getMessage() + + " reading resource " + resourceName + " from " + file, Project.MSG_VERBOSE ); + } + + return null; + } + + /** + * Get an inputstream to a given resource in the given file which may either + * be a directory or a zip file. + * + * @param file the file (directory or jar) in which to search for the + * resource. + * @param resourceName the name of the resource for which a stream is + * required. + * @return a stream to the required resource or null if the resource cannot + * be found in the given file object + */ + private URL getResourceURL( File file, String resourceName ) + { + try + { + if( !file.exists() ) + { + return null; + } + + if( file.isDirectory() ) + { + File resource = new File( file, resourceName ); + + if( resource.exists() ) + { + try + { + return new URL( "file:" + resource.toString() ); + } + catch( MalformedURLException ex ) + { + return null; + } + } + } + else + { + ZipFile zipFile = ( ZipFile )zipFiles.get( file ); + if( zipFile == null ) + { + zipFile = new ZipFile( file ); + zipFiles.put( file, zipFile ); + } + + ZipEntry entry = zipFile.getEntry( resourceName ); + if( entry != null ) + { + try + { + return new URL( "jar:file:" + file.toString() + "!/" + entry ); + } + catch( MalformedURLException ex ) + { + return null; + } + } + } + } + catch( Exception e ) + { + e.printStackTrace(); + } + + return null; + } + + private boolean isParentFirst( String resourceName ) + { + // default to the global setting and then see + // if this class belongs to a package which has been + // designated to use a specific loader first (this one or the parent one) + boolean useParentFirst = parentFirst; + + for( Enumeration e = systemPackages.elements(); e.hasMoreElements(); ) + { + String packageName = ( String )e.nextElement(); + if( resourceName.startsWith( packageName ) ) + { + useParentFirst = true; + break; + } + } + + for( Enumeration e = loaderPackages.elements(); e.hasMoreElements(); ) + { + String packageName = ( String )e.nextElement(); + if( resourceName.startsWith( packageName ) ) + { + useParentFirst = false; + break; + } + } + + return useParentFirst; + } + + /** + * Find a system class (which should be loaded from the same classloader as + * the Ant core). + * + * @param name Description of Parameter + * @return Description of the Returned Value + * @exception ClassNotFoundException Description of Exception + */ + private Class findBaseClass( String name ) + throws ClassNotFoundException + { + if( parent == null ) + { + return findSystemClass( name ); + } + else + { + return parent.loadClass( name ); + } + } + + + /** + * Find a class on the given classpath. + * + * @param name Description of Parameter + * @return Description of the Returned Value + * @exception ClassNotFoundException Description of Exception + */ + private Class findClassInComponents( String name ) + throws ClassNotFoundException + { + // we need to search the components of the path to see if we can find the + // class we want. + InputStream stream = null; + String classFilename = getClassFilename( name ); + try + { + for( Enumeration e = pathComponents.elements(); e.hasMoreElements(); ) + { + File pathComponent = ( File )e.nextElement(); + try + { + stream = getResourceStream( pathComponent, classFilename ); + if( stream != null ) + { + return getClassFromStream( stream, name ); + } + } + catch( IOException ioe ) + { + // ioe.printStackTrace(); + log( "Exception reading component " + pathComponent, Project.MSG_VERBOSE ); + } + } + + throw new ClassNotFoundException( name ); + } + finally + { + try + { + if( stream != null ) + { + stream.close(); + } + } + catch( IOException e ) + {} + } + } + + /** + * Find a system resource (which should be loaded from the parent + * classloader). + * + * @param name Description of Parameter + * @return Description of the Returned Value + */ + private InputStream loadBaseResource( String name ) + { + if( parent == null ) + { + return getSystemResourceAsStream( name ); + } + else + { + return parent.getResourceAsStream( name ); + } + } + + + /** + * Get a stream to read the requested resource name from this loader. + * + * @param name the name of the resource for which a stream is required. + * @return a stream to the required resource or null if the resource cannot + * be found on the loader's classpath. + */ + private InputStream loadResource( String name ) + { + // we need to search the components of the path to see if we can find the + // class we want. + InputStream stream = null; + + for( Enumeration e = pathComponents.elements(); e.hasMoreElements() && stream == null; ) + { + File pathComponent = ( File )e.nextElement(); + stream = getResourceStream( pathComponent, name ); + } + return stream; + } + + /** + * An enumeration of all resources of a given name found within the + * classpath of this class loader. This enumeration is used by the {@link + * #findResources(String) findResources} method, which is in turn used by + * the {@link ClassLoader#getResources ClassLoader.getResources} method. + * + * @author David A. Herman + * @see AntClassLoader#findResources(String) + * @see java.lang.ClassLoader#getResources(String) + */ + private class ResourceEnumeration implements Enumeration + { + + /** + * The URL of the next resource to return in the enumeration. If this + * field is null then the enumeration has been completed, + * i.e., there are no more elements to return. + */ + private URL nextResource; + + /** + * The index of the next classpath element to search. + */ + private int pathElementsIndex; + + /** + * The name of the resource being searched for. + */ + private String resourceName; + + /** + * Construct a new enumeration of resources of the given name found + * within this class loader's classpath. + * + * @param name the name of the resource to search for. + */ + ResourceEnumeration( String name ) + { + this.resourceName = name; + this.pathElementsIndex = 0; + findNextResource(); + } + + /** + * Indicates whether there are more elements in the enumeration to + * return. + * + * @return true if there are more elements in the + * enumeration; false otherwise. + */ + public boolean hasMoreElements() + { + return ( this.nextResource != null ); + } + + /** + * Returns the next resource in the enumeration. + * + * @return the next resource in the enumeration. + */ + public Object nextElement() + { + URL ret = this.nextResource; + findNextResource(); + return ret; + } + + /** + * Locates the next resource of the correct name in the classpath and + * sets nextResource to the URL of that resource. If no + * more resources can be found, nextResource is set to + * null. + */ + private void findNextResource() + { + URL url = null; + while( ( pathElementsIndex < pathComponents.size() ) && + ( url == null ) ) + { + try + { + File pathComponent + = ( File )pathComponents.elementAt( pathElementsIndex ); + url = getResourceURL( pathComponent, this.resourceName ); + pathElementsIndex++; + } + catch( BuildException e ) + { + // ignore path elements which are not valid relative to the project + } + } + this.nextResource = url; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/BuildEvent.java b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildEvent.java new file mode 100644 index 000000000..feef6ff96 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildEvent.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.EventObject; + +public class BuildEvent extends EventObject +{ + private int priority = Project.MSG_VERBOSE; + private Throwable exception; + private String message; + private Project project; + private Target target; + private Task task; + + /** + * Construct a BuildEvent for a project level event + * + * @param project the project that emitted the event. + */ + public BuildEvent( Project project ) + { + super( project ); + this.project = project; + this.target = null; + this.task = null; + } + + /** + * Construct a BuildEvent for a target level event + * + * @param target the target that emitted the event. + */ + public BuildEvent( Target target ) + { + super( target ); + this.project = target.getProject(); + this.target = target; + this.task = null; + } + + /** + * Construct a BuildEvent for a task level event + * + * @param task the task that emitted the event. + */ + public BuildEvent( Task task ) + { + super( task ); + this.project = task.getProject(); + this.target = task.getOwningTarget(); + this.task = task; + } + + public void setException( Throwable exception ) + { + this.exception = exception; + } + + public void setMessage( String message, int priority ) + { + this.message = message; + this.priority = priority; + } + + /** + * Returns the exception that was thrown, if any. This field will only be + * set for "taskFinished", "targetFinished", and "buildFinished" events. + * + * @return The Exception value + * @see BuildListener#taskFinished(BuildEvent) + * @see BuildListener#targetFinished(BuildEvent) + * @see BuildListener#buildFinished(BuildEvent) + */ + public Throwable getException() + { + return exception; + } + + /** + * Returns the logging message. This field will only be set for + * "messageLogged" events. + * + * @return The Message value + * @see BuildListener#messageLogged(BuildEvent) + */ + public String getMessage() + { + return message; + } + + /** + * Returns the priority of the logging message. This field will only be set + * for "messageLogged" events. + * + * @return The Priority value + * @see BuildListener#messageLogged(BuildEvent) + */ + public int getPriority() + { + return priority; + } + + /** + * Returns the project that fired this event. + * + * @return The Project value + */ + public Project getProject() + { + return project; + } + + /** + * Returns the target that fired this event. + * + * @return The Target value + */ + public Target getTarget() + { + + return target; + } + + /** + * Returns the task that fired this event. + * + * @return The Task value + */ + public Task getTask() + { + return task; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/BuildException.java b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildException.java new file mode 100644 index 000000000..a22ef8933 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildException.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +import org.apache.myrmidon.api.TaskException; + +/** + * Signals an error condition during a build. + * + * @author James Duncan Davidson + */ +public class BuildException + extends TaskException +{ + /** + * Location in the build file where the exception occured + */ + private Location location = Location.UNKNOWN_LOCATION; + + /** + * Constructs an exception with the given descriptive message. + * + * @param msg Description of or information about the exception. + */ + public BuildException( String msg ) + { + super( msg ); + } + + /** + * Constructs an exception with the given message and exception as a root + * cause. + * + * @param msg Description of or information about the exception. + * @param cause Throwable that might have cause this one. + */ + public BuildException( String msg, Throwable cause ) + { + super( msg, cause ); + } + + /** + * Constructs an exception with the given message and exception as a root + * cause and a location in a file. + * + * @param msg Description of or information about the exception. + * @param cause Exception that might have cause this one. + * @param location Location in the project file where the error occured. + */ + public BuildException( String msg, Throwable cause, Location location ) + { + this( msg, cause ); + this.location = location; + } + + /** + * Constructs an exception with the given exception as a root cause. + * + * @param cause Exception that might have caused this one. + */ + public BuildException( Throwable cause ) + { + super( cause.toString(), cause ); + } + + /** + * Constructs an exception with the given descriptive message and a location + * in a file. + * + * @param msg Description of or information about the exception. + * @param location Location in the project file where the error occured. + */ + public BuildException( String msg, Location location ) + { + super( msg ); + this.location = location; + } + + /** + * Sets the file location where the error occured. + * + * @param location The new Location value + */ + public void setLocation( Location location ) + { + this.location = location; + } + + /** + * Returns the file location where the error occured. + * + * @return The Location value + */ + public Location getLocation() + { + return location; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/BuildListener.java b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildListener.java new file mode 100644 index 000000000..f6055badb --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildListener.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.EventListener; + +/** + * Classes that implement this interface will be notified when things happend + * during a build. + * + * @author RT + * @see BuildEvent + * @see Project#addBuildListener(BuildListener) + */ +public interface BuildListener extends EventListener +{ + + /** + * Fired before any targets are started. + * + * @param event Description of Parameter + */ + void buildStarted( BuildEvent event ); + + /** + * Fired after the last target has finished. This event will still be thrown + * if an error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + void buildFinished( BuildEvent event ); + + /** + * Fired when a target is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTarget() + */ + void targetStarted( BuildEvent event ); + + /** + * Fired when a target has finished. This event will still be thrown if an + * error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + void targetFinished( BuildEvent event ); + + /** + * Fired when a task is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTask() + */ + void taskStarted( BuildEvent event ); + + /** + * Fired when a task has finished. This event will still be throw if an + * error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + void taskFinished( BuildEvent event ); + + /** + * Fired whenever a message is logged. + * + * @param event Description of Parameter + * @see BuildEvent#getMessage() + * @see BuildEvent#getPriority() + */ + void messageLogged( BuildEvent event ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/BuildLogger.java b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildLogger.java new file mode 100644 index 000000000..9e3879d14 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/BuildLogger.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.PrintStream; + +/** + * Interface used by Ant to log the build output. A build logger is a build + * listener which has the 'right' to send output to the ant log, which is + * usually System.out unles redirected by the -logfile option. + * + * @author Conor MacNeill + */ +public interface BuildLogger extends BuildListener +{ + /** + * Set the msgOutputLevel this logger is to respond to. Only messages with a + * message level lower than or equal to the given level are output to the + * log.

+ * + * Constants for the message levels are in Project.java. The order of the + * levels, from least to most verbose, is MSG_ERR, MSG_WARN, MSG_INFO, + * MSG_VERBOSE, MSG_DEBUG. + * + * @param level the logging level for the logger. + */ + void setMessageOutputLevel( int level ); + + /** + * Set the output stream to which this logger is to send its output. + * + * @param output the output stream for the logger. + */ + void setOutputPrintStream( PrintStream output ); + + /** + * Set this logger to produce emacs (and other editor) friendly output. + * + * @param emacsMode true if output is to be unadorned so that emacs and + * other editors can parse files names, etc. + */ + void setEmacsMode( boolean emacsMode ); + + /** + * Set the output stream to which this logger is to send error messages. + * + * @param err the error stream for the logger. + */ + void setErrorPrintStream( PrintStream err ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Constants.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Constants.java new file mode 100644 index 000000000..7f26d5f4e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Constants.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Abstract interface to hold constants. + * + * @author Peter Donald + */ +interface Constants +{ +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/DefaultLogger.java b/proposal/myrmidon/src/main/org/apache/tools/ant/DefaultLogger.java new file mode 100644 index 000000000..eb578b54e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/DefaultLogger.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import org.apache.tools.ant.util.StringUtils; + +/** + * Writes build event to a PrintStream. Currently, it only writes which targets + * are being executed, and any messages that get logged. + * + * @author RT + */ +public class DefaultLogger implements BuildLogger +{ + private static int LEFT_COLUMN_SIZE = 12; + protected int msgOutputLevel = Project.MSG_ERR; + private long startTime = System.currentTimeMillis(); + + protected boolean emacsMode = false; + protected PrintStream err; + + protected PrintStream out; + + protected static String formatTime( long millis ) + { + long seconds = millis / 1000; + long minutes = seconds / 60; + + if( minutes > 0 ) + { + return Long.toString( minutes ) + " minute" + + ( minutes == 1 ? " " : "s " ) + + Long.toString( seconds % 60 ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + else + { + return Long.toString( seconds ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + + } + + /** + * Set this logger to produce emacs (and other editor) friendly output. + * + * @param emacsMode true if output is to be unadorned so that emacs and + * other editors can parse files names, etc. + */ + public void setEmacsMode( boolean emacsMode ) + { + this.emacsMode = emacsMode; + } + + /** + * Set the output stream to which this logger is to send error messages. + * + * @param err the error stream for the logger. + */ + public void setErrorPrintStream( PrintStream err ) + { + this.err = new PrintStream( err, true ); + } + + /** + * Set the msgOutputLevel this logger is to respond to. Only messages with a + * message level lower than or equal to the given level are output to the + * log.

+ * + * Constants for the message levels are in Project.java. The order of the + * levels, from least to most verbose, is MSG_ERR, MSG_WARN, MSG_INFO, + * MSG_VERBOSE, MSG_DEBUG. The default message level for DefaultLogger is + * Project.MSG_ERR. + * + * @param level the logging level for the logger. + */ + public void setMessageOutputLevel( int level ) + { + this.msgOutputLevel = level; + } + + /** + * Set the output stream to which this logger is to send its output. + * + * @param output the output stream for the logger. + */ + public void setOutputPrintStream( PrintStream output ) + { + this.out = new PrintStream( output, true ); + } + + /** + * Prints whether the build succeeded or failed, and any errors the occured + * during the build. + * + * @param event Description of Parameter + */ + public void buildFinished( BuildEvent event ) + { + Throwable error = event.getException(); + StringBuffer message = new StringBuffer(); + + if( error == null ) + { + message.append( StringUtils.LINE_SEP ); + message.append( "BUILD SUCCESSFUL" ); + } + else + { + message.append( StringUtils.LINE_SEP ); + message.append( "BUILD FAILED" ); + message.append( StringUtils.LINE_SEP ); + + if( Project.MSG_VERBOSE <= msgOutputLevel || + !( error instanceof BuildException ) ) + { + message.append( StringUtils.getStackTrace( error ) ); + } + else + { + if( error instanceof BuildException ) + { + message.append( error.toString() ).append( StringUtils.LINE_SEP ); + } + else + { + message.append( error.getMessage() ).append( StringUtils.LINE_SEP ); + } + } + } + message.append( StringUtils.LINE_SEP ); + message.append( "Total time: " + + formatTime( System.currentTimeMillis() - startTime ) ); + + String msg = message.toString(); + if( error == null ) + { + out.println( msg ); + } + else + { + err.println( msg ); + } + log( msg ); + } + + public void buildStarted( BuildEvent event ) + { + startTime = System.currentTimeMillis(); + } + + public void messageLogged( BuildEvent event ) + { + // Filter out messages based on priority + if( event.getPriority() <= msgOutputLevel ) + { + + StringBuffer message = new StringBuffer(); + // Print out the name of the task if we're in one + if( event.getTask() != null ) + { + String name = event.getTask().getTaskName(); + + if( !emacsMode ) + { + String label = "[" + name + "] "; + for( int i = 0; i < ( LEFT_COLUMN_SIZE - label.length() ); i++ ) + { + message.append( " " ); + } + message.append( label ); + } + } + + message.append( event.getMessage() ); + String msg = message.toString(); + if( event.getPriority() != Project.MSG_ERR ) + { + out.println( msg ); + } + else + { + err.println( msg ); + } + log( msg ); + } + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) + { + if( Project.MSG_INFO <= msgOutputLevel ) + { + String msg = StringUtils.LINE_SEP + event.getTarget().getName() + ":"; + out.println( msg ); + log( msg ); + } + } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + + /** + * Empty implementation which allows subclasses to receive the same output + * that is generated here. + * + * @param message Description of Parameter + */ + protected void log( String message ) { } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/DemuxOutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/ant/DemuxOutputStream.java new file mode 100644 index 000000000..ef0e5472f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/DemuxOutputStream.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Hashtable; + + +/** + * Logs content written by a thread and forwards the buffers onto the project + * object which will forward the content to the appropriate task + * + * @author Conor MacNeill + */ +public class DemuxOutputStream extends OutputStream +{ + + private final static int MAX_SIZE = 1024; + + private Hashtable buffers = new Hashtable(); +// private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + private boolean skip = false; + private boolean isErrorStream; + private Project project; + + /** + * Creates a new instance of this class. + * + * @param project Description of Parameter + * @param isErrorStream Description of Parameter + */ + public DemuxOutputStream( Project project, boolean isErrorStream ) + { + this.project = project; + this.isErrorStream = isErrorStream; + } + + /** + * Writes all remaining + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + flush(); + } + + /** + * Writes all remaining + * + * @exception IOException Description of Exception + */ + public void flush() + throws IOException + { + if( getBuffer().size() > 0 ) + { + processBuffer(); + } + } + + /** + * Write the data to the buffer and flush the buffer, if a line separator is + * detected. + * + * @param cc data to log (byte). + * @exception IOException Description of Exception + */ + public void write( int cc ) + throws IOException + { + final byte c = ( byte )cc; + if( ( c == '\n' ) || ( c == '\r' ) ) + { + if( !skip ) + { + processBuffer(); + } + } + else + { + ByteArrayOutputStream buffer = getBuffer(); + buffer.write( cc ); + if( buffer.size() > MAX_SIZE ) + { + processBuffer(); + } + } + skip = ( c == '\r' ); + } + + + /** + * Converts the buffer to a string and sends it to processLine + */ + protected void processBuffer() + { + String output = getBuffer().toString(); + project.demuxOutput( output, isErrorStream ); + resetBuffer(); + } + + private ByteArrayOutputStream getBuffer() + { + Thread current = Thread.currentThread(); + ByteArrayOutputStream buffer = ( ByteArrayOutputStream )buffers.get( current ); + if( buffer == null ) + { + buffer = new ByteArrayOutputStream(); + buffers.put( current, buffer ); + } + return buffer; + } + + private void resetBuffer() + { + Thread current = Thread.currentThread(); + buffers.remove( current ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/DesirableFilter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/DesirableFilter.java new file mode 100644 index 000000000..a53315fa2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/DesirableFilter.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FilenameFilter; + + +/** + * Filters filenames to determine whether or not the file is desirable. + * + * @author Jason Hunter [jhunter@servlets.com] + * @author james@x180.com + */ +public class DesirableFilter implements FilenameFilter +{ + + /** + * Test the given filename to determine whether or not it's desirable. This + * helps tasks filter temp files and files used by CVS. + * + * @param dir Description of Parameter + * @param name Description of Parameter + * @return Description of the Returned Value + */ + + public boolean accept( File dir, String name ) + { + + // emacs save file + if( name.endsWith( "~" ) ) + { + return false; + } + + // emacs autosave file + if( name.startsWith( "#" ) && name.endsWith( "#" ) ) + { + return false; + } + + // openwindows text editor does this I think + if( name.startsWith( "%" ) && name.endsWith( "%" ) ) + { + return false; + } + + /* + * CVS stuff -- hopefully there won't be a case with + * an all cap file/dir named "CVS" that somebody wants + * to keep around... + */ + if( name.equals( "CVS" ) ) + { + return false; + } + + /* + * If we are going to ignore CVS might as well ignore + * this one as well... + */ + if( name.equals( ".cvsignore" ) ) + { + return false; + } + + // CVS merge autosaves. + if( name.startsWith( ".#" ) ) + { + return false; + } + + // SCCS/CSSC/TeamWare: + if( name.equals( "SCCS" ) ) + { + return false; + } + + // Visual Source Save + if( name.equals( "vssver.scc" ) ) + { + return false; + } + + // default + return true; + } +} + + + + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/DirectoryScanner.java b/proposal/myrmidon/src/main/org/apache/tools/ant/DirectoryScanner.java new file mode 100644 index 000000000..7d95dd304 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/DirectoryScanner.java @@ -0,0 +1,1177 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * Class for scanning a directory for files/directories that match a certain + * criteria.

+ * + * These criteria consist of a set of include and exclude patterns. With these + * patterns, you can select which files you want to have included, and which + * files you want to have excluded.

+ * + * The idea is simple. A given directory is recursively scanned for all files + * and directories. Each file/directory is matched against a set of include and + * exclude patterns. Only files/directories that match at least one pattern of + * the include pattern list, and don't match a pattern of the exclude pattern + * list will be placed in the list of files/directories found.

+ * + * When no list of include patterns is supplied, "**" will be used, which means + * that everything will be matched. When no list of exclude patterns is + * supplied, an empty list is used, such that nothing will be excluded.

+ * + * The pattern matching is done as follows: The name to be matched is split up + * in path segments. A path segment is the name of a directory or file, which is + * bounded by File.separator ('/' under UNIX, '\' under Windows). + * E.g. "abc/def/ghi/xyz.java" is split up in the segments "abc", "def", "ghi" + * and "xyz.java". The same is done for the pattern against which should be + * matched.

+ * + * Then the segments of the name and the pattern will be matched against each + * other. When '**' is used for a path segment in the pattern, then it matches + * zero or more path segments of the name.

+ * + * There are special case regarding the use of File.separators at + * the beginningof the pattern and the string to match:
+ * When a pattern starts with a File.separator, the string to match + * must also start with a File.separator. When a pattern does not + * start with a File.separator, the string to match may not start + * with a File.separator. When one of these rules is not obeyed, + * the string will not match.

+ * + * When a name path segment is matched against a pattern path segment, the + * following special characters can be used: '*' matches zero or more + * characters, '?' matches one character.

+ * + * Examples:

+ * + * "**\*.class" matches all .class files/dirs in a directory tree.

+ * + * "test\a??.java" matches all files/dirs which start with an 'a', then two more + * characters and then ".java", in a directory called test.

+ * + * "**" matches everything in a directory tree.

+ * + * "**\test\**\XYZ*" matches all files/dirs that start with "XYZ" and where + * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").

+ * + * Case sensitivity may be turned off if necessary. By default, it is turned on. + *

+ * + * Example of usage:

+ *   String[] includes = {"**\\*.class"};
+ *   String[] excludes = {"modules\\*\\**"};
+ *   ds.setIncludes(includes);
+ *   ds.setExcludes(excludes);
+ *   ds.setBasedir(new File("test"));
+ *   ds.setCaseSensitive(true);
+ *   ds.scan();
+ *
+ *   System.out.println("FILES:");
+ *   String[] files = ds.getIncludedFiles();
+ *   for (int i = 0; i < files.length;i++) {
+ *     System.out.println(files[i]);
+ *   }
+ * 
This will scan a directory called test for .class files, but excludes + * all .class files in all directories under a directory called "modules" + * + * @author Arnout J. Kuiper ajkuiper@wxs.nl + * @author Magesh Umasankar + */ +public class DirectoryScanner implements FileScanner +{ + + /** + * Patterns that should be excluded by default. + * + * @see #addDefaultExcludes() + */ + protected final static String[] DEFAULTEXCLUDES = { + "**/*~", + "**/#*#", + "**/.#*", + "**/%*%", + "**/CVS", + "**/CVS/**", + "**/.cvsignore", + "**/SCCS", + "**/SCCS/**", + "**/vssver.scc" + }; + + /** + * Have the Vectors holding our results been built by a slow scan? + */ + protected boolean haveSlowResults = false; + + /** + * Should the file system be treated as a case sensitive one? + */ + protected boolean isCaseSensitive = true; + + /** + * Is everything we've seen so far included? + */ + protected boolean everythingIncluded = true; + + /** + * The base directory which should be scanned. + */ + protected File basedir; + + /** + * The files that where found and matched at least one includes, and also + * matched at least one excludes. + */ + protected Vector dirsExcluded; + + /** + * The directories that where found and matched at least one includes, and + * matched no excludes. + */ + protected Vector dirsIncluded; + + /** + * The directories that where found and did not match any includes. + */ + protected Vector dirsNotIncluded; + + /** + * The patterns for the files that should be excluded. + */ + protected String[] excludes; + + /** + * The files that where found and matched at least one includes, and also + * matched at least one excludes. + */ + protected Vector filesExcluded; + + /** + * The files that where found and matched at least one includes, and matched + * no excludes. + */ + protected Vector filesIncluded; + + /** + * The files that where found and did not match any includes. + */ + protected Vector filesNotIncluded; + + /** + * The patterns for the files that should be included. + */ + protected String[] includes; + + /** + * Constructor. + */ + public DirectoryScanner() { } + + + /** + * Matches a string against a pattern. The pattern contains two special + * characters: '*' which means zero or more characters, '?' which means one + * and only one character. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string that must be matched against the pattern + * @return true when the string matches against the pattern, + * false otherwise. + */ + public static boolean match( String pattern, String str ) + { + return match( pattern, str, true ); + } + + + /** + * Matches a string against a pattern. The pattern contains two special + * characters: '*' which means zero or more characters, '?' which means one + * and only one character. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string that must be matched against the pattern + * @param isCaseSensitive Description of Parameter + * @return true when the string matches against the pattern, + * false otherwise. + */ + protected static boolean match( String pattern, String str, boolean isCaseSensitive ) + { + char[] patArr = pattern.toCharArray(); + char[] strArr = str.toCharArray(); + int patIdxStart = 0; + int patIdxEnd = patArr.length - 1; + int strIdxStart = 0; + int strIdxEnd = strArr.length - 1; + char ch; + + boolean containsStar = false; + for( int i = 0; i < patArr.length; i++ ) + { + if( patArr[i] == '*' ) + { + containsStar = true; + break; + } + } + + if( !containsStar ) + { + // No '*'s, so we make a shortcut + if( patIdxEnd != strIdxEnd ) + { + return false;// Pattern and string do not have the same size + } + for( int i = 0; i <= patIdxEnd; i++ ) + { + ch = patArr[i]; + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[i] ) + { + return false;// Character mismatch + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[i] ) ) + { + return false;// Character mismatch + } + } + } + return true;// String matches against pattern + } + + if( patIdxEnd == 0 ) + { + return true;// Pattern contains only '*', which matches anything + } + + // Process characters before first star + while( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd ) + { + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[strIdxStart] ) + { + return false;// Character mismatch + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[strIdxStart] ) ) + { + return false;// Character mismatch + } + } + patIdxStart++; + strIdxStart++; + } + if( strIdxStart > strIdxEnd ) + { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( patArr[i] != '*' ) + { + return false; + } + } + return true; + } + + // Process characters after last star + while( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd ) + { + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[strIdxEnd] ) + { + return false;// Character mismatch + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[strIdxEnd] ) ) + { + return false;// Character mismatch + } + } + patIdxEnd--; + strIdxEnd--; + } + if( strIdxStart > strIdxEnd ) + { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( patArr[i] != '*' ) + { + return false; + } + } + return true; + } + + // process pattern between stars. padIdxStart and patIdxEnd point + // always to a '*'. + while( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd ) + { + int patIdxTmp = -1; + for( int i = patIdxStart + 1; i <= patIdxEnd; i++ ) + { + if( patArr[i] == '*' ) + { + patIdxTmp = i; + break; + } + } + if( patIdxTmp == patIdxStart + 1 ) + { + // Two stars next to each other, skip the first one. + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = ( patIdxTmp - patIdxStart - 1 ); + int strLength = ( strIdxEnd - strIdxStart + 1 ); + int foundIdx = -1; + strLoop : + for( int i = 0; i <= strLength - patLength; i++ ) + { + for( int j = 0; j < patLength; j++ ) + { + ch = patArr[patIdxStart + j + 1]; + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[strIdxStart + i + j] ) + { + continue strLoop; + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[strIdxStart + i + j] ) ) + { + continue strLoop; + } + } + } + + foundIdx = strIdxStart + i; + break; + } + + if( foundIdx == -1 ) + { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + // All characters in the string are used. Check if only '*'s are left + // in the pattern. If so, we succeeded. Otherwise failure. + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( patArr[i] != '*' ) + { + return false; + } + } + return true; + } + + /** + * Matches a path against a pattern. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @return true when the pattern matches against the string. + * false otherwise. + */ + protected static boolean matchPath( String pattern, String str ) + { + return matchPath( pattern, str, true ); + } + + /** + * Matches a path against a pattern. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @param isCaseSensitive must a case sensitive match be done? + * @return true when the pattern matches against the string. + * false otherwise. + */ + protected static boolean matchPath( String pattern, String str, boolean isCaseSensitive ) + { + // When str starts with a File.separator, pattern has to start with a + // File.separator. + // When pattern starts with a File.separator, str has to start with a + // File.separator. + if( str.startsWith( File.separator ) != + pattern.startsWith( File.separator ) ) + { + return false; + } + + Vector patDirs = new Vector(); + StringTokenizer st = new StringTokenizer( pattern, File.separator ); + while( st.hasMoreTokens() ) + { + patDirs.addElement( st.nextToken() ); + } + + Vector strDirs = new Vector(); + st = new StringTokenizer( str, File.separator ); + while( st.hasMoreTokens() ) + { + strDirs.addElement( st.nextToken() ); + } + + int patIdxStart = 0; + int patIdxEnd = patDirs.size() - 1; + int strIdxStart = 0; + int strIdxEnd = strDirs.size() - 1; + + // up to first '**' + while( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd ) + { + String patDir = ( String )patDirs.elementAt( patIdxStart ); + if( patDir.equals( "**" ) ) + { + break; + } + if( !match( patDir, ( String )strDirs.elementAt( strIdxStart ), isCaseSensitive ) ) + { + return false; + } + patIdxStart++; + strIdxStart++; + } + if( strIdxStart > strIdxEnd ) + { + // String is exhausted + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( !patDirs.elementAt( i ).equals( "**" ) ) + { + return false; + } + } + return true; + } + else + { + if( patIdxStart > patIdxEnd ) + { + // String not exhausted, but pattern is. Failure. + return false; + } + } + + // up to last '**' + while( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd ) + { + String patDir = ( String )patDirs.elementAt( patIdxEnd ); + if( patDir.equals( "**" ) ) + { + break; + } + if( !match( patDir, ( String )strDirs.elementAt( strIdxEnd ), isCaseSensitive ) ) + { + return false; + } + patIdxEnd--; + strIdxEnd--; + } + if( strIdxStart > strIdxEnd ) + { + // String is exhausted + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( !patDirs.elementAt( i ).equals( "**" ) ) + { + return false; + } + } + return true; + } + + while( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd ) + { + int patIdxTmp = -1; + for( int i = patIdxStart + 1; i <= patIdxEnd; i++ ) + { + if( patDirs.elementAt( i ).equals( "**" ) ) + { + patIdxTmp = i; + break; + } + } + if( patIdxTmp == patIdxStart + 1 ) + { + // '**/**' situation, so skip one + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = ( patIdxTmp - patIdxStart - 1 ); + int strLength = ( strIdxEnd - strIdxStart + 1 ); + int foundIdx = -1; + strLoop : + for( int i = 0; i <= strLength - patLength; i++ ) + { + for( int j = 0; j < patLength; j++ ) + { + String subPat = ( String )patDirs.elementAt( patIdxStart + j + 1 ); + String subStr = ( String )strDirs.elementAt( strIdxStart + i + j ); + if( !match( subPat, subStr, isCaseSensitive ) ) + { + continue strLoop; + } + } + + foundIdx = strIdxStart + i; + break; + } + + if( foundIdx == -1 ) + { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( !patDirs.elementAt( i ).equals( "**" ) ) + { + return false; + } + } + + return true; + } + + + /** + * Does the path match the start of this pattern up to the first "**".

+ * + * This is not a general purpose test and should only be used if you can + * live with false positives.

+ * + * pattern=**\\a and str=b will yield true. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @return Description of the Returned Value + */ + protected static boolean matchPatternStart( String pattern, String str ) + { + return matchPatternStart( pattern, str, true ); + } + + /** + * Does the path match the start of this pattern up to the first "**".

+ * + * This is not a general purpose test and should only be used if you can + * live with false positives.

+ * + * pattern=**\\a and str=b will yield true. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @param isCaseSensitive must matches be case sensitive? + * @return Description of the Returned Value + */ + protected static boolean matchPatternStart( String pattern, String str, + boolean isCaseSensitive ) + { + // When str starts with a File.separator, pattern has to start with a + // File.separator. + // When pattern starts with a File.separator, str has to start with a + // File.separator. + if( str.startsWith( File.separator ) != + pattern.startsWith( File.separator ) ) + { + return false; + } + + Vector patDirs = new Vector(); + StringTokenizer st = new StringTokenizer( pattern, File.separator ); + while( st.hasMoreTokens() ) + { + patDirs.addElement( st.nextToken() ); + } + + Vector strDirs = new Vector(); + st = new StringTokenizer( str, File.separator ); + while( st.hasMoreTokens() ) + { + strDirs.addElement( st.nextToken() ); + } + + int patIdxStart = 0; + int patIdxEnd = patDirs.size() - 1; + int strIdxStart = 0; + int strIdxEnd = strDirs.size() - 1; + + // up to first '**' + while( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd ) + { + String patDir = ( String )patDirs.elementAt( patIdxStart ); + if( patDir.equals( "**" ) ) + { + break; + } + if( !match( patDir, ( String )strDirs.elementAt( strIdxStart ), isCaseSensitive ) ) + { + return false; + } + patIdxStart++; + strIdxStart++; + } + + if( strIdxStart > strIdxEnd ) + { + // String is exhausted + return true; + } + else if( patIdxStart > patIdxEnd ) + { + // String not exhausted, but pattern is. Failure. + return false; + } + else + { + // pattern now holds ** while string is not exhausted + // this will generate false positives but we can live with that. + return true; + } + } + + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. All '/' and '\' characters are replaced by File.separatorChar + * . So the separator used need not match File.separatorChar. + * + * @param basedir the (non-null) basedir for scanning + */ + public void setBasedir( String basedir ) + { + setBasedir( new File( basedir.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ) ) ); + } + + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. + * + * @param basedir the basedir for scanning + */ + public void setBasedir( File basedir ) + { + this.basedir = basedir; + } + + + /** + * Sets the case sensitivity of the file system + * + * @param isCaseSensitive The new CaseSensitive value + */ + public void setCaseSensitive( boolean isCaseSensitive ) + { + this.isCaseSensitive = isCaseSensitive; + } + + + /** + * Sets the set of exclude patterns to use. All '/' and '\' characters are + * replaced by File.separatorChar. So the separator used need + * not match File.separatorChar.

+ * + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param excludes list of exclude patterns + */ + public void setExcludes( String[] excludes ) + { + if( excludes == null ) + { + this.excludes = null; + } + else + { + this.excludes = new String[excludes.length]; + for( int i = 0; i < excludes.length; i++ ) + { + String pattern; + pattern = excludes[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ); + if( pattern.endsWith( File.separator ) ) + { + pattern += "**"; + } + this.excludes[i] = pattern; + } + } + } + + /** + * Sets the set of include patterns to use. All '/' and '\' characters are + * replaced by File.separatorChar. So the separator used need + * not match File.separatorChar.

+ * + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param includes list of include patterns + */ + public void setIncludes( String[] includes ) + { + if( includes == null ) + { + this.includes = null; + } + else + { + this.includes = new String[includes.length]; + for( int i = 0; i < includes.length; i++ ) + { + String pattern; + pattern = includes[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ); + if( pattern.endsWith( File.separator ) ) + { + pattern += "**"; + } + this.includes[i] = pattern; + } + } + } + + + /** + * Gets the basedir that is used for scanning. This is the directory that is + * scanned recursively. + * + * @return the basedir that is used for scanning + */ + public File getBasedir() + { + return basedir; + } + + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the directories + */ + public String[] getExcludedDirectories() + { + slowScan(); + int count = dirsExcluded.size(); + String[] directories = new String[count]; + for( int i = 0; i < count; i++ ) + { + directories[i] = ( String )dirsExcluded.elementAt( i ); + } + return directories; + } + + + /** + * Get the names of the files that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the files + */ + public String[] getExcludedFiles() + { + slowScan(); + int count = filesExcluded.size(); + String[] files = new String[count]; + for( int i = 0; i < count; i++ ) + { + files[i] = ( String )filesExcluded.elementAt( i ); + } + return files; + } + + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched none of the exclude patterns. The names are relative + * to the basedir. + * + * @return the names of the directories + */ + public String[] getIncludedDirectories() + { + int count = dirsIncluded.size(); + String[] directories = new String[count]; + for( int i = 0; i < count; i++ ) + { + directories[i] = ( String )dirsIncluded.elementAt( i ); + } + return directories; + } + + + /** + * Get the names of the files that matched at least one of the include + * patterns, and matched none of the exclude patterns. The names are + * relative to the basedir. + * + * @return the names of the files + */ + public String[] getIncludedFiles() + { + int count = filesIncluded.size(); + String[] files = new String[count]; + for( int i = 0; i < count; i++ ) + { + files[i] = ( String )filesIncluded.elementAt( i ); + } + return files; + } + + + /** + * Get the names of the directories that matched at none of the include + * patterns. The names are relative to the basedir. + * + * @return the names of the directories + */ + public String[] getNotIncludedDirectories() + { + slowScan(); + int count = dirsNotIncluded.size(); + String[] directories = new String[count]; + for( int i = 0; i < count; i++ ) + { + directories[i] = ( String )dirsNotIncluded.elementAt( i ); + } + return directories; + } + + + /** + * Get the names of the files that matched at none of the include patterns. + * The names are relative to the basedir. + * + * @return the names of the files + */ + public String[] getNotIncludedFiles() + { + slowScan(); + int count = filesNotIncluded.size(); + String[] files = new String[count]; + for( int i = 0; i < count; i++ ) + { + files[i] = ( String )filesNotIncluded.elementAt( i ); + } + return files; + } + + /** + * Has the scanner excluded or omitted any files or directories it came + * accross? + * + * @return true if all files and directories that have been found, are + * included. + */ + public boolean isEverythingIncluded() + { + return everythingIncluded; + } + + + /** + * Adds the array with default exclusions to the current exclusions set. + */ + public void addDefaultExcludes() + { + int excludesLength = excludes == null ? 0 : excludes.length; + String[] newExcludes; + newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length]; + if( excludesLength > 0 ) + { + System.arraycopy( excludes, 0, newExcludes, 0, excludesLength ); + } + for( int i = 0; i < DEFAULTEXCLUDES.length; i++ ) + { + newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ); + } + excludes = newExcludes; + } + + + /** + * Scans the base directory for files that match at least one include + * pattern, and don't match any exclude patterns. + * + */ + public void scan() + { + if( basedir == null ) + { + throw new IllegalStateException( "No basedir set" ); + } + if( !basedir.exists() ) + { + throw new IllegalStateException( "basedir " + basedir + + " does not exist" ); + } + if( !basedir.isDirectory() ) + { + throw new IllegalStateException( "basedir " + basedir + + " is not a directory" ); + } + + if( includes == null ) + { + // No includes supplied, so set it to 'matches all' + includes = new String[1]; + includes[0] = "**"; + } + if( excludes == null ) + { + excludes = new String[0]; + } + + filesIncluded = new Vector(); + filesNotIncluded = new Vector(); + filesExcluded = new Vector(); + dirsIncluded = new Vector(); + dirsNotIncluded = new Vector(); + dirsExcluded = new Vector(); + + if( isIncluded( "" ) ) + { + if( !isExcluded( "" ) ) + { + dirsIncluded.addElement( "" ); + } + else + { + dirsExcluded.addElement( "" ); + } + } + else + { + dirsNotIncluded.addElement( "" ); + } + scandir( basedir, "", true ); + } + + /** + * Tests whether a name matches against at least one exclude pattern. + * + * @param name the name to match + * @return true when the name matches against at least one + * exclude pattern, false otherwise. + */ + protected boolean isExcluded( String name ) + { + for( int i = 0; i < excludes.length; i++ ) + { + if( matchPath( excludes[i], name, isCaseSensitive ) ) + { + return true; + } + } + return false; + } + + + /** + * Tests whether a name matches against at least one include pattern. + * + * @param name the name to match + * @return true when the name matches against at least one + * include pattern, false otherwise. + */ + protected boolean isIncluded( String name ) + { + for( int i = 0; i < includes.length; i++ ) + { + if( matchPath( includes[i], name, isCaseSensitive ) ) + { + return true; + } + } + return false; + } + + /** + * Tests whether a name matches the start of at least one include pattern. + * + * @param name the name to match + * @return true when the name matches against at least one + * include pattern, false otherwise. + */ + protected boolean couldHoldIncluded( String name ) + { + for( int i = 0; i < includes.length; i++ ) + { + if( matchPatternStart( includes[i], name, isCaseSensitive ) ) + { + return true; + } + } + return false; + } + + + /** + * Scans the passed dir for files and directories. Found files and + * directories are placed in their respective collections, based on the + * matching of includes and excludes. When a directory is found, it is + * scanned recursively. + * + * @param dir the directory to scan + * @param vpath the path relative to the basedir (needed to prevent problems + * with an absolute path when using dir) + * @param fast Description of Parameter + * @see #filesIncluded + * @see #filesNotIncluded + * @see #filesExcluded + * @see #dirsIncluded + * @see #dirsNotIncluded + * @see #dirsExcluded + */ + protected void scandir( File dir, String vpath, boolean fast ) + { + String[] newfiles = dir.list(); + + if( newfiles == null ) + { + /* + * two reasons are mentioned in the API docs for File.list + * (1) dir is not a directory. This is impossible as + * we wouldn't get here in this case. + * (2) an IO error occurred (why doesn't it throw an exception + * then???) + */ + throw new BuildException( "IO error scanning directory " + + dir.getAbsolutePath() ); + } + + for( int i = 0; i < newfiles.length; i++ ) + { + String name = vpath + newfiles[i]; + File file = new File( dir, newfiles[i] ); + if( file.isDirectory() ) + { + if( isIncluded( name ) ) + { + if( !isExcluded( name ) ) + { + dirsIncluded.addElement( name ); + if( fast ) + { + scandir( file, name + File.separator, fast ); + } + } + else + { + everythingIncluded = false; + dirsExcluded.addElement( name ); + if( fast && couldHoldIncluded( name ) ) + { + scandir( file, name + File.separator, fast ); + } + } + } + else + { + everythingIncluded = false; + dirsNotIncluded.addElement( name ); + if( fast && couldHoldIncluded( name ) ) + { + scandir( file, name + File.separator, fast ); + } + } + if( !fast ) + { + scandir( file, name + File.separator, fast ); + } + } + else if( file.isFile() ) + { + if( isIncluded( name ) ) + { + if( !isExcluded( name ) ) + { + filesIncluded.addElement( name ); + } + else + { + everythingIncluded = false; + filesExcluded.addElement( name ); + } + } + else + { + everythingIncluded = false; + filesNotIncluded.addElement( name ); + } + } + } + } + + /** + * Toplevel invocation for the scan.

+ * + * Returns immediately if a slow scan has already been requested. + */ + protected void slowScan() + { + if( haveSlowResults ) + { + return; + } + + String[] excl = new String[dirsExcluded.size()]; + dirsExcluded.copyInto( excl ); + + String[] notIncl = new String[dirsNotIncluded.size()]; + dirsNotIncluded.copyInto( notIncl ); + + for( int i = 0; i < excl.length; i++ ) + { + if( !couldHoldIncluded( excl[i] ) ) + { + scandir( new File( basedir, excl[i] ), + excl[i] + File.separator, false ); + } + } + + for( int i = 0; i < notIncl.length; i++ ) + { + if( !couldHoldIncluded( notIncl[i] ) ) + { + scandir( new File( basedir, notIncl[i] ), + notIncl[i] + File.separator, false ); + } + } + + haveSlowResults = true; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/ExitException.java b/proposal/myrmidon/src/main/org/apache/tools/ant/ExitException.java new file mode 100644 index 000000000..8e812b1f2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/ExitException.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Used to report exit status of classes which call System.exit() + * + * @author Conor MacNeill + * @see NoExitSecurityManager + */ +public class ExitException extends SecurityException +{ + + private int status; + + /** + * Constructs an exit exception. + * + * @param status the status code returned via System.exit() + */ + public ExitException( int status ) + { + super( "ExitException: status " + status ); + this.status = status; + } + + /** + * @return the status code return via System.exit() + */ + public int getStatus() + { + return status; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/FileScanner.java b/proposal/myrmidon/src/main/org/apache/tools/ant/FileScanner.java new file mode 100644 index 000000000..a23d95e26 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/FileScanner.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; + +/** + * An interface used to describe the actions required by any type of directory + * scanner. + * + * @author RT + */ +public interface FileScanner +{ + /** + * Adds an array with default exclusions to the current exclusions set. + */ + void addDefaultExcludes(); + + /** + * Gets the basedir that is used for scanning. This is the directory that is + * scanned recursively. + * + * @return the basedir that is used for scanning + */ + File getBasedir(); + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the directories + */ + String[] getExcludedDirectories(); + + /** + * Get the names of the files that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the files + */ + String[] getExcludedFiles(); + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched none of the exclude patterns. The names are relative + * to the basedir. + * + * @return the names of the directories + */ + String[] getIncludedDirectories(); + + /** + * Get the names of the files that matched at least one of the include + * patterns, an matched none of the exclude patterns. The names are relative + * to the basedir. + * + * @return the names of the files + */ + String[] getIncludedFiles(); + + /** + * Get the names of the directories that matched at none of the include + * patterns. The names are relative to the basedir. + * + * @return the names of the directories + */ + String[] getNotIncludedDirectories(); + + /** + * Get the names of the files that matched at none of the include patterns. + * The names are relative to the basedir. + * + * @return the names of the files + */ + String[] getNotIncludedFiles(); + + /** + * Scans the base directory for files that match at least one include + * pattern, and don't match any exclude patterns. + * + */ + void scan(); + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. + * + * @param basedir the (non-null) basedir for scanning + */ + void setBasedir( String basedir ); + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. + * + * @param basedir the basedir for scanning + */ + void setBasedir( File basedir ); + + /** + * Sets the set of exclude patterns to use. + * + * @param excludes list of exclude patterns + */ + void setExcludes( String[] excludes ); + + /** + * Sets the set of include patterns to use. + * + * @param includes list of include patterns + */ + void setIncludes( String[] includes ); + + /** + * Sets the case sensitivity of the file system + * + * @param isCaseSensitive The new CaseSensitive value + */ + void setCaseSensitive( boolean isCaseSensitive ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/IntrospectionHelper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/IntrospectionHelper.java new file mode 100644 index 000000000..8b7160c9f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/IntrospectionHelper.java @@ -0,0 +1,848 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; + +/** + * Helper class that collects the methods a task or nested element holds to set + * attributes, create nested elements or hold PCDATA elements. + * + * @author Stefan Bodewig + */ +public class IntrospectionHelper implements BuildListener +{ + + /** + * instances we've already created + */ + private static Hashtable helpers = new Hashtable(); + + /** + * The method to add PCDATA stuff. + */ + private Method addText = null; + + /** + * holds the attribute setter methods. + */ + private Hashtable attributeSetters; + + /** + * holds the types of the attributes that could be set. + */ + private Hashtable attributeTypes; + + /** + * The Class that's been introspected. + */ + private Class bean; + + /** + * Holds methods to create nested elements. + */ + private Hashtable nestedCreators; + + /** + * Holds methods to store configured nested elements. + */ + private Hashtable nestedStorers; + + /** + * Holds the types of nested elements that could be created. + */ + private Hashtable nestedTypes; + + private IntrospectionHelper( final Class bean ) + { + attributeTypes = new Hashtable(); + attributeSetters = new Hashtable(); + nestedTypes = new Hashtable(); + nestedCreators = new Hashtable(); + nestedStorers = new Hashtable(); + + this.bean = bean; + + Method[] methods = bean.getMethods(); + for( int i = 0; i < methods.length; i++ ) + { + final Method m = methods[i]; + final String name = m.getName(); + Class returnType = m.getReturnType(); + Class[] args = m.getParameterTypes(); + + // not really user settable properties on tasks + if( org.apache.tools.ant.Task.class.isAssignableFrom( bean ) + && args.length == 1 && + ( + ( + "setLocation".equals( name ) && org.apache.tools.ant.Location.class.equals( args[0] ) + ) || ( + "setTaskType".equals( name ) && java.lang.String.class.equals( args[0] ) + ) + ) ) + { + continue; + } + + // hide addTask for TaskContainers + if( org.apache.tools.ant.TaskContainer.class.isAssignableFrom( bean ) + && args.length == 1 && "addTask".equals( name ) + && org.apache.tools.ant.Task.class.equals( args[0] ) ) + { + continue; + } + + if( "addText".equals( name ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && java.lang.String.class.equals( args[0] ) ) + { + + addText = methods[i]; + + } + else if( name.startsWith( "set" ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && !args[0].isArray() ) + { + + String propName = getPropertyName( name, "set" ); + if( attributeSetters.get( propName ) != null ) + { + if( java.lang.String.class.equals( args[0] ) ) + { + /* + * Ignore method m, as there is an overloaded + * form of this method that takes in a + * non-string argument, which gains higher + * priority. + */ + continue; + } + /* + * If the argument is not a String, and if there + * is an overloaded form of this method already defined, + * we just override that with the new one. + * This mechanism does not guarantee any specific order + * in which the methods will be selected: so any code + * that depends on the order in which "set" methods have + * been defined, is not guaranteed to be selected in any + * particular order. + */ + } + AttributeSetter as = createAttributeSetter( m, args[0] ); + if( as != null ) + { + attributeTypes.put( propName, args[0] ); + attributeSetters.put( propName, as ); + } + + } + else if( name.startsWith( "create" ) + && !returnType.isArray() + && !returnType.isPrimitive() + && args.length == 0 ) + { + + String propName = getPropertyName( name, "create" ); + nestedTypes.put( propName, returnType ); + nestedCreators.put( propName, + new NestedCreator() + { + + public Object create( Object parent ) + throws InvocationTargetException, + IllegalAccessException + { + + return m.invoke( parent, new Object[]{} ); + } + + } ); + + } + else if( name.startsWith( "addConfigured" ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && !java.lang.String.class.equals( args[0] ) + && !args[0].isArray() + && !args[0].isPrimitive() ) + { + + try + { + final Constructor c = + args[0].getConstructor( new Class[]{} ); + String propName = getPropertyName( name, "addConfigured" ); + nestedTypes.put( propName, args[0] ); + nestedCreators.put( propName, + new NestedCreator() + { + + public Object create( Object parent ) + throws InvocationTargetException, IllegalAccessException, InstantiationException + { + + Object o = c.newInstance( new Object[]{} ); + return o; + } + + } ); + nestedStorers.put( propName, + new NestedStorer() + { + + public void store( Object parent, Object child ) + throws InvocationTargetException, IllegalAccessException, InstantiationException + { + + m.invoke( parent, new Object[]{child} ); + } + + } ); + } + catch( NoSuchMethodException nse ) + { + } + } + else if( name.startsWith( "add" ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && !java.lang.String.class.equals( args[0] ) + && !args[0].isArray() + && !args[0].isPrimitive() ) + { + + try + { + final Constructor c = + args[0].getConstructor( new Class[]{} ); + String propName = getPropertyName( name, "add" ); + nestedTypes.put( propName, args[0] ); + nestedCreators.put( propName, + new NestedCreator() + { + + public Object create( Object parent ) + throws InvocationTargetException, IllegalAccessException, InstantiationException + { + + Object o = c.newInstance( new Object[]{} ); + m.invoke( parent, new Object[]{o} ); + return o; + } + + } ); + } + catch( NoSuchMethodException nse ) + { + } + } + } + } + + /** + * Factory method for helper objects. + * + * @param c Description of Parameter + * @return The Helper value + */ + public static synchronized IntrospectionHelper getHelper( Class c ) + { + IntrospectionHelper ih = ( IntrospectionHelper )helpers.get( c ); + if( ih == null ) + { + ih = new IntrospectionHelper( c ); + helpers.put( c, ih ); + } + return ih; + } + + /** + * Sets the named attribute. + * + * @param p The new Attribute value + * @param element The new Attribute value + * @param attributeName The new Attribute value + * @param value The new Attribute value + * @exception BuildException Description of Exception + */ + public void setAttribute( Project p, Object element, String attributeName, + String value ) + throws BuildException + { + AttributeSetter as = ( AttributeSetter )attributeSetters.get( attributeName ); + if( as == null ) + { + String msg = getElementName( p, element ) + + //String msg = "Class " + element.getClass().getName() + + " doesn't support the \"" + attributeName + "\" attribute."; + throw new BuildException( msg ); + } + try + { + as.set( p, element, value ); + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + /** + * returns the type of a named attribute. + * + * @param attributeName Description of Parameter + * @return The AttributeType value + * @exception BuildException Description of Exception + */ + public Class getAttributeType( String attributeName ) + throws BuildException + { + Class at = ( Class )attributeTypes.get( attributeName ); + if( at == null ) + { + String msg = "Class " + bean.getName() + + " doesn't support the \"" + attributeName + "\" attribute."; + throw new BuildException( msg ); + } + return at; + } + + /** + * Return all attribues supported by the introspected class. + * + * @return The Attributes value + */ + public Enumeration getAttributes() + { + return attributeSetters.keys(); + } + + /** + * returns the type of a named nested element. + * + * @param elementName Description of Parameter + * @return The ElementType value + * @exception BuildException Description of Exception + */ + public Class getElementType( String elementName ) + throws BuildException + { + Class nt = ( Class )nestedTypes.get( elementName ); + if( nt == null ) + { + String msg = "Class " + bean.getName() + + " doesn't support the nested \"" + elementName + "\" element."; + throw new BuildException( msg ); + } + return nt; + } + + /** + * Return all nested elements supported by the introspected class. + * + * @return The NestedElements value + */ + public Enumeration getNestedElements() + { + return nestedTypes.keys(); + } + + /** + * Adds PCDATA areas. + * + * @param project The feature to be added to the Text attribute + * @param element The feature to be added to the Text attribute + * @param text The feature to be added to the Text attribute + */ + public void addText( Project project, Object element, String text ) + { + if( addText == null ) + { + String msg = getElementName( project, element ) + + //String msg = "Class " + element.getClass().getName() + + " doesn't support nested text data."; + throw new BuildException( msg ); + } + try + { + addText.invoke( element, new String[]{text} ); + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + public void buildFinished( BuildEvent event ) + { + attributeTypes.clear(); + attributeSetters.clear(); + nestedTypes.clear(); + nestedCreators.clear(); + addText = null; + helpers.clear(); + } + + public void buildStarted( BuildEvent event ) { } + + /** + * Creates a named nested element. + * + * @param project Description of Parameter + * @param element Description of Parameter + * @param elementName Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Object createElement( Project project, Object element, String elementName ) + throws BuildException + { + NestedCreator nc = ( NestedCreator )nestedCreators.get( elementName ); + if( nc == null ) + { + String msg = getElementName( project, element ) + + " doesn't support the nested \"" + elementName + "\" element."; + throw new BuildException( msg ); + } + try + { + Object nestedElement = nc.create( element ); + if( nestedElement instanceof ProjectComponent ) + { + ( ( ProjectComponent )nestedElement ).setProject( project ); + } + return nestedElement; + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InstantiationException ine ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ine ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + public void messageLogged( BuildEvent event ) { } + + /** + * Creates a named nested element. + * + * @param project Description of Parameter + * @param element Description of Parameter + * @param child Description of Parameter + * @param elementName Description of Parameter + * @exception BuildException Description of Exception + */ + public void storeElement( Project project, Object element, Object child, String elementName ) + throws BuildException + { + if( elementName == null ) + { + return; + } + NestedStorer ns = ( NestedStorer )nestedStorers.get( elementName ); + if( ns == null ) + { + return; + } + try + { + ns.store( element, child ); + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InstantiationException ine ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ine ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + /** + * Does the introspected class support PCDATA? + * + * @return Description of the Returned Value + */ + public boolean supportsCharacters() + { + return addText != null; + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) { } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + + protected String getElementName( Project project, Object element ) + { + Hashtable elements = project.getTaskDefinitions(); + String typeName = "task"; + if( !elements.contains( element.getClass() ) ) + { + elements = project.getDataTypeDefinitions(); + typeName = "data type"; + if( !elements.contains( element.getClass() ) ) + { + elements = null; + } + } + + if( elements != null ) + { + Enumeration e = elements.keys(); + while( e.hasMoreElements() ) + { + String elementName = ( String )e.nextElement(); + Class elementClass = ( Class )elements.get( elementName ); + if( element.getClass().equals( elementClass ) ) + { + return "The <" + elementName + "> " + typeName; + } + } + } + + return "Class " + element.getClass().getName(); + } + + /** + * extract the name of a property from a method name - subtracting a given + * prefix. + * + * @param methodName Description of Parameter + * @param prefix Description of Parameter + * @return The PropertyName value + */ + private String getPropertyName( String methodName, String prefix ) + { + int start = prefix.length(); + return methodName.substring( start ).toLowerCase( Locale.US ); + } + + /** + * Create a proper implementation of AttributeSetter for the given attribute + * type. + * + * @param m Description of Parameter + * @param arg Description of Parameter + * @return Description of the Returned Value + */ + private AttributeSetter createAttributeSetter( final Method m, + final Class arg ) + { + + // simplest case - setAttribute expects String + if( java.lang.String.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new String[]{value} ); + } + }; + // now for the primitive types, use their wrappers + } + else if( java.lang.Character.class.equals( arg ) + || java.lang.Character.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Character[]{new Character( value.charAt( 0 ) )} ); + } + + }; + } + else if( java.lang.Byte.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Byte[]{new Byte( value )} ); + } + + }; + } + else if( java.lang.Short.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Short[]{new Short( value )} ); + } + + }; + } + else if( java.lang.Integer.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Integer[]{new Integer( value )} ); + } + + }; + } + else if( java.lang.Long.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Long[]{new Long( value )} ); + } + + }; + } + else if( java.lang.Float.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Float[]{new Float( value )} ); + } + + }; + } + else if( java.lang.Double.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Double[]{new Double( value )} ); + } + + }; + // boolean gets an extra treatment, because we have a nice method + // in Project + } + else if( java.lang.Boolean.class.equals( arg ) + || java.lang.Boolean.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, + new Boolean[]{new Boolean( Project.toBoolean( value ) )} ); + } + + }; + // Class doesn't have a String constructor but a decent factory method + } + else if( java.lang.Class.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException, BuildException + { + try + { + m.invoke( parent, new Class[]{Class.forName( value )} ); + } + catch( ClassNotFoundException ce ) + { + throw new BuildException( ce ); + } + } + }; + // resolve relative paths through Project + } + else if( java.io.File.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new File[]{p.resolveFile( value )} ); + } + + }; + // resolve relative paths through Project + } + else if( org.apache.tools.ant.types.Path.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Path[]{new Path( p, value )} ); + } + + }; + // EnumeratedAttributes have their own helper class + } + else if( org.apache.tools.ant.types.EnumeratedAttribute.class.isAssignableFrom( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException, BuildException + { + try + { + org.apache.tools.ant.types.EnumeratedAttribute ea = ( org.apache.tools.ant.types.EnumeratedAttribute )arg.newInstance(); + ea.setValue( value ); + m.invoke( parent, new EnumeratedAttribute[]{ea} ); + } + catch( InstantiationException ie ) + { + throw new BuildException( ie ); + } + } + }; + + // worst case. look for a public String constructor and use it + } + else + { + + try + { + final Constructor c = + arg.getConstructor( new Class[]{java.lang.String.class} ); + + return + new AttributeSetter() + { + public void set( Project p, Object parent, + String value ) + throws InvocationTargetException, IllegalAccessException, BuildException + { + try + { + Object attribute = c.newInstance( new String[]{value} ); + if( attribute instanceof ProjectComponent ) + { + ( ( ProjectComponent )attribute ).setProject( p ); + } + m.invoke( parent, new Object[]{attribute} ); + } + catch( InstantiationException ie ) + { + throw new BuildException( ie ); + } + } + }; + } + catch( NoSuchMethodException nme ) + { + } + } + + return null; + } + + private interface AttributeSetter + { + void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException, + BuildException; + } + + private interface NestedCreator + { + Object create( Object parent ) + throws InvocationTargetException, IllegalAccessException, InstantiationException; + } + + private interface NestedStorer + { + void store( Object parent, Object child ) + throws InvocationTargetException, IllegalAccessException, InstantiationException; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Launcher.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Launcher.java new file mode 100644 index 000000000..0a220ec89 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Launcher.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FilenameFilter; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Properties; +import java.util.StringTokenizer; + +/** + * This is the Ant command line front end to end. This front end works out where + * ant is installed and loads the ant libraries before starting Ant proper. + * + * @author Conor MacNeill + */ +public class Launcher +{ + + public static void main( String[] args ) + { + File antHome = null; + ClassLoader systemClassLoader = Launcher.class.getClassLoader(); + if( systemClassLoader == null ) + { + antHome = determineAntHome11(); + } + else + { + antHome = determineAntHome( systemClassLoader ); + } + if( antHome == null ) + { + System.err.println( "Unable to determine ANT_HOME" ); + System.exit( 1 ); + } + + System.out.println( "ANT_HOME is " + antHome ); + + // We now create the class loader with which we are going to launch ant + AntClassLoader antLoader = new AntClassLoader( systemClassLoader, false ); + + // need to find tools.jar + addToolsJar( antLoader ); + + // add everything in the lib directory to this classloader + File libDir = new File( antHome, "lib" ); + addDirJars( antLoader, libDir ); + + File optionalDir = new File( antHome, "lib/optional" ); + addDirJars( antLoader, optionalDir ); + + Properties launchProperties = new Properties(); + launchProperties.put( "ant.home", antHome.getAbsolutePath() ); + + try + { + Class mainClass = antLoader.loadClass( "org.apache.tools.ant.Main" ); + antLoader.initializeClass( mainClass ); + + final Class[] param = {Class.forName( "[Ljava.lang.String;" ), + Properties.class, ClassLoader.class}; + final Method startMethod = mainClass.getMethod( "start", param ); + final Object[] argument = {args, launchProperties, systemClassLoader}; + startMethod.invoke( null, argument ); + } + catch( Exception e ) + { + System.out.println( "Exception running Ant: " + e.getClass().getName() + ": " + e.getMessage() ); + e.printStackTrace(); + } + } + + private static void addDirJars( AntClassLoader classLoader, File jarDir ) + { + String[] fileList = jarDir.list( + new FilenameFilter() + { + public boolean accept( File dir, String name ) + { + return name.endsWith( ".jar" ); + } + } ); + + if( fileList != null ) + { + for( int i = 0; i < fileList.length; ++i ) + { + File jarFile = new File( jarDir, fileList[i] ); + classLoader.addPathElement( jarFile.getAbsolutePath() ); + } + } + } + + private static void addToolsJar( AntClassLoader antLoader ) + { + String javaHome = System.getProperty( "java.home" ); + if( javaHome.endsWith( "jre" ) ) + { + javaHome = javaHome.substring( 0, javaHome.length() - 4 ); + } + System.out.println( "Java home is " + javaHome ); + File toolsJar = new File( javaHome, "lib/tools.jar" ); + if( !toolsJar.exists() ) + { + System.out.println( "Unable to find tools.jar at " + toolsJar.getPath() ); + } + else + { + antLoader.addPathElement( toolsJar.getAbsolutePath() ); + } + } + + private static File determineAntHome( ClassLoader systemClassLoader ) + { + try + { + String className = Launcher.class.getName().replace( '.', '/' ) + ".class"; + URL classResource = systemClassLoader.getResource( className ); + String fileComponent = classResource.getFile(); + if( classResource.getProtocol().equals( "file" ) ) + { + // Class comes from a directory of class files rather than + // from a jar. + int classFileIndex = fileComponent.lastIndexOf( className ); + if( classFileIndex != -1 ) + { + fileComponent = fileComponent.substring( 0, classFileIndex ); + } + File classFilesDir = new File( fileComponent ); + File buildDir = new File( classFilesDir.getParent() ); + File devAntHome = new File( buildDir.getParent() ); + return devAntHome; + } + else if( classResource.getProtocol().equals( "jar" ) ) + { + // Class is coming from a jar. The file component of the URL + // is actually the URL of the jar file + int classSeparatorIndex = fileComponent.lastIndexOf( "!" ); + if( classSeparatorIndex != -1 ) + { + fileComponent = fileComponent.substring( 0, classSeparatorIndex ); + } + URL antJarURL = new URL( fileComponent ); + File antJarFile = new File( antJarURL.getFile() ); + File libDirectory = new File( antJarFile.getParent() ); + File antHome = new File( libDirectory.getParent() ); + return antHome; + } + } + catch( MalformedURLException e ) + { + e.printStackTrace(); + } + return null; + } + + private static File determineAntHome11() + { + String classpath = System.getProperty( "java.class.path" ); + StringTokenizer tokenizer = new StringTokenizer( classpath, System.getProperty( "path.separator" ) ); + while( tokenizer.hasMoreTokens() ) + { + String path = tokenizer.nextToken(); + if( path.endsWith( "ant.jar" ) ) + { + File antJarFile = new File( path ); + File libDirectory = new File( antJarFile.getParent() ); + File antHome = new File( libDirectory.getParent() ); + return antHome; + } + } + return null; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Location.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Location.java new file mode 100644 index 000000000..e48a844dc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Location.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Stores the file name and line number in a file. + * + * @author RT + */ +public class Location +{ + + public final static Location UNKNOWN_LOCATION = new Location(); + private int columnNumber; + private String fileName; + private int lineNumber; + + /** + * Creates a location consisting of a file name but no line number. + * + * @param fileName Description of Parameter + */ + public Location( String fileName ) + { + this( fileName, 0, 0 ); + } + + /** + * Creates a location consisting of a file name and line number. + * + * @param fileName Description of Parameter + * @param lineNumber Description of Parameter + * @param columnNumber Description of Parameter + */ + public Location( String fileName, int lineNumber, int columnNumber ) + { + this.fileName = fileName; + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; + } + + /** + * Creates an "unknown" location. + */ + private Location() + { + this( null, 0, 0 ); + } + + /** + * Returns the file name, line number and a trailing space. An error message + * can be appended easily. For unknown locations, returns an empty string. + * + * @return Description of the Returned Value + */ + public String toString() + { + StringBuffer buf = new StringBuffer(); + + if( fileName != null ) + { + buf.append( fileName ); + + if( lineNumber != 0 ) + { + buf.append( ":" ); + buf.append( lineNumber ); + } + + buf.append( ": " ); + } + + return buf.toString(); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Main.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Main.java new file mode 100644 index 000000000..0c605f732 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Main.java @@ -0,0 +1,842 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Enumeration; +import java.util.Properties; +import java.util.Vector; + +/** + * Command line entry point into Ant. This class is entered via the cannonical + * `public static void main` entry point and reads the command line arguments. + * It then assembles and executes an Ant project.

+ * + * If you integrating Ant into some other tool, this is not the class to use as + * an entry point. Please see the source code of this class to see how it + * manipulates the Ant project classes. + * + * @author duncan@x180.com + */ +public class Main +{ + + /** + * The default build file name + */ + public final static String DEFAULT_BUILD_FILENAME = "build.xml"; + + private static String antVersion = null; + + /** + * Our current message output status. Follows Project.MSG_XXX + */ + private int msgOutputLevel = Project.MSG_INFO; + /** + * null + */ + + /** + * Stream that we are using for logging + */ + private PrintStream out = System.out; + + /** + * Stream that we are using for logging error messages + */ + private PrintStream err = System.err; + + /** + * The build targets + */ + private Vector targets = new Vector( 5 ); + + /** + * Set of properties that can be used by tasks + */ + private Properties definedProps = new Properties(); + + /** + * Names of classes to add as listeners to project + */ + private Vector listeners = new Vector( 5 ); + + /** + * The Ant logger class. There may be only one logger. It will have the + * right to use the 'out' PrintStream. The class must implements the + * BuildLogger interface + */ + private String loggerClassname = null; + + /** + * Indicates whether output to the log is to be unadorned. + */ + private boolean emacsMode = false; + + /** + * Indicates if this ant should be run. + */ + private boolean readyToRun = false; + + /** + * Indicates we should only parse and display the project help information + */ + private boolean projectHelp = false; + + /** + * File that we are using for configuration + */ + private File buildFile; + + protected Main( String[] args ) + throws BuildException + { + + String searchForThis = null; + + // cycle through given args + + for( int i = 0; i < args.length; i++ ) + { + String arg = args[i]; + + if( arg.equals( "-help" ) ) + { + printUsage(); + return; + } + else if( arg.equals( "-version" ) ) + { + printVersion(); + return; + } + else if( arg.equals( "-quiet" ) || arg.equals( "-q" ) ) + { + msgOutputLevel = Project.MSG_WARN; + } + else if( arg.equals( "-verbose" ) || arg.equals( "-v" ) ) + { + printVersion(); + msgOutputLevel = Project.MSG_VERBOSE; + } + else if( arg.equals( "-debug" ) ) + { + printVersion(); + msgOutputLevel = Project.MSG_DEBUG; + } + else if( arg.equals( "-logfile" ) || arg.equals( "-l" ) ) + { + try + { + File logFile = new File( args[i + 1] ); + i++; + out = new PrintStream( new FileOutputStream( logFile ) ); + err = out; + System.setOut( out ); + System.setErr( out ); + } + catch( IOException ioe ) + { + String msg = "Cannot write on the specified log file. " + + "Make sure the path exists and you have write permissions."; + System.out.println( msg ); + return; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + String msg = "You must specify a log file when " + + "using the -log argument"; + System.out.println( msg ); + return; + } + } + else if( arg.equals( "-buildfile" ) || arg.equals( "-file" ) || arg.equals( "-f" ) ) + { + try + { + buildFile = new File( args[i + 1] ); + i++; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + String msg = "You must specify a buildfile when " + + "using the -buildfile argument"; + System.out.println( msg ); + return; + } + } + else if( arg.equals( "-listener" ) ) + { + try + { + listeners.addElement( args[i + 1] ); + i++; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + String msg = "You must specify a classname when " + + "using the -listener argument"; + System.out.println( msg ); + return; + } + } + else if( arg.startsWith( "-D" ) ) + { + + /* + * Interestingly enough, we get to here when a user + * uses -Dname=value. However, in some cases, the JDK + * goes ahead * and parses this out to args + * {"-Dname", "value"} + * so instead of parsing on "=", we just make the "-D" + * characters go away and skip one argument forward. + * + * I don't know how to predict when the JDK is going + * to help or not, so we simply look for the equals sign. + */ + String name = arg.substring( 2, arg.length() ); + String value = null; + int posEq = name.indexOf( "=" ); + if( posEq > 0 ) + { + value = name.substring( posEq + 1 ); + name = name.substring( 0, posEq ); + } + else if( i < args.length - 1 ) + value = args[++i]; + + definedProps.put( name, value ); + } + else if( arg.equals( "-logger" ) ) + { + if( loggerClassname != null ) + { + System.out.println( "Only one logger class may be specified." ); + return; + } + try + { + loggerClassname = args[++i]; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + System.out.println( "You must specify a classname when " + + "using the -logger argument" ); + return; + } + } + else if( arg.equals( "-emacs" ) ) + { + emacsMode = true; + } + else if( arg.equals( "-projecthelp" ) ) + { + // set the flag to display the targets and quit + projectHelp = true; + } + else if( arg.equals( "-find" ) ) + { + // eat up next arg if present, default to build.xml + if( i < args.length - 1 ) + { + searchForThis = args[++i]; + } + else + { + searchForThis = DEFAULT_BUILD_FILENAME; + } + } + else if( arg.startsWith( "-" ) ) + { + // we don't have any more args to recognize! + String msg = "Unknown argument: " + arg; + System.out.println( msg ); + printUsage(); + return; + } + else + { + // if it's no other arg, it may be the target + targets.addElement( arg ); + } + + } + + // if buildFile was not specified on the command line, + if( buildFile == null ) + { + // but -find then search for it + if( searchForThis != null ) + { + buildFile = findBuildFile( System.getProperty( "user.dir" ), + searchForThis ); + } + else + { + buildFile = new File( DEFAULT_BUILD_FILENAME ); + } + } + + // make sure buildfile exists + if( !buildFile.exists() ) + { + System.out.println( "Buildfile: " + buildFile + " does not exist!" ); + throw new BuildException( "Build failed" ); + } + + // make sure it's not a directory (this falls into the ultra + // paranoid lets check everything catagory + + if( buildFile.isDirectory() ) + { + System.out.println( "What? Buildfile: " + buildFile + " is a dir!" ); + throw new BuildException( "Build failed" ); + } + + readyToRun = true; + } + + public static synchronized String getAntVersion() + throws BuildException + { + if( antVersion == null ) + { + try + { + Properties props = new Properties(); + InputStream in = + Main.class.getResourceAsStream( "/org/apache/tools/ant/version.txt" ); + props.load( in ); + in.close(); + + String lSep = System.getProperty( "line.separator" ); + StringBuffer msg = new StringBuffer(); + msg.append( "Apache Ant version " ); + msg.append( props.getProperty( "VERSION" ) ); + msg.append( " compiled on " ); + msg.append( props.getProperty( "DATE" ) ); + antVersion = msg.toString(); + } + catch( IOException ioe ) + { + throw new BuildException( "Could not load the version information:" + + ioe.getMessage() ); + } + catch( NullPointerException npe ) + { + throw new BuildException( "Could not load the version information." ); + } + } + return antVersion; + } + + + /** + * Command line entry point. This method kicks off the building of a project + * object and executes a build using either a given target or the default + * target. + * + * @param args Command line args. + */ + public static void main( String[] args ) + { + start( args, null, null ); + } + + /** + * Entry point method. + * + * @param args Description of Parameter + * @param additionalUserProperties Description of Parameter + * @param coreLoader Description of Parameter + */ + public static void start( String[] args, Properties additionalUserProperties, + ClassLoader coreLoader ) + { + Main m = null; + + try + { + m = new Main( args ); + } + catch( Throwable exc ) + { + printMessage( exc ); + System.exit( 1 ); + } + + if( additionalUserProperties != null ) + { + for( Enumeration e = additionalUserProperties.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + String property = additionalUserProperties.getProperty( key ); + m.definedProps.put( key, property ); + } + } + + try + { + m.runBuild( coreLoader ); + System.exit( 0 ); + } + catch( BuildException be ) + { + if( m.err != System.err ) + { + printMessage( be ); + } + System.exit( 1 ); + } + catch( Throwable exc ) + { + exc.printStackTrace(); + printMessage( exc ); + System.exit( 1 ); + } + } + + /** + * Search for the insert position to keep names a sorted list of Strings + * + * @param names Description of Parameter + * @param name Description of Parameter + * @return Description of the Returned Value + */ + private static int findTargetPosition( Vector names, String name ) + { + int res = names.size(); + for( int i = 0; i < names.size() && res == names.size(); i++ ) + { + if( name.compareTo( ( String )names.elementAt( i ) ) < 0 ) + { + res = i; + } + } + return res; + } + + /** + * Print the project description, if any + * + * @param project Description of Parameter + */ + private static void printDescription( Project project ) + { + if( project.getDescription() != null ) + { + System.out.println( project.getDescription() ); + } + } + + /** + * Prints the message of the Throwable if it's not null. + * + * @param t Description of Parameter + */ + private static void printMessage( Throwable t ) + { + String message = t.getMessage(); + if( message != null ) + { + System.err.println( message ); + } + } + + /** + * Print out a list of all targets in the current buildfile + * + * @param project Description of Parameter + * @param printSubTargets Description of Parameter + */ + private static void printTargets( Project project, boolean printSubTargets ) + { + // find the target with the longest name + int maxLength = 0; + Enumeration ptargets = project.getTargets().elements(); + String targetName; + String targetDescription; + Target currentTarget; + // split the targets in top-level and sub-targets depending + // on the presence of a description + Vector topNames = new Vector(); + Vector topDescriptions = new Vector(); + Vector subNames = new Vector(); + + while( ptargets.hasMoreElements() ) + { + currentTarget = ( Target )ptargets.nextElement(); + targetName = currentTarget.getName(); + targetDescription = currentTarget.getDescription(); + // maintain a sorted list of targets + if( targetDescription == null ) + { + int pos = findTargetPosition( subNames, targetName ); + subNames.insertElementAt( targetName, pos ); + } + else + { + int pos = findTargetPosition( topNames, targetName ); + topNames.insertElementAt( targetName, pos ); + topDescriptions.insertElementAt( targetDescription, pos ); + if( targetName.length() > maxLength ) + { + maxLength = targetName.length(); + } + } + } + + printTargets( topNames, topDescriptions, "Main targets:", maxLength ); + + if( printSubTargets ) + { + printTargets( subNames, null, "Subtargets:", 0 ); + } + + String defaultTarget = project.getDefaultTarget(); + if( defaultTarget != null && !"".equals( defaultTarget ) ) + {// shouldn't need to check but... + System.out.println( "Default target: " + defaultTarget ); + } + } + + /** + * Output a formatted list of target names with an optional description + * + * @param names Description of Parameter + * @param descriptions Description of Parameter + * @param heading Description of Parameter + * @param maxlen Description of Parameter + */ + private static void printTargets( Vector names, Vector descriptions, String heading, int maxlen ) + { + // now, start printing the targets and their descriptions + String lSep = System.getProperty( "line.separator" ); + // got a bit annoyed that I couldn't find a pad function + String spaces = " "; + while( spaces.length() < maxlen ) + { + spaces += spaces; + } + StringBuffer msg = new StringBuffer(); + msg.append( heading + lSep + lSep ); + for( int i = 0; i < names.size(); i++ ) + { + msg.append( " " ); + msg.append( names.elementAt( i ) ); + if( descriptions != null ) + { + msg.append( spaces.substring( 0, maxlen - ( ( String )names.elementAt( i ) ).length() + 2 ) ); + msg.append( descriptions.elementAt( i ) ); + } + msg.append( lSep ); + } + System.out.println( msg.toString() ); + } + + /** + * Prints the usage of how to use this class to System.out + */ + private static void printUsage() + { + String lSep = System.getProperty( "line.separator" ); + StringBuffer msg = new StringBuffer(); + msg.append( "ant [options] [target [target2 [target3] ...]]" + lSep ); + msg.append( "Options: " + lSep ); + msg.append( " -help print this message" + lSep ); + msg.append( " -projecthelp print project help information" + lSep ); + msg.append( " -version print the version information and exit" + lSep ); + msg.append( " -quiet be extra quiet" + lSep ); + msg.append( " -verbose be extra verbose" + lSep ); + msg.append( " -debug print debugging information" + lSep ); + msg.append( " -emacs produce logging information without adornments" + lSep ); + msg.append( " -logfile use given file for log" + lSep ); + msg.append( " -logger the class which is to perform logging" + lSep ); + msg.append( " -listener add an instance of class as a project listener" + lSep ); + msg.append( " -buildfile use given buildfile" + lSep ); + msg.append( " -D= use value for given property" + lSep ); + msg.append( " -find search for buildfile towards the root of the" + lSep ); + msg.append( " filesystem and use it" + lSep ); + System.out.println( msg.toString() ); + } + + private static void printVersion() + throws BuildException + { + System.out.println( getAntVersion() ); + } + + protected void addBuildListeners( Project project ) + { + + // Add the default listener + project.addBuildListener( createLogger() ); + + for( int i = 0; i < listeners.size(); i++ ) + { + String className = ( String )listeners.elementAt( i ); + try + { + BuildListener listener = + ( BuildListener )Class.forName( className ).newInstance(); + project.addBuildListener( listener ); + } + catch( Throwable exc ) + { + throw new BuildException( "Unable to instantiate listener " + className, exc ); + } + } + } + + /** + * Helper to get the parent file for a given file.

+ * + * Added to simulate File.getParentFile() from JDK 1.2. + * + * @param file File + * @return Parent file or null if none + */ + private File getParentFile( File file ) + { + String filename = file.getAbsolutePath(); + file = new File( filename ); + filename = file.getParent(); + + if( filename != null && msgOutputLevel >= Project.MSG_VERBOSE ) + { + System.out.println( "Searching in " + filename ); + } + + return ( filename == null ) ? null : new File( filename ); + } + + /** + * Creates the default build logger for sending build events to the ant log. + * + * @return Description of the Returned Value + */ + private BuildLogger createLogger() + { + BuildLogger logger = null; + if( loggerClassname != null ) + { + try + { + logger = ( BuildLogger )( Class.forName( loggerClassname ).newInstance() ); + } + catch( ClassCastException e ) + { + System.err.println( "The specified logger class " + loggerClassname + + " does not implement the BuildLogger interface" ); + throw new RuntimeException(); + } + catch( Exception e ) + { + System.err.println( "Unable to instantiate specified logger class " + + loggerClassname + " : " + e.getClass().getName() ); + throw new RuntimeException(); + } + } + else + { + logger = new DefaultLogger(); + } + + logger.setMessageOutputLevel( msgOutputLevel ); + logger.setOutputPrintStream( out ); + logger.setErrorPrintStream( err ); + logger.setEmacsMode( emacsMode ); + + return logger; + } + + /** + * Search parent directories for the build file.

+ * + * Takes the given target as a suffix to append to each parent directory in + * seach of a build file. Once the root of the file-system has been reached + * an exception is thrown. + * + * @param suffix Suffix filename to look for in parents. + * @param start Description of Parameter + * @return A handle to the build file + * @exception BuildException Failed to locate a build file + */ + private File findBuildFile( String start, String suffix ) + throws BuildException + { + if( msgOutputLevel >= Project.MSG_INFO ) + { + System.out.println( "Searching for " + suffix + " ..." ); + } + + File parent = new File( new File( start ).getAbsolutePath() ); + File file = new File( parent, suffix ); + + // check if the target file exists in the current directory + while( !file.exists() ) + { + // change to parent directory + parent = getParentFile( parent ); + + // if parent is null, then we are at the root of the fs, + // complain that we can't find the build file. + if( parent == null ) + { + throw new BuildException( "Could not locate a build file!" ); + } + + // refresh our file handle + file = new File( parent, suffix ); + } + + return file; + } + + /** + * Executes the build. + * + * @param coreLoader Description of Parameter + * @exception BuildException Description of Exception + */ + private void runBuild( ClassLoader coreLoader ) + throws BuildException + { + + if( !readyToRun ) + { + return; + } + + // track when we started + + if( msgOutputLevel >= Project.MSG_INFO ) + { + System.out.println( "Buildfile: " + buildFile ); + } + + final Project project = new Project(); + project.setCoreLoader( coreLoader ); + + Throwable error = null; + + try + { + addBuildListeners( project ); + + PrintStream err = System.err; + PrintStream out = System.out; + + // use a system manager that prevents from System.exit() + // only in JDK > 1.1 + SecurityManager oldsm = null; + if( !Project.JAVA_1_0.equals( Project.getJavaVersion() ) && + !Project.JAVA_1_1.equals( Project.getJavaVersion() ) ) + { + oldsm = System.getSecurityManager(); + + //SecurityManager can not be installed here for backwards + //compatability reasons (PD). Needs to be loaded prior to + //ant class if we are going to implement it. + //System.setSecurityManager(new NoExitSecurityManager()); + } + try + { + System.setOut( new PrintStream( new DemuxOutputStream( project, false ) ) ); + System.setErr( new PrintStream( new DemuxOutputStream( project, true ) ) ); + + if( !projectHelp ) + { + project.fireBuildStarted(); + } + project.init(); + project.setUserProperty( "ant.version", getAntVersion() ); + + // set user-define properties + Enumeration e = definedProps.keys(); + while( e.hasMoreElements() ) + { + String arg = ( String )e.nextElement(); + String value = ( String )definedProps.get( arg ); + project.setUserProperty( arg, value ); + } + + project.setUserProperty( "ant.file", buildFile.getAbsolutePath() ); + + // first use the ProjectHelper to create the project object + // from the given build file. + String noParserMessage = + "No JAXP compliant XML parser found. Please visit http://xml.apache.org for a suitable parser"; + try + { + Class.forName( "javax.xml.parsers.SAXParserFactory" ); + ProjectHelper.configureProject( project, buildFile ); + } + catch( NoClassDefFoundError ncdfe ) + { + throw new BuildException( noParserMessage, ncdfe ); + } + catch( ClassNotFoundException cnfe ) + { + throw new BuildException( noParserMessage, cnfe ); + } + catch( NullPointerException npe ) + { + throw new BuildException( noParserMessage, npe ); + } + + if( projectHelp ) + { + printDescription( project ); + printTargets( project, msgOutputLevel > Project.MSG_INFO ); + return; + } + + // make sure that we have a target to execute + if( targets.size() == 0 ) + { + targets.addElement( project.getDefaultTarget() ); + } + + project.executeTargets( targets ); + } + finally + { + // put back the original security manager + //The following will never eval to true. (PD) + if( oldsm != null ) + { + System.setSecurityManager( oldsm ); + } + + System.setOut( out ); + System.setErr( err ); + } + } + catch( RuntimeException exc ) + { + error = exc; + throw exc; + } + catch( Error err ) + { + error = err; + throw err; + } + finally + { + if( !projectHelp ) + { + project.fireBuildFinished( error ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/NoBannerLogger.java b/proposal/myrmidon/src/main/org/apache/tools/ant/NoBannerLogger.java new file mode 100644 index 000000000..9311265f5 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/NoBannerLogger.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Extends DefaultLogger to strip out empty targets. + * + * @author Peter Donald + */ +public class NoBannerLogger extends DefaultLogger +{ + + private final static String lSep = System.getProperty( "line.separator" ); + + protected String targetName; + + public void messageLogged( BuildEvent event ) + { + + if( event.getPriority() > msgOutputLevel || + null == event.getMessage() || + "".equals( event.getMessage().trim() ) ) + { + return; + } + + if( null != targetName ) + { + out.println( lSep + targetName + ":" ); + targetName = null; + } + + super.messageLogged( event ); + } + + public void targetFinished( BuildEvent event ) + { + targetName = null; + } + + public void targetStarted( BuildEvent event ) + { + targetName = event.getTarget().getName(); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/PathTokenizer.java b/proposal/myrmidon/src/main/org/apache/tools/ant/PathTokenizer.java new file mode 100644 index 000000000..c66d94a07 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/PathTokenizer.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +/** + * A Path tokenizer takes a path and returns the components that make up that + * path. The path can use path separators of either ':' or ';' and file + * separators of either '/' or '\' + * + * @author Conor MacNeill (conor@ieee.org) + */ +public class PathTokenizer +{ + + /** + * A String which stores any path components which have been read ahead. + */ + private String lookahead = null; + + /** + * Flag to indicate whether we are running on a platform with a DOS style + * filesystem + */ + private boolean dosStyleFilesystem; + /** + * A tokenizer to break the string up based on the ':' or ';' separators. + */ + private StringTokenizer tokenizer; + + public PathTokenizer( String path ) + { + tokenizer = new StringTokenizer( path, ":;", false ); + dosStyleFilesystem = File.pathSeparatorChar == ';'; + } + + public boolean hasMoreTokens() + { + if( lookahead != null ) + { + return true; + } + + return tokenizer.hasMoreTokens(); + } + + public String nextToken() + throws NoSuchElementException + { + String token = null; + if( lookahead != null ) + { + token = lookahead; + lookahead = null; + } + else + { + token = tokenizer.nextToken().trim(); + } + + if( token.length() == 1 && Character.isLetter( token.charAt( 0 ) ) + && dosStyleFilesystem + && tokenizer.hasMoreTokens() ) + { + // we are on a dos style system so this path could be a drive + // spec. We look at the next token + String nextToken = tokenizer.nextToken().trim(); + if( nextToken.startsWith( "\\" ) || nextToken.startsWith( "/" ) ) + { + // we know we are on a DOS style platform and the next path starts with a + // slash or backslash, so we know this is a drive spec + token += ":" + nextToken; + } + else + { + // store the token just read for next time + lookahead = nextToken; + } + } + + return token; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Project.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Project.java new file mode 100644 index 000000000..c509b8795 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Project.java @@ -0,0 +1,1575 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Modifier; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Stack; +import java.util.Vector; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; +import org.apache.tools.ant.util.FileUtils; + +/** + * Central representation of an Ant project. This class defines a Ant project + * with all of it's targets and tasks. It also provides the mechanism to kick + * off a build using a particular target name.

+ * + * This class also encapsulates methods which allow Files to be refered to using + * abstract path names which are translated to native system file paths at + * runtime as well as defining various project properties. + * + * @author duncan@x180.com + */ + +public class Project +{ + + public final static int MSG_ERR = 0; + public final static int MSG_WARN = 1; + public final static int MSG_INFO = 2; + public final static int MSG_VERBOSE = 3; + public final static int MSG_DEBUG = 4; + + // private set of constants to represent the state + // of a DFS of the Target dependencies + private final static String VISITING = "VISITING"; + private final static String VISITED = "VISITED"; + + public final static String JAVA_1_0 = "1.0"; + public final static String JAVA_1_1 = "1.1"; + public final static String JAVA_1_2 = "1.2"; + public final static String JAVA_1_3 = "1.3"; + public final static String JAVA_1_4 = "1.4"; + + public final static String TOKEN_START = FilterSet.DEFAULT_TOKEN_START; + public final static String TOKEN_END = FilterSet.DEFAULT_TOKEN_END; + + private static String javaVersion; + + private Hashtable properties = new Hashtable(); + private Hashtable userProperties = new Hashtable(); + private Hashtable references = new Hashtable(); + private Hashtable dataClassDefinitions = new Hashtable(); + private Hashtable taskClassDefinitions = new Hashtable(); + private Hashtable createdTasks = new Hashtable(); + private Hashtable targets = new Hashtable(); + private FilterSet globalFilterSet = new FilterSet(); + private FilterSetCollection globalFilters = new FilterSetCollection( globalFilterSet ); + + private Vector listeners = new Vector(); + + /** + * The Ant core classloader - may be null if using system loader + */ + private ClassLoader coreLoader = null; + + /** + * Records the latest task on a thread + */ + private Hashtable threadTasks = new Hashtable(); + private File baseDir; + private String defaultTarget; + private String description; + + private FileUtils fileUtils; + + private String name; + + static + { + + // Determine the Java version by looking at available classes + // java.lang.StrictMath was introduced in JDK 1.3 + // java.lang.ThreadLocal was introduced in JDK 1.2 + // java.lang.Void was introduced in JDK 1.1 + // Count up version until a NoClassDefFoundError ends the try + + try + { + javaVersion = JAVA_1_0; + Class.forName( "java.lang.Void" ); + javaVersion = JAVA_1_1; + Class.forName( "java.lang.ThreadLocal" ); + javaVersion = JAVA_1_2; + Class.forName( "java.lang.StrictMath" ); + javaVersion = JAVA_1_3; + Class.forName( "java.lang.CharSequence" ); + javaVersion = JAVA_1_4; + } + catch( ClassNotFoundException cnfe ) + { + // swallow as we've hit the max class version that + // we have + } + } + + /** + * create a new ant project + */ + public Project() + { + fileUtils = FileUtils.newFileUtils(); + } + + /** + * static query of the java version + * + * @return something like "1.1" or "1.3" + */ + public static String getJavaVersion() + { + return javaVersion; + } + + /** + * returns the boolean equivalent of a string, which is considered true if + * either "on", "true", or "yes" is found, ignoring case. + * + * @param s Description of Parameter + * @return Description of the Returned Value + */ + public static boolean toBoolean( String s ) + { + return ( s.equalsIgnoreCase( "on" ) || + s.equalsIgnoreCase( "true" ) || + s.equalsIgnoreCase( "yes" ) ); + } + + /** + * Translate a path into its native (platform specific) format.

+ * + * This method uses the PathTokenizer class to separate the input path into + * its components. This handles DOS style paths in a relatively sensible + * way. The file separators are then converted to their platform specific + * versions. + * + * @param to_process the path to be converted + * @return the native version of to_process or an empty string if to_process + * is null or empty + */ + public static String translatePath( String to_process ) + { + if( to_process == null || to_process.length() == 0 ) + { + return ""; + } + + StringBuffer path = new StringBuffer( to_process.length() + 50 ); + PathTokenizer tokenizer = new PathTokenizer( to_process ); + while( tokenizer.hasMoreTokens() ) + { + String pathComponent = tokenizer.nextToken(); + pathComponent = pathComponent.replace( '/', File.separatorChar ); + pathComponent = pathComponent.replace( '\\', File.separatorChar ); + if( path.length() != 0 ) + { + path.append( File.pathSeparatorChar ); + } + path.append( pathComponent ); + } + + return path.toString(); + } + + private static BuildException makeCircularException( String end, Stack stk ) + { + StringBuffer sb = new StringBuffer( "Circular dependency: " ); + sb.append( end ); + String c; + do + { + c = ( String )stk.pop(); + sb.append( " <- " ); + sb.append( c ); + }while ( !c.equals( end ) ); + return new BuildException( new String( sb ) ); + } + + /** + * set the base directory; XML attribute. checks for the directory existing + * and being a directory type + * + * @param baseDir project base directory. + * @throws BuildException if the directory was invalid + */ + public void setBaseDir( File baseDir ) + throws BuildException + { + baseDir = fileUtils.normalize( baseDir.getAbsolutePath() ); + if( !baseDir.exists() ) + throw new BuildException( "Basedir " + baseDir.getAbsolutePath() + " does not exist" ); + if( !baseDir.isDirectory() ) + throw new BuildException( "Basedir " + baseDir.getAbsolutePath() + " is not a directory" ); + this.baseDir = baseDir; + setPropertyInternal( "basedir", this.baseDir.getPath() ); + String msg = "Project base dir set to: " + this.baseDir; + log( msg, MSG_VERBOSE ); + } + + /** + * match basedir attribute in xml + * + * @param baseD project base directory. + * @throws BuildException if the directory was invalid + */ + public void setBasedir( String baseD ) + throws BuildException + { + setBaseDir( new File( baseD ) ); + } + + public void setCoreLoader( ClassLoader coreLoader ) + { + this.coreLoader = coreLoader; + } + + + /** + * set the default target of the project XML attribute name. + * + * @param defaultTarget The new Default value + */ + public void setDefault( String defaultTarget ) + { + this.defaultTarget = defaultTarget; + } + + /** + * set the default target of the project + * + * @param defaultTarget The new DefaultTarget value + * @see #setDefault(String) + * @deprecated, use setDefault + */ + public void setDefaultTarget( String defaultTarget ) + { + this.defaultTarget = defaultTarget; + } + + /** + * set the project description + * + * @param description text + */ + public void setDescription( String description ) + { + this.description = description; + } + + /** + * Calls File.setLastModified(long time) in a Java 1.1 compatible way. + * + * @param file The new FileLastModified value + * @param time The new FileLastModified value + * @exception BuildException Description of Exception + * @deprecated + */ + public void setFileLastModified( File file, long time ) + throws BuildException + { + if( getJavaVersion() == JAVA_1_1 ) + { + log( "Cannot change the modification time of " + file + + " in JDK 1.1", Project.MSG_WARN ); + return; + } + fileUtils.setFileLastModified( file, time ); + log( "Setting modification time for " + file, MSG_VERBOSE ); + } + + /** + * set the ant.java.version property, also tests for unsupported JVM + * versions, prints the verbose log messages + * + * @throws BuildException if this Java version is not supported + */ + public void setJavaVersionProperty() + throws BuildException + { + setPropertyInternal( "ant.java.version", javaVersion ); + + // sanity check + if( javaVersion == JAVA_1_0 ) + { + throw new BuildException( "Ant cannot work on Java 1.0" ); + } + + log( "Detected Java version: " + javaVersion + " in: " + System.getProperty( "java.home" ), MSG_VERBOSE ); + + log( "Detected OS: " + System.getProperty( "os.name" ), MSG_VERBOSE ); + } + + /** + * ant xml property. Set the project name as an attribute of this class, and + * of the property ant.project.name + * + * @param name The new Name value + */ + public void setName( String name ) + { + setUserProperty( "ant.project.name", name ); + this.name = name; + } + + /** + * set a property. An existing property of the same name will not be + * overwritten. + * + * @param name name of property + * @param value new value of the property + * @since 1.5 + */ + public void setNewProperty( String name, String value ) + { + if( null != properties.get( name ) ) + { + log( "Override ignored for property " + name, MSG_VERBOSE ); + return; + } + log( "Setting project property: " + name + " -> " + + value, MSG_DEBUG ); + properties.put( name, value ); + } + + /** + * set a property. Any existing property of the same name is overwritten, + * unless it is a user property. + * + * @param name name of property + * @param value new value of the property + */ + public void setProperty( String name, String value ) + { + // command line properties take precedence + if( null != userProperties.get( name ) ) + { + log( "Override ignored for user property " + name, MSG_VERBOSE ); + return; + } + + if( null != properties.get( name ) ) + { + log( "Overriding previous definition of property " + name, + MSG_VERBOSE ); + } + + log( "Setting project property: " + name + " -> " + + value, MSG_DEBUG ); + properties.put( name, value ); + } + + /** + * turn all the system properties into ant properties. user properties still + * override these values + */ + public void setSystemProperties() + { + Properties systemP = System.getProperties(); + Enumeration e = systemP.keys(); + while( e.hasMoreElements() ) + { + Object name = e.nextElement(); + String value = systemP.get( name ).toString(); + this.setPropertyInternal( name.toString(), value ); + } + } + + /** + * set a user property, which can not be overwritten by set/unset property + * calls + * + * @param name The new UserProperty value + * @param value The new UserProperty value + * @see #setProperty(String,String) + */ + public void setUserProperty( String name, String value ) + { + log( "Setting ro project property: " + name + " -> " + + value, MSG_DEBUG ); + userProperties.put( name, value ); + properties.put( name, value ); + } + + /** + * get the base directory of the project as a file object + * + * @return the base directory. If this is null, then the base dir is not + * valid + */ + public File getBaseDir() + { + if( baseDir == null ) + { + try + { + setBasedir( "." ); + } + catch( BuildException ex ) + { + ex.printStackTrace(); + } + } + return baseDir; + } + + public Vector getBuildListeners() + { + return listeners; + } + + public ClassLoader getCoreLoader() + { + return coreLoader; + } + + /** + * get the current task definition hashtable + * + * @return The DataTypeDefinitions value + */ + public Hashtable getDataTypeDefinitions() + { + return dataClassDefinitions; + } + + /** + * get the default target of the project + * + * @return default target or null + */ + public String getDefaultTarget() + { + return defaultTarget; + } + + /** + * get the project description + * + * @return description or null if no description has been set + */ + public String getDescription() + { + return description; + } + + /** + * @return The Filters value + * @deprecated + */ + public Hashtable getFilters() + { + // we need to build the hashtable dynamically + return globalFilterSet.getFilterHash(); + } + + + public FilterSet getGlobalFilterSet() + { + return globalFilterSet; + } + + /** + * get the project name + * + * @return name string + */ + public String getName() + { + return name; + } + + /** + * get a copy of the property hashtable + * + * @return the hashtable containing all properties, user included + */ + public Hashtable getProperties() + { + Hashtable propertiesCopy = new Hashtable(); + + Enumeration e = properties.keys(); + while( e.hasMoreElements() ) + { + Object name = e.nextElement(); + Object value = properties.get( name ); + propertiesCopy.put( name, value ); + } + + return propertiesCopy; + } + + /** + * query a property. + * + * @param name the name of the property + * @return the property value, or null for no match + */ + public String getProperty( String name ) + { + if( name == null ) + return null; + String property = ( String )properties.get( name ); + return property; + } + + /** + * @param key Description of Parameter + * @return The object with the "id" key. + */ + public Object getReference( String key ) + { + return references.get( key ); + } + + public Hashtable getReferences() + { + return references; + } + + /** + * get the target hashtable + * + * @return hashtable, the contents of which can be cast to Target + */ + public Hashtable getTargets() + { + return targets; + } + + /** + * get the current task definition hashtable + * + * @return The TaskDefinitions value + */ + public Hashtable getTaskDefinitions() + { + return taskClassDefinitions; + } + + /** + * get a copy of the user property hashtable + * + * @return the hashtable user properties only + */ + public Hashtable getUserProperties() + { + Hashtable propertiesCopy = new Hashtable(); + + Enumeration e = userProperties.keys(); + while( e.hasMoreElements() ) + { + Object name = e.nextElement(); + Object value = properties.get( name ); + propertiesCopy.put( name, value ); + } + + return propertiesCopy; + } + + /** + * query a user property. + * + * @param name the name of the property + * @return the property value, or null for no match + */ + public String getUserProperty( String name ) + { + if( name == null ) + return null; + String property = ( String )userProperties.get( name ); + return property; + } + + /** + * Topologically sort a set of Targets. + * + * @param root is the (String) name of the root Target. The sort is created + * in such a way that the sequence of Targets uptil the root target is + * the minimum possible such sequence. + * @param targets is a Hashtable representing a "name to Target" mapping + * @return a Vector of Strings with the names of the targets in sorted + * order. + * @exception BuildException if there is a cyclic dependency among the + * Targets, or if a Target does not exist. + */ + public final Vector topoSort( String root, Hashtable targets ) + throws BuildException + { + Vector ret = new Vector(); + Hashtable state = new Hashtable(); + Stack visiting = new Stack(); + + // We first run a DFS based sort using the root as the starting node. + // This creates the minimum sequence of Targets to the root node. + // We then do a sort on any remaining unVISITED targets. + // This is unnecessary for doing our build, but it catches + // circular dependencies or missing Targets on the entire + // dependency tree, not just on the Targets that depend on the + // build Target. + + tsort( root, targets, state, visiting, ret ); + log( "Build sequence for target `" + root + "' is " + ret, MSG_VERBOSE ); + for( Enumeration en = targets.keys(); en.hasMoreElements(); ) + { + String curTarget = ( String )( en.nextElement() ); + String st = ( String )state.get( curTarget ); + if( st == null ) + { + tsort( curTarget, targets, state, visiting, ret ); + } + else if( st == VISITING ) + { + throw new RuntimeException( "Unexpected node in visiting state: " + curTarget ); + } + } + log( "Complete build sequence is " + ret, MSG_VERBOSE ); + return ret; + } + + public void addBuildListener( BuildListener listener ) + { + listeners.addElement( listener ); + } + + /** + * add a new datatype + * + * @param typeName name of the datatype + * @param typeClass full datatype classname + */ + public void addDataTypeDefinition( String typeName, Class typeClass ) + { + if( null != dataClassDefinitions.get( typeName ) ) + { + log( "Trying to override old definition of datatype " + typeName, + MSG_WARN ); + } + + String msg = " +User datatype: " + typeName + " " + typeClass.getName(); + log( msg, MSG_DEBUG ); + dataClassDefinitions.put( typeName, typeClass ); + } + + /** + * @param token The feature to be added to the Filter attribute + * @param value The feature to be added to the Filter attribute + * @deprecated + */ + public void addFilter( String token, String value ) + { + if( token == null ) + { + return; + } + + globalFilterSet.addFilter( new FilterSet.Filter( token, value ) ); + } + + /** + * @param target is the Target to be added or replaced in the current + * Project. + */ + public void addOrReplaceTarget( Target target ) + { + addOrReplaceTarget( target.getName(), target ); + } + + /** + * @param target is the Target to be added/replaced in the current Project. + * @param targetName is the name to use for the Target + */ + public void addOrReplaceTarget( String targetName, Target target ) + { + String msg = " +Target: " + targetName; + log( msg, MSG_DEBUG ); + target.setProject( this ); + targets.put( targetName, target ); + } + + public void addReference( String name, Object value ) + { + if( null != references.get( name ) ) + { + log( "Overriding previous definition of reference to " + name, + MSG_WARN ); + } + log( "Adding reference: " + name + " -> " + value, MSG_DEBUG ); + references.put( name, value ); + } + + /** + * This call expects to add a new Target. + * + * @param target is the Target to be added to the current Project. + * @see Project#addOrReplaceTarget to replace existing Targets. + */ + public void addTarget( Target target ) + { + String name = target.getName(); + if( targets.get( name ) != null ) + { + throw new BuildException( "Duplicate target: `" + name + "'" ); + } + addOrReplaceTarget( name, target ); + } + + /** + * This call expects to add a new Target. + * + * @param target is the Target to be added to the current Project. + * @param targetName is the name to use for the Target + * @exception BuildException if the Target already exists in the project. + * @see Project#addOrReplaceTarget to replace existing Targets. + */ + public void addTarget( String targetName, Target target ) + throws BuildException + { + if( targets.get( targetName ) != null ) + { + throw new BuildException( "Duplicate target: `" + targetName + "'" ); + } + addOrReplaceTarget( targetName, target ); + } + + /** + * add a new task definition, complain if there is an overwrite attempt + * + * @param taskName name of the task + * @param taskClass full task classname + * @throws BuildException and logs as Project.MSG_ERR for conditions, that + * will cause the task execution to fail. + */ + public void addTaskDefinition( String taskName, Class taskClass ) + throws BuildException + { + Class old = ( Class )taskClassDefinitions.get( taskName ); + if( null != old ) + { + if( old.equals( taskClass ) ) + { + log( "Ignoring override for task " + taskName + + ", it is already defined by the same class.", + MSG_VERBOSE ); + return; + } + else + { + log( "Trying to override old definition of task " + taskName, + MSG_WARN ); + invalidateCreatedTasks( taskName ); + } + } + + String msg = " +User task: " + taskName + " " + taskClass.getName(); + log( msg, MSG_DEBUG ); + checkTaskClass( taskClass ); + taskClassDefinitions.put( taskName, taskClass ); + } + + /** + * Checks a class, whether it is suitable for serving as ant task. + * + * @param taskClass Description of Parameter + * @throws BuildException and logs as Project.MSG_ERR for conditions, that + * will cause the task execution to fail. + */ + public void checkTaskClass( final Class taskClass ) + throws BuildException + { + if( !Modifier.isPublic( taskClass.getModifiers() ) ) + { + final String message = taskClass + " is not public"; + log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + if( Modifier.isAbstract( taskClass.getModifiers() ) ) + { + final String message = taskClass + " is abstract"; + log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + try + { + taskClass.getConstructor( null ); + // don't have to check for public, since + // getConstructor finds public constructors only. + } + catch( NoSuchMethodException e ) + { + final String message = "No public default constructor in " + taskClass; + log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + if( !Task.class.isAssignableFrom( taskClass ) ) + TaskAdapter.checkTaskClass( taskClass, this ); + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile, boolean filtering ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile, boolean filtering, + boolean overwrite ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, overwrite ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile, boolean filtering, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, + overwrite, preserveLastModified ); + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile, boolean filtering ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile, boolean filtering, + boolean overwrite ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, overwrite ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile, boolean filtering, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, + overwrite, preserveLastModified ); + } + + /** + * create a new DataType instance + * + * @param typeName name of the datatype + * @return null if the datatype name is unknown + * @throws BuildException when datatype creation goes bad + */ + public Object createDataType( String typeName ) + throws BuildException + { + Class c = ( Class )dataClassDefinitions.get( typeName ); + + if( c == null ) + return null; + + try + { + java.lang.reflect.Constructor ctor = null; + boolean noArg = false; + // DataType can have a "no arg" constructor or take a single + // Project argument. + try + { + ctor = c.getConstructor( new Class[0] ); + noArg = true; + } + catch( NoSuchMethodException nse ) + { + ctor = c.getConstructor( new Class[]{Project.class} ); + noArg = false; + } + + Object o = null; + if( noArg ) + { + o = ctor.newInstance( new Object[0] ); + } + else + { + o = ctor.newInstance( new Object[]{this} ); + } + if( o instanceof ProjectComponent ) + { + ( ( ProjectComponent )o ).setProject( this ); + } + String msg = " +DataType: " + typeName; + log( msg, MSG_DEBUG ); + return o; + } + catch( java.lang.reflect.InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + String msg = "Could not create datatype of type: " + + typeName + " due to " + t; + throw new BuildException( msg, t ); + } + catch( Throwable t ) + { + String msg = "Could not create datatype of type: " + + typeName + " due to " + t; + throw new BuildException( msg, t ); + } + } + + /** + * create a new task instance + * + * @param taskType name of the task + * @return null if the task name is unknown + * @throws BuildException when task creation goes bad + */ + public Task createTask( String taskType ) + throws BuildException + { + Class c = ( Class )taskClassDefinitions.get( taskType ); + + if( c == null ) + return null; + try + { + Object o = c.newInstance(); + Task task = null; + if( o instanceof Task ) + { + task = ( Task )o; + } + else + { + // "Generic" Bean - use the setter pattern + // and an Adapter + TaskAdapter taskA = new TaskAdapter(); + taskA.setProxy( o ); + task = taskA; + } + task.setProject( this ); + task.setTaskType( taskType ); + + // set default value, can be changed by the user + task.setTaskName( taskType ); + + String msg = " +Task: " + taskType; + log( msg, MSG_DEBUG ); + addCreatedTask( taskType, task ); + return task; + } + catch( Throwable t ) + { + String msg = "Could not create task of type: " + + taskType + " due to " + t; + throw new BuildException( msg, t ); + } + } + + public void demuxOutput( String line, boolean isError ) + { + Task task = ( Task )threadTasks.get( Thread.currentThread() ); + if( task == null ) + { + fireMessageLogged( this, line, isError ? MSG_ERR : MSG_INFO ); + } + else + { + if( isError ) + { + task.handleErrorOutput( line ); + } + else + { + task.handleOutput( line ); + } + } + } + + /** + * execute the targets and any targets it depends on + * + * @param targetName the target to execute + * @throws BuildException if the build failed + */ + public void executeTarget( String targetName ) + throws BuildException + { + + // sanity check ourselves, if we've been asked to build nothing + // then we should complain + + if( targetName == null ) + { + String msg = "No target specified"; + throw new BuildException( msg ); + } + + // Sort the dependency tree, and run everything from the + // beginning until we hit our targetName. + // Sorting checks if all the targets (and dependencies) + // exist, and if there is any cycle in the dependency + // graph. + Vector sortedTargets = topoSort( targetName, targets ); + + int curidx = 0; + Target curtarget; + + do + { + curtarget = ( Target )sortedTargets.elementAt( curidx++ ); + curtarget.performTasks(); + }while ( !curtarget.getName().equals( targetName ) ); + } + + /** + * execute the sequence of targets, and the targets they depend on + * + * @param targetNames Description of Parameter + * @throws BuildException if the build failed + */ + public void executeTargets( Vector targetNames ) + throws BuildException + { + Throwable error = null; + + for( int i = 0; i < targetNames.size(); i++ ) + { + executeTarget( ( String )targetNames.elementAt( i ) ); + } + } + + /** + * Initialise the project. This involves setting the default task + * definitions and loading the system properties. + * + * @exception BuildException Description of Exception + */ + public void init() + throws BuildException + { + setJavaVersionProperty(); + + String defs = "/org/apache/tools/ant/taskdefs/defaults.properties"; + + try + { + Properties props = new Properties(); + InputStream in = this.getClass().getResourceAsStream( defs ); + if( in == null ) + { + throw new BuildException( "Can't load default task list" ); + } + props.load( in ); + in.close(); + + Enumeration enum = props.propertyNames(); + while( enum.hasMoreElements() ) + { + String key = ( String )enum.nextElement(); + String value = props.getProperty( key ); + try + { + Class taskClass = Class.forName( value ); + addTaskDefinition( key, taskClass ); + } + catch( NoClassDefFoundError ncdfe ) + { + // ignore... + } + catch( ClassNotFoundException cnfe ) + { + // ignore... + } + } + } + catch( IOException ioe ) + { + throw new BuildException( "Can't load default task list" ); + } + + String dataDefs = "/org/apache/tools/ant/types/defaults.properties"; + + try + { + Properties props = new Properties(); + InputStream in = this.getClass().getResourceAsStream( dataDefs ); + if( in == null ) + { + throw new BuildException( "Can't load default datatype list" ); + } + props.load( in ); + in.close(); + + Enumeration enum = props.propertyNames(); + while( enum.hasMoreElements() ) + { + String key = ( String )enum.nextElement(); + String value = props.getProperty( key ); + try + { + Class dataClass = Class.forName( value ); + addDataTypeDefinition( key, dataClass ); + } + catch( NoClassDefFoundError ncdfe ) + { + // ignore... + } + catch( ClassNotFoundException cnfe ) + { + // ignore... + } + } + } + catch( IOException ioe ) + { + throw new BuildException( "Can't load default datatype list" ); + } + + setSystemProperties(); + } + + /** + * Output a message to the log with the default log level of MSG_INFO + * + * @param msg text to log + */ + + public void log( String msg ) + { + log( msg, MSG_INFO ); + } + + /** + * Output a message to the log with the given log level and an event scope + * of project + * + * @param msg text to log + * @param msgLevel level to log at + */ + public void log( String msg, int msgLevel ) + { + fireMessageLogged( this, msg, msgLevel ); + } + + /** + * Output a message to the log with the given log level and an event scope + * of a task + * + * @param task task to use in the log + * @param msg text to log + * @param msgLevel level to log at + */ + public void log( Task task, String msg, int msgLevel ) + { + fireMessageLogged( task, msg, msgLevel ); + } + + /** + * Output a message to the log with the given log level and an event scope + * of a target + * + * @param target target to use in the log + * @param msg text to log + * @param msgLevel level to log at + */ + public void log( Target target, String msg, int msgLevel ) + { + fireMessageLogged( target, msg, msgLevel ); + } + + public void removeBuildListener( BuildListener listener ) + { + listeners.removeElement( listener ); + } + + /** + * Replace ${} style constructions in the given value with the string value + * of the corresponding data types. + * + * @param value the string to be scanned for property references. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public String replaceProperties( String value ) + throws BuildException + { + return ProjectHelper.replaceProperties( this, value ); + } + + /** + * Return the canonical form of fileName as an absolute path.

+ * + * If fileName is a relative file name, resolve it relative to rootDir.

+ * + * @param fileName Description of Parameter + * @param rootDir Description of Parameter + * @return Description of the Returned Value + * @deprecated + */ + public File resolveFile( String fileName, File rootDir ) + { + return fileUtils.resolveFile( rootDir, fileName ); + } + + public File resolveFile( String fileName ) + { + return fileUtils.resolveFile( baseDir, fileName ); + } + + /** + * send build finished event to the listeners + * + * @param exception exception which indicates failure if not null + */ + protected void fireBuildFinished( Throwable exception ) + { + BuildEvent event = new BuildEvent( this ); + event.setException( exception ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.buildFinished( event ); + } + } + + /** + * send build started event to the listeners + */ + protected void fireBuildStarted() + { + BuildEvent event = new BuildEvent( this ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.buildStarted( event ); + } + } + + protected void fireMessageLogged( Project project, String message, int priority ) + { + BuildEvent event = new BuildEvent( project ); + fireMessageLoggedEvent( event, message, priority ); + } + + protected void fireMessageLogged( Target target, String message, int priority ) + { + BuildEvent event = new BuildEvent( target ); + fireMessageLoggedEvent( event, message, priority ); + } + + protected void fireMessageLogged( Task task, String message, int priority ) + { + BuildEvent event = new BuildEvent( task ); + fireMessageLoggedEvent( event, message, priority ); + } + + /** + * send build finished event to the listeners + * + * @param exception exception which indicates failure if not null + * @param target Description of Parameter + */ + protected void fireTargetFinished( Target target, Throwable exception ) + { + BuildEvent event = new BuildEvent( target ); + event.setException( exception ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.targetFinished( event ); + } + } + + + /** + * send target started event to the listeners + * + * @param target Description of Parameter + */ + protected void fireTargetStarted( Target target ) + { + BuildEvent event = new BuildEvent( target ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.targetStarted( event ); + } + } + + protected void fireTaskFinished( Task task, Throwable exception ) + { + threadTasks.remove( Thread.currentThread() ); + System.out.flush(); + System.err.flush(); + BuildEvent event = new BuildEvent( task ); + event.setException( exception ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.taskFinished( event ); + } + } + + protected void fireTaskStarted( Task task ) + { + // register this as the current task on the current thread. + threadTasks.put( Thread.currentThread(), task ); + BuildEvent event = new BuildEvent( task ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.taskStarted( event ); + } + } + + /** + * Allows Project and subclasses to set a property unless its already + * defined as a user property. There are a few cases internally to Project + * that need to do this currently. + * + * @param name The new PropertyInternal value + * @param value The new PropertyInternal value + */ + private void setPropertyInternal( String name, String value ) + { + if( null != userProperties.get( name ) ) + { + return; + } + properties.put( name, value ); + } + + // one step in a recursive DFS traversal of the Target dependency tree. + // - The Hashtable "state" contains the state (VISITED or VISITING or null) + // of all the target names. + // - The Stack "visiting" contains a stack of target names that are + // currently on the DFS stack. (NB: the target names in "visiting" are + // exactly the target names in "state" that are in the VISITING state.) + // 1. Set the current target to the VISITING state, and push it onto + // the "visiting" stack. + // 2. Throw a BuildException if any child of the current node is + // in the VISITING state (implies there is a cycle.) It uses the + // "visiting" Stack to construct the cycle. + // 3. If any children have not been VISITED, tsort() the child. + // 4. Add the current target to the Vector "ret" after the children + // have been visited. Move the current target to the VISITED state. + // "ret" now contains the sorted sequence of Targets upto the current + // Target. + + private final void tsort( String root, Hashtable targets, + Hashtable state, Stack visiting, + Vector ret ) + throws BuildException + { + state.put( root, VISITING ); + visiting.push( root ); + + Target target = ( Target )( targets.get( root ) ); + + // Make sure we exist + if( target == null ) + { + StringBuffer sb = new StringBuffer( "Target `" ); + sb.append( root ); + sb.append( "' does not exist in this project. " ); + visiting.pop(); + if( !visiting.empty() ) + { + String parent = ( String )visiting.peek(); + sb.append( "It is used from target `" ); + sb.append( parent ); + sb.append( "'." ); + } + + throw new BuildException( new String( sb ) ); + } + + for( Enumeration en = target.getDependencies(); en.hasMoreElements(); ) + { + String cur = ( String )en.nextElement(); + String m = ( String )state.get( cur ); + if( m == null ) + { + // Not been visited + tsort( cur, targets, state, visiting, ret ); + } + else if( m == VISITING ) + { + // Currently visiting this node, so have a cycle + throw makeCircularException( cur, visiting ); + } + } + + String p = ( String )visiting.pop(); + if( root != p ) + { + throw new RuntimeException( "Unexpected internal error: expected to pop " + root + " but got " + p ); + } + state.put( root, VISITED ); + ret.addElement( target ); + } + + /** + * Keep a record of all tasks that have been created so that they can be + * invalidated if a taskdef overrides the definition. + * + * @param type The feature to be added to the CreatedTask attribute + * @param task The feature to be added to the CreatedTask attribute + */ + private void addCreatedTask( String type, Task task ) + { + synchronized( createdTasks ) + { + Vector v = ( Vector )createdTasks.get( type ); + if( v == null ) + { + v = new Vector(); + createdTasks.put( type, v ); + } + v.addElement( task ); + } + } + + private void fireMessageLoggedEvent( BuildEvent event, String message, int priority ) + { + event.setMessage( message, priority ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.messageLogged( event ); + } + } + + /** + * Mark tasks as invalid which no longer are of the correct type for a given + * taskname. + * + * @param type Description of Parameter + */ + private void invalidateCreatedTasks( String type ) + { + synchronized( createdTasks ) + { + Vector v = ( Vector )createdTasks.get( type ); + if( v != null ) + { + Enumeration enum = v.elements(); + while( enum.hasMoreElements() ) + { + Task t = ( Task )enum.nextElement(); + t.markInvalid(); + } + v.removeAllElements(); + createdTasks.remove( type ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/ProjectComponent.java b/proposal/myrmidon/src/main/org/apache/tools/ant/ProjectComponent.java new file mode 100644 index 000000000..c7ddf7f96 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/ProjectComponent.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +import org.apache.myrmidon.api.AbstractTask; + +/** + * Base class for components of a project, including tasks and data types. + * Provides common facilities. + * + * @author Conor MacNeill + */ + +public abstract class ProjectComponent + extends AbstractTask +{ + protected Project project = null; + + /** + * Sets the project object of this component. This method is used by project + * when a component is added to it so that the component has access to the + * functions of the project. It should not be used for any other purpose. + * + * @param project Project in whose scope this component belongs. + */ + public void setProject( Project project ) + { + this.project = project; + } + + /** + * Get the Project to which this component belongs + * + * @return the components's project. + */ + public Project getProject() + { + return project; + } + + /** + * Log a message with the default (INFO) priority. + * + * @param msg Description of Parameter + */ + public void log( String msg ) + { + log( msg, Project.MSG_INFO ); + } + + /** + * Log a mesage with the give priority. + * + * @param msgLevel the message priority at which this message is to be + * logged. + * @param msg Description of Parameter + */ + public void log( String msg, int msgLevel ) + { + if( project != null ) + { + project.log( msg, msgLevel ); + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/ProjectHelper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/ProjectHelper.java new file mode 100644 index 000000000..896a0d8f8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/ProjectHelper.java @@ -0,0 +1,1061 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Vector; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.AttributeList; +import org.xml.sax.DocumentHandler; +import org.xml.sax.HandlerBase; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * Configures a Project (complete with Targets and Tasks) based on a XML build + * file. + * + * @author duncan@x180.com + */ + +public class ProjectHelper +{ + + private static SAXParserFactory parserFactory = null; + private File buildFile; + private File buildFileParent; + private Locator locator; + + private org.xml.sax.Parser parser; + private Project project; + + /** + * Constructs a new Ant parser for the specified XML file. + * + * @param project Description of Parameter + * @param buildFile Description of Parameter + */ + private ProjectHelper( Project project, File buildFile ) + { + this.project = project; + this.buildFile = new File( buildFile.getAbsolutePath() ); + buildFileParent = new File( this.buildFile.getParent() ); + } + + /** + * Adds the content of #PCDATA sections to an element. + * + * @param project The feature to be added to the Text attribute + * @param target The feature to be added to the Text attribute + * @param buf The feature to be added to the Text attribute + * @param start The feature to be added to the Text attribute + * @param end The feature to be added to the Text attribute + * @exception BuildException Description of Exception + */ + public static void addText( Project project, Object target, char[] buf, int start, int end ) + throws BuildException + { + addText( project, target, new String( buf, start, end ) ); + } + + /** + * Adds the content of #PCDATA sections to an element. + * + * @param project The feature to be added to the Text attribute + * @param target The feature to be added to the Text attribute + * @param text The feature to be added to the Text attribute + * @exception BuildException Description of Exception + */ + public static void addText( Project project, Object target, String text ) + throws BuildException + { + + if( text == null || text.trim().length() == 0 ) + { + return; + } + + if( target instanceof TaskAdapter ) + target = ( ( TaskAdapter )target ).getProxy(); + + IntrospectionHelper.getHelper( target.getClass() ).addText( project, target, text ); + } + + public static void configure( Object target, AttributeList attrs, + Project project ) + throws BuildException + { + if( target instanceof TaskAdapter ) + target = ( ( TaskAdapter )target ).getProxy(); + + IntrospectionHelper ih = + IntrospectionHelper.getHelper( target.getClass() ); + + project.addBuildListener( ih ); + + for( int i = 0; i < attrs.getLength(); i++ ) + { + // reflect these into the target + String value = replaceProperties( project, attrs.getValue( i ), + project.getProperties() ); + try + { + ih.setAttribute( project, target, + attrs.getName( i ).toLowerCase( Locale.US ), value ); + + } + catch( BuildException be ) + { + // id attribute must be set externally + if( !attrs.getName( i ).equals( "id" ) ) + { + throw be; + } + } + } + } + + /** + * Configures the Project with the contents of the specified XML file. + * + * @param project Description of Parameter + * @param buildFile Description of Parameter + * @exception BuildException Description of Exception + */ + public static void configureProject( Project project, File buildFile ) + throws BuildException + { + new ProjectHelper( project, buildFile ).parse(); + } + + /** + * This method will parse a string containing ${value} style property values + * into two lists. The first list is a collection of text fragments, while + * the other is a set of string property names null entries in the first + * list indicate a property reference from the second list. + * + * @param value Description of Parameter + * @param fragments Description of Parameter + * @param propertyRefs Description of Parameter + * @exception BuildException Description of Exception + */ + public static void parsePropertyString( String value, Vector fragments, Vector propertyRefs ) + throws BuildException + { + int prev = 0; + int pos; + while( ( pos = value.indexOf( "$", prev ) ) >= 0 ) + { + if( pos > 0 ) + { + fragments.addElement( value.substring( prev, pos ) ); + } + + if( pos == ( value.length() - 1 ) ) + { + fragments.addElement( "$" ); + prev = pos + 1; + } + else if( value.charAt( pos + 1 ) != '{' ) + { + fragments.addElement( value.substring( pos + 1, pos + 2 ) ); + prev = pos + 2; + } + else + { + int endName = value.indexOf( '}', pos ); + if( endName < 0 ) + { + throw new BuildException( "Syntax error in property: " + + value ); + } + String propertyName = value.substring( pos + 2, endName ); + fragments.addElement( null ); + propertyRefs.addElement( propertyName ); + prev = endName + 1; + } + } + + if( prev < value.length() ) + { + fragments.addElement( value.substring( prev ) ); + } + } + + /** + * Replace ${} style constructions in the given value with the string value + * of the corresponding data types. + * + * @param value the string to be scanned for property references. + * @param project Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @since 1.5 + */ + public static String replaceProperties( Project project, String value ) + throws BuildException + { + return replaceProperties( project, value, project.getProperties() ); + } + + /** + * Replace ${} style constructions in the given value with the string value + * of the corresponding data types. + * + * @param value the string to be scanned for property references. + * @param project Description of Parameter + * @param keys Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public static String replaceProperties( Project project, String value, Hashtable keys ) + throws BuildException + { + if( value == null ) + { + return null; + } + + Vector fragments = new Vector(); + Vector propertyRefs = new Vector(); + parsePropertyString( value, fragments, propertyRefs ); + + StringBuffer sb = new StringBuffer(); + Enumeration i = fragments.elements(); + Enumeration j = propertyRefs.elements(); + while( i.hasMoreElements() ) + { + String fragment = ( String )i.nextElement(); + if( fragment == null ) + { + String propertyName = ( String )j.nextElement(); + if( !keys.containsKey( propertyName ) ) + { + project.log( "Property ${" + propertyName + "} has not been set", Project.MSG_VERBOSE ); + } + fragment = ( keys.containsKey( propertyName ) ) ? ( String )keys.get( propertyName ) + : "${" + propertyName + "}"; + } + sb.append( fragment ); + } + + return sb.toString(); + } + + /** + * Stores a configured child element into its parent object + * + * @param project Description of Parameter + * @param parent Description of Parameter + * @param child Description of Parameter + * @param tag Description of Parameter + */ + public static void storeChild( Project project, Object parent, Object child, String tag ) + { + IntrospectionHelper ih = IntrospectionHelper.getHelper( parent.getClass() ); + ih.storeElement( project, parent, child, tag ); + } + + private static SAXParserFactory getParserFactory() + { + if( parserFactory == null ) + { + parserFactory = SAXParserFactory.newInstance(); + } + + return parserFactory; + } + + /** + * Scan AttributeList for the id attribute and maybe add a reference to + * project.

+ * + * Moved out of {@link #configure configure} to make it happen at parser + * time.

+ * + * @param target Description of Parameter + * @param attr Description of Parameter + */ + private void configureId( Object target, AttributeList attr ) + { + String id = attr.getValue( "id" ); + if( id != null ) + { + project.addReference( id, target ); + } + } + + /** + * Parses the project file. + * + * @exception BuildException Description of Exception + */ + private void parse() + throws BuildException + { + FileInputStream inputStream = null; + InputSource inputSource = null; + + try + { + SAXParser saxParser = getParserFactory().newSAXParser(); + parser = saxParser.getParser(); + + String uri = "file:" + buildFile.getAbsolutePath().replace( '\\', '/' ); + for( int index = uri.indexOf( '#' ); index != -1; index = uri.indexOf( '#' ) ) + { + uri = uri.substring( 0, index ) + "%23" + uri.substring( index + 1 ); + } + + inputStream = new FileInputStream( buildFile ); + inputSource = new InputSource( inputStream ); + inputSource.setSystemId( uri ); + project.log( "parsing buildfile " + buildFile + " with URI = " + uri, Project.MSG_VERBOSE ); + saxParser.parse( inputSource, new RootHandler() ); + } + catch( ParserConfigurationException exc ) + { + throw new BuildException( "Parser has not been configured correctly", exc ); + } + catch( SAXParseException exc ) + { + Location location = + new Location( buildFile.toString(), exc.getLineNumber(), exc.getColumnNumber() ); + + Throwable t = exc.getException(); + if( t instanceof BuildException ) + { + BuildException be = ( BuildException )t; + if( be.getLocation() == Location.UNKNOWN_LOCATION ) + { + be.setLocation( location ); + } + throw be; + } + + throw new BuildException( exc.getMessage(), t, location ); + } + catch( SAXException exc ) + { + Throwable t = exc.getException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( exc.getMessage(), t ); + } + catch( FileNotFoundException exc ) + { + throw new BuildException( exc ); + } + catch( IOException exc ) + { + throw new BuildException( "Error reading project file", exc ); + } + finally + { + if( inputStream != null ) + { + try + { + inputStream.close(); + } + catch( IOException ioe ) + { + // ignore this + } + } + } + } + + /** + * The common superclass for all sax event handlers in Ant. Basically throws + * an exception in each method, so subclasses should override what they can + * handle. Each type of xml element (task, target, etc) in ant will have its + * own subclass of AbstractHandler. In the constructor, this class takes + * over the handling of sax events from the parent handler, and returns + * control back to the parent in the endElement method. + * + * @author RT + */ + private class AbstractHandler extends HandlerBase + { + protected DocumentHandler parentHandler; + + public AbstractHandler( DocumentHandler parentHandler ) + { + this.parentHandler = parentHandler; + + // Start handling SAX events + parser.setDocumentHandler( this ); + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + String s = new String( buf, start, end ).trim(); + + if( s.length() > 0 ) + { + throw new SAXParseException( "Unexpected text \"" + s + "\"", locator ); + } + } + + public void endElement( String name ) + throws SAXException + { + + finished(); + // Let parent resume handling SAX events + parser.setDocumentHandler( parentHandler ); + } + + public void startElement( String tag, AttributeList attrs ) + throws SAXParseException + { + throw new SAXParseException( "Unexpected element \"" + tag + "\"", locator ); + } + + /** + * Called when this element and all elements nested into it have been + * handled. + */ + protected void finished() { } + } + + /** + * Handler for all data types at global level. + * + * @author RT + */ + private class DataTypeHandler extends AbstractHandler + { + private RuntimeConfigurable wrapper = null; + private Object element; + private Target target; + + public DataTypeHandler( DocumentHandler parentHandler ) + { + this( parentHandler, null ); + } + + public DataTypeHandler( DocumentHandler parentHandler, Target target ) + { + super( parentHandler ); + this.target = target; + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + try + { + addText( project, element, buf, start, end ); + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + + public void init( String propType, AttributeList attrs ) + throws SAXParseException + { + try + { + element = project.createDataType( propType ); + if( element == null ) + { + throw new BuildException( "Unknown data type " + propType ); + } + + if( target != null ) + { + wrapper = new RuntimeConfigurable( element, propType ); + wrapper.setAttributes( attrs ); + target.addDataType( wrapper ); + } + else + { + configure( element, attrs, project ); + configureId( element, attrs ); + } + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + new NestedElementHandler( this, element, wrapper, target ).init( name, attrs ); + } + } + + /** + * Handler for all nested properties. + * + * @author RT + */ + private class NestedElementHandler extends AbstractHandler + { + private RuntimeConfigurable childWrapper = null; + private Object child; + private Object parent; + private RuntimeConfigurable parentWrapper; + private Target target; + + public NestedElementHandler( DocumentHandler parentHandler, + Object parent, + RuntimeConfigurable parentWrapper, + Target target ) + { + super( parentHandler ); + + if( parent instanceof TaskAdapter ) + { + this.parent = ( ( TaskAdapter )parent ).getProxy(); + } + else + { + this.parent = parent; + } + this.parentWrapper = parentWrapper; + this.target = target; + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + if( parentWrapper == null ) + { + try + { + addText( project, child, buf, start, end ); + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + else + { + childWrapper.addText( buf, start, end ); + } + } + + public void init( String propType, AttributeList attrs ) + throws SAXParseException + { + Class parentClass = parent.getClass(); + IntrospectionHelper ih = + IntrospectionHelper.getHelper( parentClass ); + + try + { + String elementName = propType.toLowerCase( Locale.US ); + if( parent instanceof UnknownElement ) + { + UnknownElement uc = new UnknownElement( elementName ); + uc.setProject( project ); + ( ( UnknownElement )parent ).addChild( uc ); + child = uc; + } + else + { + child = ih.createElement( project, parent, elementName ); + } + + configureId( child, attrs ); + + if( parentWrapper != null ) + { + childWrapper = new RuntimeConfigurable( child, propType ); + childWrapper.setAttributes( attrs ); + parentWrapper.addChild( childWrapper ); + } + else + { + configure( child, attrs, project ); + ih.storeElement( project, parent, child, elementName ); + } + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( child instanceof TaskContainer ) + { + // taskcontainer nested element can contain other tasks - no other + // nested elements possible + new TaskHandler( this, ( TaskContainer )child, childWrapper, target ).init( name, attrs ); + } + else + { + new NestedElementHandler( this, child, childWrapper, target ).init( name, attrs ); + } + } + } + + /** + * Handler for the top level "project" element. + * + * @author RT + */ + private class ProjectHandler extends AbstractHandler + { + public ProjectHandler( DocumentHandler parentHandler ) + { + super( parentHandler ); + } + + public void init( String tag, AttributeList attrs ) + throws SAXParseException + { + String def = null; + String name = null; + String id = null; + String baseDir = null; + + for( int i = 0; i < attrs.getLength(); i++ ) + { + String key = attrs.getName( i ); + String value = attrs.getValue( i ); + + if( key.equals( "default" ) ) + { + def = value; + } + else if( key.equals( "name" ) ) + { + name = value; + } + else if( key.equals( "id" ) ) + { + id = value; + } + else if( key.equals( "basedir" ) ) + { + baseDir = value; + } + else + { + throw new SAXParseException( "Unexpected attribute \"" + attrs.getName( i ) + "\"", locator ); + } + } + + if( def == null ) + { + throw new SAXParseException( "The default attribute of project is required", + locator ); + } + + project.setDefaultTarget( def ); + + if( name != null ) + { + project.setName( name ); + project.addReference( name, project ); + } + + if( id != null ) + project.addReference( id, project ); + + if( project.getProperty( "basedir" ) != null ) + { + project.setBasedir( project.getProperty( "basedir" ) ); + } + else + { + if( baseDir == null ) + { + project.setBasedir( buildFileParent.getAbsolutePath() ); + } + else + { + // check whether the user has specified an absolute path + if( ( new File( baseDir ) ).isAbsolute() ) + { + project.setBasedir( baseDir ); + } + else + { + project.setBaseDir( project.resolveFile( baseDir, buildFileParent ) ); + } + } + } + + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( name.equals( "taskdef" ) ) + { + handleTaskdef( name, attrs ); + } + else if( name.equals( "typedef" ) ) + { + handleTypedef( name, attrs ); + } + else if( name.equals( "property" ) ) + { + handleProperty( name, attrs ); + } + else if( name.equals( "target" ) ) + { + handleTarget( name, attrs ); + } + else if( project.getDataTypeDefinitions().get( name ) != null ) + { + handleDataType( name, attrs ); + } + else + { + throw new SAXParseException( "Unexpected element \"" + name + "\"", locator ); + } + } + + private void handleDataType( String name, AttributeList attrs ) + throws SAXParseException + { + new DataTypeHandler( this ).init( name, attrs ); + } + + private void handleProperty( String name, AttributeList attrs ) + throws SAXParseException + { + ( new TaskHandler( this, null, null, null ) ).init( name, attrs ); + } + + private void handleTarget( String tag, AttributeList attrs ) + throws SAXParseException + { + new TargetHandler( this ).init( tag, attrs ); + } + + private void handleTaskdef( String name, AttributeList attrs ) + throws SAXParseException + { + ( new TaskHandler( this, null, null, null ) ).init( name, attrs ); + } + + private void handleTypedef( String name, AttributeList attrs ) + throws SAXParseException + { + ( new TaskHandler( this, null, null, null ) ).init( name, attrs ); + } + + } + + /** + * Handler for the root element. It's only child must be the "project" + * element. + * + * @author RT + */ + private class RootHandler extends HandlerBase + { + + public void setDocumentLocator( Locator locator ) + { + ProjectHelper.this.locator = locator; + } + + /** + * resolve file: URIs as relative to the build file. + * + * @param publicId Description of Parameter + * @param systemId Description of Parameter + * @return Description of the Returned Value + */ + public InputSource resolveEntity( String publicId, + String systemId ) + { + + project.log( "resolving systemId: " + systemId, Project.MSG_VERBOSE ); + + if( systemId.startsWith( "file:" ) ) + { + String path = systemId.substring( 5 ); + int index = path.indexOf( "file:" ); + + // we only have to handle these for backward compatibility + // since they are in the FAQ. + while( index != -1 ) + { + path = path.substring( 0, index ) + path.substring( index + 5 ); + index = path.indexOf( "file:" ); + } + + String entitySystemId = path; + index = path.indexOf( "%23" ); + // convert these to # + while( index != -1 ) + { + path = path.substring( 0, index ) + "#" + path.substring( index + 3 ); + index = path.indexOf( "%23" ); + } + + File file = new File( path ); + if( !file.isAbsolute() ) + { + file = new File( buildFileParent, path ); + } + + try + { + InputSource inputSource = new InputSource( new FileInputStream( file ) ); + inputSource.setSystemId( "file:" + entitySystemId ); + return inputSource; + } + catch( FileNotFoundException fne ) + { + project.log( file.getAbsolutePath() + " could not be found", + Project.MSG_WARN ); + } + } + // use default if not file or file not found + return null; + } + + public void startElement( String tag, AttributeList attrs ) + throws SAXParseException + { + if( tag.equals( "project" ) ) + { + new ProjectHandler( this ).init( tag, attrs ); + } + else + { + throw new SAXParseException( "Config file is not of expected XML type", locator ); + } + } + } + + /** + * Handler for "target" elements. + * + * @author RT + */ + private class TargetHandler extends AbstractHandler + { + private Target target; + + public TargetHandler( DocumentHandler parentHandler ) + { + super( parentHandler ); + } + + public void init( String tag, AttributeList attrs ) + throws SAXParseException + { + String name = null; + String depends = ""; + String ifCond = null; + String unlessCond = null; + String id = null; + String description = null; + + for( int i = 0; i < attrs.getLength(); i++ ) + { + String key = attrs.getName( i ); + String value = attrs.getValue( i ); + + if( key.equals( "name" ) ) + { + name = value; + } + else if( key.equals( "depends" ) ) + { + depends = value; + } + else if( key.equals( "if" ) ) + { + ifCond = value; + } + else if( key.equals( "unless" ) ) + { + unlessCond = value; + } + else if( key.equals( "id" ) ) + { + id = value; + } + else if( key.equals( "description" ) ) + { + description = value; + } + else + { + throw new SAXParseException( "Unexpected attribute \"" + key + "\"", locator ); + } + } + + if( name == null ) + { + throw new SAXParseException( "target element appears without a name attribute", locator ); + } + + target = new Target(); + target.setName( name ); + target.setIf( ifCond ); + target.setUnless( unlessCond ); + target.setDescription( description ); + project.addTarget( name, target ); + + if( id != null && !id.equals( "" ) ) + project.addReference( id, target ); + + // take care of dependencies + + if( depends.length() > 0 ) + { + target.setDepends( depends ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( project.getDataTypeDefinitions().get( name ) != null ) + { + new DataTypeHandler( this, target ).init( name, attrs ); + } + else + { + new TaskHandler( this, target, null, target ).init( name, attrs ); + } + } + } + + /** + * Handler for all task elements. + * + * @author RT + */ + private class TaskHandler extends AbstractHandler + { + private RuntimeConfigurable wrapper = null; + private TaskContainer container; + private RuntimeConfigurable parentWrapper; + private Target target; + private Task task; + + public TaskHandler( DocumentHandler parentHandler, TaskContainer container, RuntimeConfigurable parentWrapper, Target target ) + { + super( parentHandler ); + this.container = container; + this.parentWrapper = parentWrapper; + this.target = target; + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + if( wrapper == null ) + { + try + { + addText( project, task, buf, start, end ); + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + else + { + wrapper.addText( buf, start, end ); + } + } + + public void init( String tag, AttributeList attrs ) + throws SAXParseException + { + try + { + task = project.createTask( tag ); + } + catch( BuildException e ) + { + // swallow here, will be thrown again in + // UnknownElement.maybeConfigure if the problem persists. + } + + if( task == null ) + { + task = new UnknownElement( tag ); + task.setProject( project ); + task.setTaskType( tag ); + task.setTaskName( tag ); + } + + task.setLocation( new Location( buildFile.toString(), locator.getLineNumber(), locator.getColumnNumber() ) ); + configureId( task, attrs ); + + // Top level tasks don't have associated targets + if( target != null ) + { + task.setOwningTarget( target ); + container.addTask( task ); + task.init(); + wrapper = task.getRuntimeConfigurableWrapper(); + wrapper.setAttributes( attrs ); + if( parentWrapper != null ) + { + parentWrapper.addChild( wrapper ); + } + } + else + { + task.init(); + configure( task, attrs, project ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( task instanceof TaskContainer ) + { + // task can contain other tasks - no other nested elements possible + new TaskHandler( this, ( TaskContainer )task, wrapper, target ).init( name, attrs ); + } + else + { + new NestedElementHandler( this, task, wrapper, target ).init( name, attrs ); + } + } + + protected void finished() + { + if( task != null && target == null ) + { + task.execute(); + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/RuntimeConfigurable.java b/proposal/myrmidon/src/main/org/apache/tools/ant/RuntimeConfigurable.java new file mode 100644 index 000000000..4acd55108 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/RuntimeConfigurable.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Vector; +import org.xml.sax.AttributeList; +import org.xml.sax.helpers.AttributeListImpl; + +/** + * Wrapper class that holds the attributes of a Task (or elements nested below + * that level) and takes care of configuring that element at runtime. + * + * @author Stefan Bodewig + */ +public class RuntimeConfigurable +{ + + private String elementTag = null; + private Vector children = new Vector(); + private Object wrappedObject = null; + private StringBuffer characters = new StringBuffer(); + private AttributeList attributes; + + /** + * @param proxy The element to wrap. + * @param elementTag Description of Parameter + */ + public RuntimeConfigurable( Object proxy, String elementTag ) + { + wrappedObject = proxy; + this.elementTag = elementTag; + } + + /** + * Set's the attributes for the wrapped element. + * + * @param attributes The new Attributes value + */ + public void setAttributes( AttributeList attributes ) + { + this.attributes = new AttributeListImpl( attributes ); + } + + /** + * Returns the AttributeList of the wrapped element. + * + * @return The Attributes value + */ + public AttributeList getAttributes() + { + return attributes; + } + + public String getElementTag() + { + return elementTag; + } + + /** + * Adds child elements to the wrapped element. + * + * @param child The feature to be added to the Child attribute + */ + public void addChild( RuntimeConfigurable child ) + { + children.addElement( child ); + } + + /** + * Add characters from #PCDATA areas to the wrapped element. + * + * @param data The feature to be added to the Text attribute + */ + public void addText( String data ) + { + characters.append( data ); + } + + /** + * Add characters from #PCDATA areas to the wrapped element. + * + * @param buf The feature to be added to the Text attribute + * @param start The feature to be added to the Text attribute + * @param end The feature to be added to the Text attribute + */ + public void addText( char[] buf, int start, int end ) + { + addText( new String( buf, start, end ) ); + } + + + /** + * Configure the wrapped element and all children. + * + * @param p Description of Parameter + * @exception BuildException Description of Exception + */ + public void maybeConfigure( Project p ) + throws BuildException + { + String id = null; + + if( attributes != null ) + { + ProjectHelper.configure( wrappedObject, attributes, p ); + id = attributes.getValue( "id" ); + attributes = null; + } + if( characters.length() != 0 ) + { + ProjectHelper.addText( p, wrappedObject, characters.toString() ); + characters.setLength( 0 ); + } + Enumeration enum = children.elements(); + while( enum.hasMoreElements() ) + { + RuntimeConfigurable child = ( RuntimeConfigurable )enum.nextElement(); + if( child.wrappedObject instanceof Task ) + { + Task childTask = ( Task )child.wrappedObject; + childTask.setRuntimeConfigurableWrapper( child ); + childTask.maybeConfigure(); + } + else + { + child.maybeConfigure( p ); + } + ProjectHelper.storeChild( p, wrappedObject, child.wrappedObject, child.getElementTag().toLowerCase( Locale.US ) ); + } + + if( id != null ) + { + p.addReference( id, wrappedObject ); + } + } + + void setProxy( Object proxy ) + { + wrappedObject = proxy; + } + + /** + * Returns the child with index index. + * + * @param index Description of Parameter + * @return The Child value + */ + RuntimeConfigurable getChild( int index ) + { + return ( RuntimeConfigurable )children.elementAt( index ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Target.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Target.java new file mode 100644 index 000000000..78cb4f980 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Target.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * This class implements a target object with required parameters. + * + * @author James Davidson duncan@x180.com + */ + +public class Target implements TaskContainer +{ + private String ifCondition = ""; + private String unlessCondition = ""; + private Vector dependencies = new Vector( 2 ); + private Vector children = new Vector( 5 ); + private String description = null; + + private String name; + private Project project; + + public void setDepends( String depS ) + { + if( depS.length() > 0 ) + { + StringTokenizer tok = + new StringTokenizer( depS, ",", true ); + while( tok.hasMoreTokens() ) + { + String token = tok.nextToken().trim(); + + //Make sure the dependency is not empty string + if( token.equals( "" ) || token.equals( "," ) ) + { + throw new BuildException( "Syntax Error: Depend attribute " + + "for target \"" + getName() + + "\" has an empty string for dependency." ); + } + + addDependency( token ); + + //Make sure that depends attribute does not + //end in a , + if( tok.hasMoreTokens() ) + { + token = tok.nextToken(); + if( !tok.hasMoreTokens() || !token.equals( "," ) ) + { + throw new BuildException( "Syntax Error: Depend attribute " + + "for target \"" + getName() + + "\" ends with a , character" ); + } + } + } + } + } + + public void setDescription( String description ) + { + this.description = description; + } + + public void setIf( String property ) + { + this.ifCondition = ( property == null ) ? "" : property; + } + + public void setName( String name ) + { + this.name = name; + } + + public void setProject( Project project ) + { + this.project = project; + } + + public void setUnless( String property ) + { + this.unlessCondition = ( property == null ) ? "" : property; + } + + public Enumeration getDependencies() + { + return dependencies.elements(); + } + + public String getDescription() + { + return description; + } + + public String getName() + { + return name; + } + + public Project getProject() + { + return project; + } + + /** + * Get the current set of tasks to be executed by this target. + * + * @return The current set of tasks. + */ + public Task[] getTasks() + { + Vector tasks = new Vector( children.size() ); + Enumeration enum = children.elements(); + while( enum.hasMoreElements() ) + { + Object o = enum.nextElement(); + if( o instanceof Task ) + { + tasks.addElement( o ); + } + } + + Task[] retval = new Task[tasks.size()]; + tasks.copyInto( retval ); + return retval; + } + + public final void performTasks() + { + try + { + project.fireTargetStarted( this ); + execute(); + project.fireTargetFinished( this, null ); + } + catch( RuntimeException exc ) + { + project.fireTargetFinished( this, exc ); + throw exc; + } + } + + public void addDataType( RuntimeConfigurable r ) + { + children.addElement( r ); + } + + public void addDependency( String dependency ) + { + dependencies.addElement( dependency ); + } + + public void addTask( Task task ) + { + children.addElement( task ); + } + + public void execute() + throws BuildException + { + if( testIfCondition() && testUnlessCondition() ) + { + Enumeration enum = children.elements(); + while( enum.hasMoreElements() ) + { + Object o = enum.nextElement(); + if( o instanceof Task ) + { + Task task = ( Task )o; + task.perform(); + } + else + { + RuntimeConfigurable r = ( RuntimeConfigurable )o; + r.maybeConfigure( project ); + } + } + } + else if( !testIfCondition() ) + { + project.log( this, "Skipped because property '" + this.ifCondition + "' not set.", + Project.MSG_VERBOSE ); + } + else + { + project.log( this, "Skipped because property '" + this.unlessCondition + "' set.", + Project.MSG_VERBOSE ); + } + } + + public String toString() + { + return name; + } + + void replaceChild( Task el, Object o ) + { + int index = -1; + while( ( index = children.indexOf( el ) ) >= 0 ) + { + children.setElementAt( o, index ); + } + } + + private boolean testIfCondition() + { + if( "".equals( ifCondition ) ) + { + return true; + } + + String test = project.replaceProperties( ifCondition ); + return project.getProperty( test ) != null; + } + + private boolean testUnlessCondition() + { + if( "".equals( unlessCondition ) ) + { + return true; + } + String test = project.replaceProperties( unlessCondition ); + return project.getProperty( test ) == null; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/Task.java b/proposal/myrmidon/src/main/org/apache/tools/ant/Task.java new file mode 100644 index 000000000..7a08963ea --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/Task.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +import org.apache.myrmidon.api.TaskException; + +public abstract class Task + extends ProjectComponent + implements org.apache.myrmidon.api.Task +{ + protected Target target; + protected String description; + protected Location location = Location.UNKNOWN_LOCATION; + protected String taskName; + protected String taskType; + private boolean invalid; + protected RuntimeConfigurable wrapper; + + private UnknownElement replacement; + + /** + * Sets a description of the current action. It will be usefull in + * commenting what we are doing. + * + * @param desc The new Description value + */ + public void setDescription( String desc ) + { + description = desc; + } + + /** + * Sets the file location where this task was defined. + * + * @param location The new Location value + */ + public void setLocation( Location location ) + { + this.location = location; + } + + /** + * Sets the target object of this task. + * + * @param target Target in whose scope this task belongs. + */ + public void setOwningTarget( Target target ) + { + this.target = target; + } + + /** + * Set the name to use in logging messages. + * + * @param name the name to use in logging messages. + */ + public void setTaskName( String name ) + { + this.taskName = name; + } + + public String getDescription() + { + return description; + } + + /** + * Returns the file location where this task was defined. + * + * @return The Location value + */ + public Location getLocation() + { + return location; + } + + /** + * Get the Target to which this task belongs + * + * @return the task's target. + */ + public Target getOwningTarget() + { + return target; + } + + /** + * Returns the wrapper class for runtime configuration. + * + * @return The RuntimeConfigurableWrapper value + */ + public RuntimeConfigurable getRuntimeConfigurableWrapper() + { + if( wrapper == null ) + { + wrapper = new RuntimeConfigurable( this, getTaskName() ); + } + return wrapper; + } + + /** + * Get the name to use in logging messages. + * + * @return the name to use in logging messages. + */ + public String getTaskName() + { + return taskName; + } + + /** + * Perform this task + */ + public final void perform() + throws TaskException + { + if( !invalid ) + { + try + { + project.fireTaskStarted( this ); + maybeConfigure(); + execute(); + project.fireTaskFinished( this, null ); + } + catch( TaskException te ) + { + if( te instanceof BuildException ) + { + BuildException be = (BuildException)te; + if( be.getLocation() == Location.UNKNOWN_LOCATION ) + { + be.setLocation( getLocation() ); + } + } + project.fireTaskFinished( this, te ); + throw te; + } + catch( RuntimeException re ) + { + project.fireTaskFinished( this, re ); + throw re; + } + } + else + { + UnknownElement ue = getReplacement(); + Task task = ue.getTask(); + task.perform(); + } + } + + /** + * Called by the project to let the task do it's work. This method may be + * called more than once, if the task is invoked more than once. For + * example, if target1 and target2 both depend on target3, then running "ant + * target1 target2" will run all tasks in target3 twice. + * + * @throws BuildException if someting goes wrong with the build + */ + public void execute() + throws TaskException + { + } + + /** + * Called by the project to let the task initialize properly. + * + * @throws BuildException if someting goes wrong with the build + */ + public void init() + throws TaskException + { + } + + /** + * Log a message with the default (INFO) priority. + * + * @param msg Description of Parameter + */ + public void log( String msg ) + { + log( msg, Project.MSG_INFO ); + } + + /** + * Log a mesage with the give priority. + * + * @param msgLevel the message priority at which this message is to be + * logged. + * @param msg Description of Parameter + */ + public void log( String msg, int msgLevel ) + { + project.log( this, msg, msgLevel ); + } + + /** + * Configure this task - if it hasn't been done already. + * + * @exception BuildException Description of Exception + */ + public void maybeConfigure() + throws TaskException + { + if( !invalid ) + { + if( wrapper != null ) + { + wrapper.maybeConfigure( project ); + } + } + else + { + getReplacement(); + } + } + + protected void setRuntimeConfigurableWrapper( RuntimeConfigurable wrapper ) + { + this.wrapper = wrapper; + } + + protected void handleErrorOutput( String line ) + { + log( line, Project.MSG_ERR ); + } + + protected void handleOutput( String line ) + { + log( line, Project.MSG_INFO ); + } + + /** + * Set the name with which the task has been invoked. + * + * @param type the name the task has been invoked as. + */ + void setTaskType( String type ) + { + this.taskType = type; + } + + /** + * Mark this task as invalid. + */ + final void markInvalid() + { + invalid = true; + } + + /** + * Create an UnknownElement that can be used to replace this task. + * + * @return The Replacement value + */ + private UnknownElement getReplacement() + throws TaskException + { + if( replacement == null ) + { + replacement = new UnknownElement( taskType ); + replacement.setProject( project ); + replacement.setTaskType( taskType ); + replacement.setTaskName( taskName ); + replacement.setLocation( location ); + replacement.setOwningTarget( target ); + replacement.setRuntimeConfigurableWrapper( wrapper ); + wrapper.setProxy( replacement ); + target.replaceChild( this, replacement ); + replacement.maybeConfigure(); + } + return replacement; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/TaskAdapter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/TaskAdapter.java new file mode 100644 index 000000000..6f7ebcbf1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/TaskAdapter.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + + +/** + * Use introspection to "adapt" an arbitrary Bean ( not extending Task, but with + * similar patterns). + * + * @author costin@dnt.ro + */ +public class TaskAdapter extends Task +{ + + Object proxy; + + /** + * Checks a class, whether it is suitable to be adapted by TaskAdapter. + * Checks conditions only, which are additionally required for a tasks + * adapted by TaskAdapter. Thus, this method should be called by {@link + * Project#checkTaskClass}. Throws a BuildException and logs as + * Project.MSG_ERR for conditions, that will cause the task execution to + * fail. Logs other suspicious conditions with Project.MSG_WARN. + * + * @param taskClass Description of Parameter + * @param project Description of Parameter + */ + public static void checkTaskClass( final Class taskClass, final Project project ) + { + // don't have to check for interface, since then + // taskClass would be abstract too. + try + { + final Method executeM = taskClass.getMethod( "execute", null ); + // don't have to check for public, since + // getMethod finds public method only. + // don't have to check for abstract, since then + // taskClass would be abstract too. + if( !Void.TYPE.equals( executeM.getReturnType() ) ) + { + final String message = "return type of execute() should be void but was \"" + executeM.getReturnType() + "\" in " + taskClass; + project.log( message, Project.MSG_WARN ); + } + } + catch( NoSuchMethodException e ) + { + final String message = "No public execute() in " + taskClass; + project.log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + } + + /** + * Set the target object class + * + * @param o The new Proxy value + */ + public void setProxy( Object o ) + { + this.proxy = o; + } + + public Object getProxy() + { + return this.proxy; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Method setProjectM = null; + try + { + Class c = proxy.getClass(); + setProjectM = + c.getMethod( "setProject", new Class[]{Project.class} ); + if( setProjectM != null ) + { + setProjectM.invoke( proxy, new Object[]{project} ); + } + } + catch( NoSuchMethodException e ) + { + // ignore this if the class being used as a task does not have + // a set project method. + } + catch( Exception ex ) + { + log( "Error setting project in " + proxy.getClass(), + Project.MSG_ERR ); + throw new BuildException( ex ); + } + + Method executeM = null; + try + { + Class c = proxy.getClass(); + executeM = c.getMethod( "execute", new Class[0] ); + if( executeM == null ) + { + log( "No public execute() in " + proxy.getClass(), Project.MSG_ERR ); + throw new BuildException( "No public execute() in " + proxy.getClass() ); + } + executeM.invoke( proxy, null ); + return; + } + catch( Exception ex ) + { + log( "Error in " + proxy.getClass(), Project.MSG_ERR ); + throw new BuildException( ex ); + } + + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/TaskContainer.java b/proposal/myrmidon/src/main/org/apache/tools/ant/TaskContainer.java new file mode 100644 index 000000000..0802b18c4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/TaskContainer.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Interface for objects which can contain tasks

+ * + * It is recommended that implementations call {@link Task#perform perform} + * instead of {@link Task#execute execute} for the tasks they contain, as this + * method ensures that {@link BuildEvent BuildEvents} will be generated.

+ * + * @author Conor MacNeill + */ +public interface TaskContainer +{ + /** + * Add a task to this task container + * + * @param task the task to be added to this container + */ + void addTask( Task task ); +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/UnknownElement.java b/proposal/myrmidon/src/main/org/apache/tools/ant/UnknownElement.java new file mode 100644 index 000000000..efe3b6046 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/UnknownElement.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.Vector; + +/** + * Wrapper class that holds all information necessary to create a task or data + * type that did not exist when Ant started. + * + * @author Stefan Bodewig + */ +public class UnknownElement extends Task +{ + + /** + * Childelements, holds UnknownElement instances. + */ + private Vector children = new Vector(); + + /** + * Holds the name of the task/type or nested child element of a task/type + * that hasn't been defined at parser time. + */ + private String elementName; + + /** + * The real object after it has been loaded. + */ + private Object realThing; + + public UnknownElement( String elementName ) + { + this.elementName = elementName; + } + + /** + * return the corresponding XML element name. + * + * @return The Tag value + */ + public String getTag() + { + return elementName; + } + + /** + * Return the task instance after it has been created (and if it is a task. + * + * @return The Task value + */ + public Task getTask() + { + if( realThing != null && realThing instanceof Task ) + { + return ( Task )realThing; + } + return null; + } + + /** + * Get the name to use in logging messages. + * + * @return the name to use in logging messages. + */ + public String getTaskName() + { + return realThing == null || !( realThing instanceof Task ) ? + super.getTaskName() : ( ( Task )realThing ).getTaskName(); + } + + /** + * Adds a child element to this element. + * + * @param child The feature to be added to the Child attribute + */ + public void addChild( UnknownElement child ) + { + children.addElement( child ); + } + + /** + * Called when the real task has been configured for the first time. + */ + public void execute() + { + if( realThing == null ) + { + // plain impossible to get here, maybeConfigure should + // have thrown an exception. + throw new BuildException( "Could not create task of type: " + + elementName, location ); + } + + if( realThing instanceof Task ) + { + ( ( Task )realThing ).perform(); + } + } + + /** + * creates the real object instance, creates child elements, configures the + * attributes of the real object. + * + * @exception BuildException Description of Exception + */ + public void maybeConfigure() + throws BuildException + { + realThing = makeObject( this, wrapper ); + + wrapper.setProxy( realThing ); + if( realThing instanceof Task ) + { + ( ( Task )realThing ).setRuntimeConfigurableWrapper( wrapper ); + } + + handleChildren( realThing, wrapper ); + + wrapper.maybeConfigure( project ); + if( realThing instanceof Task ) + { + target.replaceChild( this, realThing ); + } + else + { + target.replaceChild( this, wrapper ); + } + } + + protected BuildException getNotFoundException( String what, + String elementName ) + { + String lSep = System.getProperty( "line.separator" ); + String msg = "Could not create " + what + " of type: " + elementName + + "." + lSep + + "Ant could not find the task or a class this" + lSep + + "task relies upon." + lSep + + "Common solutions are to use taskdef to declare" + lSep + + "your task, or, if this is an optional task," + lSep + + "to put the optional.jar and all required libraries of" + lSep + + "this task in the lib directory of" + lSep + + "your ant installation (ANT_HOME)." + lSep + + "There is also the possibility that your build file " + lSep + + "is written to work with a more recent version of ant " + lSep + + "than the one you are using, in which case you have to " + lSep + + "upgrade."; + return new BuildException( msg, location ); + } + + /** + * Creates child elements, creates children of the children, sets attributes + * of the child elements. + * + * @param parent Description of Parameter + * @param parentWrapper Description of Parameter + * @exception BuildException Description of Exception + */ + protected void handleChildren( Object parent, + RuntimeConfigurable parentWrapper ) + throws BuildException + { + + if( parent instanceof TaskAdapter ) + { + parent = ( ( TaskAdapter )parent ).getProxy(); + } + + Class parentClass = parent.getClass(); + IntrospectionHelper ih = IntrospectionHelper.getHelper( parentClass ); + + for( int i = 0; i < children.size(); i++ ) + { + RuntimeConfigurable childWrapper = parentWrapper.getChild( i ); + UnknownElement child = ( UnknownElement )children.elementAt( i ); + Object realChild = null; + + if( parent instanceof TaskContainer ) + { + realChild = makeTask( child, childWrapper, false ); + ( ( TaskContainer )parent ).addTask( ( Task )realChild ); + } + else + { + realChild = ih.createElement( project, parent, child.getTag() ); + } + + childWrapper.setProxy( realChild ); + if( parent instanceof TaskContainer ) + { + ( ( Task )realChild ).setRuntimeConfigurableWrapper( childWrapper ); + } + + child.handleChildren( realChild, childWrapper ); + + if( parent instanceof TaskContainer ) + { + ( ( Task )realChild ).maybeConfigure(); + } + } + } + + /** + * Creates a named task or data type - if it is a task, configure it up to + * the init() stage. + * + * @param ue Description of Parameter + * @param w Description of Parameter + * @return Description of the Returned Value + */ + protected Object makeObject( UnknownElement ue, RuntimeConfigurable w ) + { + Object o = makeTask( ue, w, true ); + if( o == null ) + { + o = project.createDataType( ue.getTag() ); + } + if( o == null ) + { + throw getNotFoundException( "task or type", ue.getTag() ); + } + return o; + } + + /** + * Create a named task and configure it up to the init() stage. + * + * @param ue Description of Parameter + * @param w Description of Parameter + * @param onTopLevel Description of Parameter + * @return Description of the Returned Value + */ + protected Task makeTask( UnknownElement ue, RuntimeConfigurable w, + boolean onTopLevel ) + { + Task task = project.createTask( ue.getTag() ); + if( task == null && !onTopLevel ) + { + throw getNotFoundException( "task", ue.getTag() ); + } + + if( task != null ) + { + task.setLocation( getLocation() ); + // UnknownElement always has an associated target + task.setOwningTarget( target ); + task.init(); + } + return task; + } + +}// UnknownElement diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/defaultManifest.mf b/proposal/myrmidon/src/main/org/apache/tools/ant/defaultManifest.mf new file mode 100644 index 000000000..1dc733da7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/defaultManifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Created-By: Apache Ant @VERSION@ + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ant.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ant.java new file mode 100644 index 000000000..937f44cd2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ant.java @@ -0,0 +1,550 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.DefaultLogger; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.util.FileUtils; + +/** + * Call Ant in a sub-project
+ *  <target name="foo" depends="init">
+ *    <ant antfile="build.xml" target="bar" >
+ *      <property name="property1" value="aaaaa" />
+ *      <property name="foo" value="baz" />
+ *    </ant> </target> <target name="bar"
+ * depends="init"> <echo message="prop is ${property1}
+ * ${foo}" /> </target> 
+ * + * @author costin@dnt.ro + */ +public class Ant extends Task +{ + + /** + * the basedir where is executed the build file + */ + private File dir = null; + + /** + * the build.xml file (can be absolute) in this case dir will be ignored + */ + private String antFile = null; + + /** + * the target to call if any + */ + private String target = null; + + /** + * the output + */ + private String output = null; + + /** + * should we inherit properties from the parent ? + */ + private boolean inheritAll = true; + + /** + * should we inherit references from the parent ? + */ + private boolean inheritRefs = false; + + /** + * the properties to pass to the new project + */ + private Vector properties = new Vector(); + + /** + * the references to pass to the new project + */ + private Vector references = new Vector(); + + /** + * the temporary project created to run the build file + */ + private Project newProject; + + /** + * set the build file, it can be either absolute or relative. If it is + * absolute, dir will be ignored, if it is relative it will be + * resolved relative to dir . + * + * @param s The new Antfile value + */ + public void setAntfile( String s ) + { + // @note: it is a string and not a file to handle relative/absolute + // otherwise a relative file will be resolved based on the current + // basedir. + this.antFile = s; + } + + /** + * ... + * + * @param d The new Dir value + */ + public void setDir( File d ) + { + this.dir = d; + } + + /** + * If true, inherit all properties from parent Project If false, inherit + * only userProperties and those defined inside the ant call itself + * + * @param value The new InheritAll value + */ + public void setInheritAll( boolean value ) + { + inheritAll = value; + } + + /** + * If true, inherit all references from parent Project If false, inherit + * only those defined inside the ant call itself + * + * @param value The new InheritRefs value + */ + public void setInheritRefs( boolean value ) + { + inheritRefs = value; + } + + public void setOutput( String s ) + { + this.output = s; + } + + /** + * set the target to execute. If none is defined it will execute the default + * target of the build file + * + * @param s The new Target value + */ + public void setTarget( String s ) + { + this.target = s; + } + + /** + * create a reference element that identifies a data type that should be + * carried over to the new project. + * + * @param r The feature to be added to the Reference attribute + */ + public void addReference( Reference r ) + { + references.addElement( r ); + } + + /** + * create a property to pass to the new project as a 'user property' + * + * @return Description of the Returned Value + */ + public Property createProperty() + { + if( newProject == null ) + { + reinit(); + } + Property p = new Property( true ); + p.setProject( newProject ); + p.setTaskName( "property" ); + properties.addElement( p ); + return p; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + try + { + if( newProject == null ) + { + reinit(); + } + + if( ( dir == null ) && ( inheritAll == true ) ) + { + dir = project.getBaseDir(); + } + + initializeProject(); + + if( dir != null ) + { + newProject.setBaseDir( dir ); + newProject.setUserProperty( "basedir", dir.getAbsolutePath() ); + } + else + { + dir = project.getBaseDir(); + } + + overrideProperties(); + + if( antFile == null ) + { + antFile = "build.xml"; + } + + File file = FileUtils.newFileUtils().resolveFile( dir, antFile ); + antFile = file.getAbsolutePath(); + + newProject.setUserProperty( "ant.file", antFile ); + ProjectHelper.configureProject( newProject, new File( antFile ) ); + + if( target == null ) + { + target = newProject.getDefaultTarget(); + } + + addReferences(); + + // Are we trying to call the target in which we are defined? + if( newProject.getBaseDir().equals( project.getBaseDir() ) && + newProject.getProperty( "ant.file" ).equals( project.getProperty( "ant.file" ) ) && + getOwningTarget() != null && + target.equals( this.getOwningTarget().getName() ) ) + { + + throw new BuildException( "ant task calling its own parent target" ); + } + + newProject.executeTarget( target ); + } + finally + { + // help the gc + newProject = null; + } + } + + public void init() + { + newProject = new Project(); + newProject.setJavaVersionProperty(); + newProject.addTaskDefinition( "property", + ( Class )project.getTaskDefinitions().get( "property" ) ); + } + + protected void handleErrorOutput( String line ) + { + if( newProject != null ) + { + newProject.demuxOutput( line, true ); + } + else + { + super.handleErrorOutput( line ); + } + } + + protected void handleOutput( String line ) + { + if( newProject != null ) + { + newProject.demuxOutput( line, false ); + } + else + { + super.handleOutput( line ); + } + } + + /** + * Add the references explicitly defined as nested elements to the new + * project. Also copy over all references that don't override existing + * references in the new project if inheritall has been requested. + * + * @exception BuildException Description of Exception + */ + private void addReferences() + throws BuildException + { + Hashtable thisReferences = ( Hashtable )project.getReferences().clone(); + Hashtable newReferences = newProject.getReferences(); + Enumeration e; + if( references.size() > 0 ) + { + for( e = references.elements(); e.hasMoreElements(); ) + { + Reference ref = ( Reference )e.nextElement(); + String refid = ref.getRefId(); + if( refid == null ) + { + throw new BuildException( "the refid attribute is required for reference elements" ); + } + if( !thisReferences.containsKey( refid ) ) + { + log( "Parent project doesn't contain any reference '" + + refid + "'", + Project.MSG_WARN ); + continue; + } + + Object o = thisReferences.remove( refid ); + String toRefid = ref.getToRefid(); + if( toRefid == null ) + { + toRefid = refid; + } + copyReference( refid, toRefid ); + } + } + + // Now add all references that are not defined in the + // subproject, if inheritRefs is true + if( inheritRefs ) + { + for( e = thisReferences.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + if( newReferences.containsKey( key ) ) + { + continue; + } + copyReference( key, key ); + } + } + } + + /** + * Try to clone and reconfigure the object referenced by oldkey in the + * parent project and add it to the new project with the key newkey.

+ * + * If we cannot clone it, copy the referenced object itself and keep our + * fingers crossed.

+ * + * @param oldKey Description of Parameter + * @param newKey Description of Parameter + */ + private void copyReference( String oldKey, String newKey ) + { + Object orig = project.getReference( oldKey ); + Class c = orig.getClass(); + Object copy = orig; + try + { + Method cloneM = c.getMethod( "clone", new Class[0] ); + if( cloneM != null ) + { + copy = cloneM.invoke( orig, new Object[0] ); + } + } + catch( Exception e ) + { + // not Clonable + } + + if( copy instanceof ProjectComponent ) + { + ( ( ProjectComponent )copy ).setProject( newProject ); + } + else + { + try + { + Method setProjectM = + c.getMethod( "setProject", new Class[]{Project.class} ); + if( setProjectM != null ) + { + setProjectM.invoke( copy, new Object[]{newProject} ); + } + } + catch( NoSuchMethodException e ) + { + // ignore this if the class being referenced does not have + // a set project method. + } + catch( Exception e2 ) + { + String msg = "Error setting new project instance for reference with id " + + oldKey; + throw new BuildException( msg, e2, location ); + } + } + newProject.addReference( newKey, copy ); + } + + private void initializeProject() + { + Vector listeners = project.getBuildListeners(); + for( int i = 0; i < listeners.size(); i++ ) + { + newProject.addBuildListener( ( BuildListener )listeners.elementAt( i ) ); + } + + if( output != null ) + { + try + { + PrintStream out = new PrintStream( new FileOutputStream( output ) ); + DefaultLogger logger = new DefaultLogger(); + logger.setMessageOutputLevel( Project.MSG_INFO ); + logger.setOutputPrintStream( out ); + logger.setErrorPrintStream( out ); + newProject.addBuildListener( logger ); + } + catch( IOException ex ) + { + log( "Ant: Can't set output to " + output ); + } + } + + Hashtable taskdefs = project.getTaskDefinitions(); + Enumeration et = taskdefs.keys(); + while( et.hasMoreElements() ) + { + String taskName = ( String )et.nextElement(); + if( taskName.equals( "property" ) ) + { + // we have already added this taskdef in #init + continue; + } + Class taskClass = ( Class )taskdefs.get( taskName ); + newProject.addTaskDefinition( taskName, taskClass ); + } + + Hashtable typedefs = project.getDataTypeDefinitions(); + Enumeration e = typedefs.keys(); + while( e.hasMoreElements() ) + { + String typeName = ( String )e.nextElement(); + Class typeClass = ( Class )typedefs.get( typeName ); + newProject.addDataTypeDefinition( typeName, typeClass ); + } + + // set user-defined or all properties from calling project + Hashtable prop1; + if( inheritAll == true ) + { + prop1 = project.getProperties(); + } + else + { + prop1 = project.getUserProperties(); + + // set Java built-in properties separately, + // b/c we won't inherit them. + newProject.setSystemProperties(); + } + + e = prop1.keys(); + while( e.hasMoreElements() ) + { + String arg = ( String )e.nextElement(); + if( "basedir".equals( arg ) || "ant.file".equals( arg ) ) + { + // basedir and ant.file get special treatment in execute() + continue; + } + + String value = ( String )prop1.get( arg ); + if( inheritAll == true ) + { + newProject.setProperty( arg, value ); + } + else + { + newProject.setUserProperty( arg, value ); + } + } + } + + /** + * Override the properties in the new project with the one explicitly + * defined as nested elements here. + * + * @exception BuildException Description of Exception + */ + private void overrideProperties() + throws BuildException + { + Enumeration e = properties.elements(); + while( e.hasMoreElements() ) + { + Property p = ( Property )e.nextElement(); + p.setProject( newProject ); + p.execute(); + } + } + + private void reinit() + { + init(); + for( int i = 0; i < properties.size(); i++ ) + { + Property p = ( Property )properties.elementAt( i ); + Property newP = ( Property )newProject.createTask( "property" ); + newP.setName( p.getName() ); + if( p.getValue() != null ) + { + newP.setValue( p.getValue() ); + } + if( p.getFile() != null ) + { + newP.setFile( p.getFile() ); + } + if( p.getResource() != null ) + { + newP.setResource( p.getResource() ); + } + properties.setElementAt( newP, i ); + } + } + + /** + * Helper class that implements the nested <reference> element of + * <ant> and <antcall>. + * + * @author RT + */ + public static class Reference + extends org.apache.tools.ant.types.Reference + { + + private String targetid = null; + + public Reference() + { + super(); + } + + public void setToRefid( String targetid ) + { + this.targetid = targetid; + } + + public String getToRefid() + { + return targetid; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/AntStructure.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/AntStructure.java new file mode 100644 index 000000000..1b1007267 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/AntStructure.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.IntrospectionHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.TaskContainer; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Creates a partial DTD for Ant from the currently known tasks. + * + * @author Stefan Bodewig + * @version $Revision$ + */ + +public class AntStructure extends Task +{ + + private final String lSep = System.getProperty( "line.separator" ); + + private final String BOOLEAN = "%boolean;"; + private final String TASKS = "%tasks;"; + private final String TYPES = "%types;"; + + private Hashtable visited = new Hashtable(); + + private File output; + + /** + * The output file. + * + * @param output The new Output value + */ + public void setOutput( File output ) + { + this.output = output; + } + + public void execute() + throws BuildException + { + + if( output == null ) + { + throw new BuildException( "output attribute is required", location ); + } + + PrintWriter out = null; + try + { + try + { + out = new PrintWriter( new OutputStreamWriter( new FileOutputStream( output ), "UTF8" ) ); + } + catch( UnsupportedEncodingException ue ) + { + /* + * Plain impossible with UTF8, see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * + * fallback to platform specific anyway. + */ + out = new PrintWriter( new FileWriter( output ) ); + } + + printHead( out, project.getTaskDefinitions().keys(), + project.getDataTypeDefinitions().keys() ); + + printTargetDecl( out ); + + Enumeration dataTypes = project.getDataTypeDefinitions().keys(); + while( dataTypes.hasMoreElements() ) + { + String typeName = ( String )dataTypes.nextElement(); + printElementDecl( out, typeName, + ( Class )project.getDataTypeDefinitions().get( typeName ) ); + } + + Enumeration tasks = project.getTaskDefinitions().keys(); + while( tasks.hasMoreElements() ) + { + String taskName = ( String )tasks.nextElement(); + printElementDecl( out, taskName, + ( Class )project.getTaskDefinitions().get( taskName ) ); + } + + printTail( out ); + + } + catch( IOException ioe ) + { + throw new BuildException( "Error writing " + output.getAbsolutePath(), + ioe, location ); + } + finally + { + if( out != null ) + { + out.close(); + } + } + } + + /** + * Does this String match the XML-NMTOKEN production? + * + * @param s Description of Parameter + * @return The Nmtoken value + */ + protected boolean isNmtoken( String s ) + { + for( int i = 0; i < s.length(); i++ ) + { + char c = s.charAt( i ); + // XXX - we are ommitting CombiningChar and Extender here + if( !Character.isLetterOrDigit( c ) && + c != '.' && c != '-' && + c != '_' && c != ':' ) + { + return false; + } + } + return true; + } + + /** + * Do the Strings all match the XML-NMTOKEN production?

+ * + * Otherwise they are not suitable as an enumerated attribute, for example. + *

+ * + * @param s Description of Parameter + * @return Description of the Returned Value + */ + protected boolean areNmtokens( String[] s ) + { + for( int i = 0; i < s.length; i++ ) + { + if( !isNmtoken( s[i] ) ) + { + return false; + } + } + return true; + } + + private void printElementDecl( PrintWriter out, String name, Class element ) + throws BuildException + { + + if( visited.containsKey( name ) ) + { + return; + } + visited.put( name, "" ); + + IntrospectionHelper ih = null; + try + { + ih = IntrospectionHelper.getHelper( element ); + } + catch( Throwable t ) + { + /* + * XXX - failed to load the class properly. + * + * should we print a warning here? + */ + return; + } + + StringBuffer sb = new StringBuffer( "" ).append( lSep ); + sb.append( "" ).append( lSep ); + out.println( sb ); + return; + } + + Vector v = new Vector(); + if( ih.supportsCharacters() ) + { + v.addElement( "#PCDATA" ); + } + + if( TaskContainer.class.isAssignableFrom( element ) ) + { + v.addElement( TASKS ); + } + + Enumeration enum = ih.getNestedElements(); + while( enum.hasMoreElements() ) + { + v.addElement( ( String )enum.nextElement() ); + } + + if( v.isEmpty() ) + { + sb.append( "EMPTY" ); + } + else + { + sb.append( "(" ); + for( int i = 0; i < v.size(); i++ ) + { + if( i != 0 ) + { + sb.append( " | " ); + } + sb.append( v.elementAt( i ) ); + } + sb.append( ")" ); + if( v.size() > 1 || !v.elementAt( 0 ).equals( "#PCDATA" ) ) + { + sb.append( "*" ); + } + } + sb.append( ">" ); + out.println( sb ); + + sb.setLength( 0 ); + sb.append( "" ).append( lSep ); + out.println( sb ); + + for( int i = 0; i < v.size(); i++ ) + { + String nestedName = ( String )v.elementAt( i ); + if( !"#PCDATA".equals( nestedName ) && + !TASKS.equals( nestedName ) && + !TYPES.equals( nestedName ) + ) + { + printElementDecl( out, nestedName, ih.getElementType( nestedName ) ); + } + } + } + + private void printHead( PrintWriter out, Enumeration tasks, + Enumeration types ) + { + out.println( "" ); + out.println( "" ); + out.print( "" ); + out.print( "" ); + + out.println( "" ); + + out.print( "" ); + out.println( "" ); + out.println( "" ); + } + + private void printTail( PrintWriter out ) { } + + private void printTargetDecl( PrintWriter out ) + { + out.print( "" ); + out.println( "" ); + + out.println( "" ); + out.println( "" ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Available.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Available.java new file mode 100644 index 000000000..a4396a6a0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Available.java @@ -0,0 +1,418 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileUtils; + +/** + * Will set the given property if the requested resource is available at + * runtime. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Magesh Umasankar + */ + +public class Available extends Task implements Condition +{ + private String value = "true"; + private String classname; + private Path classpath; + private String file; + private Path filepath; + private AntClassLoader loader; + + private String property; + private String resource; + private FileDir type; + + public void setClassname( String classname ) + { + if( !"".equals( classname ) ) + { + this.classname = classname; + } + } + + public void setClasspath( Path classpath ) + { + createClasspath().append( classpath ); + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setFile( String file ) + { + this.file = file; + } + + public void setFilepath( Path filepath ) + { + createFilepath().append( filepath ); + } + + public void setProperty( String property ) + { + this.property = property; + } + + public void setResource( String resource ) + { + this.resource = resource; + } + + /** + * @param type The new Type value + * @deprecated setType(String) is deprecated and is replaced with + * setType(Available.FileDir) to make Ant's Introspection mechanism do + * the work and also to encapsulate operations on the type in its own + * class. + */ + public void setType( String type ) + { + log( "DEPRECATED - The setType(String) method has been deprecated." + + " Use setType(Available.FileDir) instead." ); + this.type = new FileDir(); + this.type.setValue( type ); + } + + public void setType( FileDir type ) + { + this.type = type; + } + + public void setValue( String value ) + { + this.value = value; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + public Path createFilepath() + { + if( this.filepath == null ) + { + this.filepath = new Path( project ); + } + return this.filepath.createPath(); + } + + public boolean eval() + throws BuildException + { + if( classname == null && file == null && resource == null ) + { + throw new BuildException( "At least one of (classname|file|resource) is required", location ); + } + + if( type != null ) + { + if( file == null ) + { + throw new BuildException( "The type attribute is only valid when specifying the file attribute." ); + } + } + + if( classpath != null ) + { + classpath.setProject( project ); + this.loader = new AntClassLoader( project, classpath ); + } + + if( ( classname != null ) && !checkClass( classname ) ) + { + log( "Unable to load class " + classname + " to set property " + property, Project.MSG_VERBOSE ); + return false; + } + + if( ( file != null ) && !checkFile() ) + { + if( type != null ) + { + log( "Unable to find " + type + " " + file + " to set property " + property, Project.MSG_VERBOSE ); + } + else + { + log( "Unable to find " + file + " to set property " + property, Project.MSG_VERBOSE ); + } + return false; + } + + if( ( resource != null ) && !checkResource( resource ) ) + { + log( "Unable to load resource " + resource + " to set property " + property, Project.MSG_VERBOSE ); + return false; + } + + if( loader != null ) + { + loader.cleanup(); + } + + return true; + } + + public void execute() + throws BuildException + { + if( property == null ) + { + throw new BuildException( "property attribute is required", location ); + } + + if( eval() ) + { + String lSep = System.getProperty( "line.separator" ); + if( null != project.getProperty( property ) ) + { + log( "DEPRECATED - used to overide an existing property. " + + lSep + + " Build writer should not reuse the same property name for " + + lSep + "different values." ); + } + this.project.setProperty( property, value ); + } + } + + private boolean checkClass( String classname ) + { + try + { + if( loader != null ) + { + loader.loadClass( classname ); + } + else + { + ClassLoader l = this.getClass().getClassLoader(); + // Can return null to represent the bootstrap class loader. + // see API docs of Class.getClassLoader. + if( l != null ) + { + l.loadClass( classname ); + } + else + { + Class.forName( classname ); + } + } + return true; + } + catch( ClassNotFoundException e ) + { + return false; + } + catch( NoClassDefFoundError e ) + { + return false; + } + } + + private boolean checkFile() + { + if( filepath == null ) + { + return checkFile( project.resolveFile( file ), file ); + } + else + { + String[] paths = filepath.list(); + for( int i = 0; i < paths.length; ++i ) + { + log( "Searching " + paths[i], Project.MSG_DEBUG ); + /* + * filepath can be a list of directory and/or + * file names (gen'd via ) + * + * look for: + * full-pathname specified == path in list + * full-pathname specified == parent dir of path in list + * simple name specified == path in list + * simple name specified == path in list + name + * simple name specified == parent dir + name + * simple name specified == parent of parent dir + name + * + */ + File path = new File( paths[i] ); + + // ** full-pathname specified == path in list + // ** simple name specified == path in list + if( path.exists() && file.equals( paths[i] ) ) + { + if( type == null ) + { + log( "Found: " + path, Project.MSG_VERBOSE ); + return true; + } + else if( type.isDir() + && path.isDirectory() ) + { + log( "Found directory: " + path, Project.MSG_VERBOSE ); + return true; + } + else if( type.isFile() + && path.isFile() ) + { + log( "Found file: " + path, Project.MSG_VERBOSE ); + return true; + } + // not the requested type + return false; + } + + FileUtils fileUtils = FileUtils.newFileUtils(); + File parent = fileUtils.getParentFile( path ); + // ** full-pathname specified == parent dir of path in list + if( parent != null && parent.exists() + && file.equals( parent.getAbsolutePath() ) ) + { + if( type == null ) + { + log( "Found: " + parent, Project.MSG_VERBOSE ); + return true; + } + else if( type.isDir() ) + { + log( "Found directory: " + parent, Project.MSG_VERBOSE ); + return true; + } + // not the requested type + return false; + } + + // ** simple name specified == path in list + name + if( path.exists() && path.isDirectory() ) + { + if( checkFile( new File( path, file ), + file + " in " + path ) ) + { + return true; + } + } + + // ** simple name specified == parent dir + name + if( parent != null && parent.exists() ) + { + if( checkFile( new File( parent, file ), + file + " in " + parent ) ) + { + return true; + } + } + + // ** simple name specified == parent of parent dir + name + if( parent != null ) + { + File grandParent = fileUtils.getParentFile( parent ); + if( grandParent != null && grandParent.exists() ) + { + if( checkFile( new File( grandParent, file ), + file + " in " + grandParent ) ) + { + return true; + } + } + } + } + } + return false; + } + + private boolean checkFile( File f, String text ) + { + if( type != null ) + { + if( type.isDir() ) + { + if( f.isDirectory() ) + { + log( "Found directory: " + text, Project.MSG_VERBOSE ); + } + return f.isDirectory(); + } + else if( type.isFile() ) + { + if( f.isFile() ) + { + log( "Found file: " + text, Project.MSG_VERBOSE ); + } + return f.isFile(); + } + } + if( f.exists() ) + { + log( "Found: " + text, Project.MSG_VERBOSE ); + } + return f.exists(); + } + + private boolean checkResource( String resource ) + { + if( loader != null ) + { + return ( loader.getResourceAsStream( resource ) != null ); + } + else + { + ClassLoader cL = this.getClass().getClassLoader(); + if( cL != null ) + { + return ( cL.getResourceAsStream( resource ) != null ); + } + else + { + return + ( ClassLoader.getSystemResourceAsStream( resource ) != null ); + } + } + } + + public static class FileDir extends EnumeratedAttribute + { + + private final static String[] values = {"file", "dir"}; + + public String[] getValues() + { + return values; + } + + public boolean isDir() + { + return "dir".equalsIgnoreCase( getValue() ); + } + + public boolean isFile() + { + return "file".equalsIgnoreCase( getValue() ); + } + + public String toString() + { + return getValue(); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BUnzip2.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BUnzip2.java new file mode 100644 index 000000000..882af9655 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BUnzip2.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.bzip2.CBZip2InputStream; + +/** + * Expands a file that has been compressed with the BZIP2 algorithm. Normally + * used to compress non-compressed archives such as TAR files. + * + * @author Magesh Umasankar + */ + +public class BUnzip2 extends Unpack +{ + + private final static String DEFAULT_EXTENSION = ".bz2"; + + protected String getDefaultExtension() + { + return DEFAULT_EXTENSION; + } + + protected void extract() + { + if( source.lastModified() > dest.lastModified() ) + { + log( "Expanding " + source.getAbsolutePath() + " to " + + dest.getAbsolutePath() ); + + FileOutputStream out = null; + CBZip2InputStream zIn = null; + FileInputStream fis = null; + BufferedInputStream bis = null; + try + { + out = new FileOutputStream( dest ); + fis = new FileInputStream( source ); + bis = new BufferedInputStream( fis ); + int b = bis.read(); + if( b != 'B' ) + { + throw new BuildException( "Invalid bz2 file.", location ); + } + b = bis.read(); + if( b != 'Z' ) + { + throw new BuildException( "Invalid bz2 file.", location ); + } + zIn = new CBZip2InputStream( bis ); + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + out.write( buffer, 0, count ); + count = zIn.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } + catch( IOException ioe ) + { + String msg = "Problem expanding bzip2 " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( bis != null ) + { + try + { + bis.close(); + } + catch( IOException ioex ) + {} + } + if( fis != null ) + { + try + { + fis.close(); + } + catch( IOException ioex ) + {} + } + if( out != null ) + { + try + { + out.close(); + } + catch( IOException ioex ) + {} + } + if( zIn != null ) + { + try + { + zIn.close(); + } + catch( IOException ioex ) + {} + } + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BZip2.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BZip2.java new file mode 100644 index 000000000..9c3cdc401 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/BZip2.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Pack; +import org.apache.tools.bzip2.CBZip2OutputStream; + +/** + * Compresses a file with the BZip2 algorithm. Normally used to compress + * non-compressed archives such as TAR files. + * + * @author Magesh Umasankar + */ + +public class BZip2 extends Pack +{ + protected void pack() + { + CBZip2OutputStream zOut = null; + try + { + BufferedOutputStream bos = + new BufferedOutputStream( new FileOutputStream( zipFile ) ); + bos.write( 'B' ); + bos.write( 'Z' ); + zOut = new CBZip2OutputStream( bos ); + zipFile( source, zOut ); + } + catch( IOException ioe ) + { + String msg = "Problem creating bzip2 " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( zOut != null ) + { + try + { + // close up + zOut.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CVSPass.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CVSPass.java new file mode 100644 index 000000000..356579a4a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CVSPass.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * CVSLogin Adds an new entry to a CVS password file + * + * @author Jeff Martin + * @version $Revision$ + */ +public class CVSPass extends Task +{ + /** + * CVS Root + */ + private String cvsRoot = null; + /** + * Password file to add password to + */ + private File passFile = null; + /** + * Password to add to file + */ + private String password = null; + /** + * End of line character + */ + private final String EOL = System.getProperty( "line.separator" ); + + /** + * Array contain char conversion data + */ + private final char shifts[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 114, 120, 53, 79, 96, 109, 72, 108, 70, 64, 76, 67, 116, 74, 68, 87, + 111, 52, 75, 119, 49, 34, 82, 81, 95, 65, 112, 86, 118, 110, 122, 105, + 41, 57, 83, 43, 46, 102, 40, 89, 38, 103, 45, 50, 42, 123, 91, 35, + 125, 55, 54, 66, 124, 126, 59, 47, 92, 71, 115, 78, 88, 107, 106, 56, + 36, 121, 117, 104, 101, 100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48, + 58, 113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85, 223, + 225, 216, 187, 166, 229, 189, 222, 188, 141, 249, 148, 200, 184, 136, 248, 190, + 199, 170, 181, 204, 138, 232, 218, 183, 255, 234, 220, 247, 213, 203, 226, 193, + 174, 172, 228, 252, 217, 201, 131, 230, 197, 211, 145, 238, 161, 179, 160, 212, + 207, 221, 254, 173, 202, 146, 224, 151, 140, 196, 205, 130, 135, 133, 143, 246, + 192, 159, 244, 239, 185, 168, 215, 144, 139, 165, 180, 157, 147, 186, 214, 176, + 227, 231, 219, 169, 175, 156, 206, 198, 129, 164, 150, 210, 154, 177, 134, 127, + 182, 128, 158, 208, 162, 132, 167, 209, 149, 241, 153, 251, 237, 236, 171, 195, + 243, 233, 253, 240, 194, 250, 191, 155, 142, 137, 245, 235, 163, 242, 178, 152}; + + public CVSPass() + { + passFile = new File( System.getProperty( "user.home" ) + "/.cvspass" ); + } + + /** + * Sets cvs root to be added to the password file + * + * @param cvsRoot The new Cvsroot value + */ + public void setCvsroot( String cvsRoot ) + { + this.cvsRoot = cvsRoot; + } + + /** + * Sets the password file attribute. + * + * @param passFile The new Passfile value + */ + public void setPassfile( File passFile ) + { + this.passFile = passFile; + } + + /** + * Sets the password attribute. + * + * @param password The new Password value + */ + public void setPassword( String password ) + { + this.password = password; + } + + /** + * Does the work. + * + * @exception BuildException if someting goes wrong with the build + */ + public final void execute() + throws BuildException + { + if( cvsRoot == null ) + throw new BuildException( "cvsroot is required" ); + if( password == null ) + throw new BuildException( "password is required" ); + + log( "cvsRoot: " + cvsRoot, project.MSG_DEBUG ); + log( "password: " + password, project.MSG_DEBUG ); + log( "passFile: " + passFile, project.MSG_DEBUG ); + + try + { + StringBuffer buf = new StringBuffer(); + + if( passFile.exists() ) + { + BufferedReader reader = + new BufferedReader( new FileReader( passFile ) ); + + String line = null; + + while( ( line = reader.readLine() ) != null ) + { + if( !line.startsWith( cvsRoot ) ) + { + buf.append( line + EOL ); + } + } + + reader.close(); + } + + String pwdfile = buf.toString() + cvsRoot + " A" + mangle( password ); + + log( "Writing -> " + pwdfile, project.MSG_DEBUG ); + + PrintWriter writer = new PrintWriter( new FileWriter( passFile ) ); + + writer.println( pwdfile ); + + writer.close(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + } + + private final String mangle( String password ) + { + StringBuffer buf = new StringBuffer(); + for( int i = 0; i < password.length(); i++ ) + { + buf.append( shifts[password.charAt( i )] ); + } + return buf.toString(); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CallTarget.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CallTarget.java new file mode 100644 index 000000000..80f570e08 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CallTarget.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Call another target in the same project.
+ *    <target name="foo">
+ *      <antcall target="bar">
+ *        <param name="property1" value="aaaaa" />
+ *        <param name="foo" value="baz" />
+ *       </antcall>
+ *    </target>
+ *
+ *    <target name="bar" depends="init">
+ *      <echo message="prop is ${property1} ${foo}" />
+ *    </target>
+ * 

+ * + * This only works as expected if neither property1 nor foo are defined in the + * project itself. + * + * @author Stefan Bodewig + */ +public class CallTarget extends Task +{ + private boolean initialized = false; + private boolean inheritAll = true; + + private Ant callee; + private String subTarget; + + /** + * If true, inherit all properties from parent Project If false, inherit + * only userProperties and those defined inside the antcall call itself + * + * @param inherit The new InheritAll value + */ + public void setInheritAll( boolean inherit ) + { + inheritAll = inherit; + } + + public void setTarget( String target ) + { + subTarget = target; + } + + /** + * create a reference element that identifies a data type that should be + * carried over to the new project. + * + * @param r The feature to be added to the Reference attribute + */ + public void addReference( Ant.Reference r ) + { + callee.addReference( r ); + } + + public Property createParam() + { + return callee.createProperty(); + } + + public void execute() + { + if( !initialized ) + { + init(); + } + + if( subTarget == null ) + { + throw new BuildException( "Attribute target is required.", + location ); + } + + callee.setDir( project.getBaseDir() ); + callee.setAntfile( project.getProperty( "ant.file" ) ); + callee.setTarget( subTarget ); + callee.setInheritAll( inheritAll ); + callee.execute(); + }//-- setInheritAll + + public void init() + { + callee = ( Ant )project.createTask( "ant" ); + callee.setOwningTarget( target ); + callee.setTaskName( getTaskName() ); + callee.setLocation( location ); + callee.init(); + initialized = true; + } + + protected void handleErrorOutput( String line ) + { + if( callee != null ) + { + callee.handleErrorOutput( line ); + } + else + { + super.handleErrorOutput( line ); + } + } + + protected void handleOutput( String line ) + { + if( callee != null ) + { + callee.handleOutput( line ); + } + else + { + super.handleOutput( line ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Checksum.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Checksum.java new file mode 100644 index 000000000..a778428c5 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Checksum.java @@ -0,0 +1,489 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.types.FileSet; + +/** + * This task can be used to create checksums for files. It can also be used to + * verify checksums. + * + * @author Magesh Umasankar + */ +public class Checksum extends MatchingTask implements Condition +{ + /** + * File for which checksum is to be calculated. + */ + private File file = null; + /** + * MessageDigest algorithm to be used. + */ + private String algorithm = "MD5"; + /** + * MessageDigest Algorithm provider + */ + private String provider = null; + /** + * Vector to hold source file sets. + */ + private Vector filesets = new Vector(); + /** + * Stores SourceFile, DestFile pairs and SourceFile, Property String pairs. + */ + private Hashtable includeFileMap = new Hashtable(); + /** + * File Extension that is be to used to create or identify destination file + */ + private String fileext; + /** + * Create new destination file? Defaults to false. + */ + private boolean forceOverwrite; + /** + * is this task being used as a nested condition element? + */ + private boolean isCondition; + /** + * Message Digest instance + */ + private MessageDigest messageDigest; + /** + * Holds generated checksum and gets set as a Project Property. + */ + private String property; + /** + * Contains the result of a checksum verification. ("true" or "false") + */ + private String verifyProperty; + + /** + * Sets the MessageDigest algorithm to be used to calculate the checksum. + * + * @param algorithm The new Algorithm value + */ + public void setAlgorithm( String algorithm ) + { + this.algorithm = algorithm; + } + + /** + * Sets the file for which the checksum is to be calculated. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Sets the File Extension that is be to used to create or identify + * destination file + * + * @param fileext The new Fileext value + */ + public void setFileext( String fileext ) + { + this.fileext = fileext; + } + + /** + * Overwrite existing file irrespective of whether it is newer than the + * source file? Defaults to false. + * + * @param forceOverwrite The new ForceOverwrite value + */ + public void setForceOverwrite( boolean forceOverwrite ) + { + this.forceOverwrite = forceOverwrite; + } + + /** + * Sets the property to hold the generated checksum + * + * @param property The new Property value + */ + public void setProperty( String property ) + { + this.property = property; + } + + /** + * Sets the MessageDigest algorithm provider to be used to calculate the + * checksum. + * + * @param provider The new Provider value + */ + public void setProvider( String provider ) + { + this.provider = provider; + } + + /** + * Sets verify property. This project property holds the result of a + * checksum verification - "true" or "false" + * + * @param verifyProperty The new Verifyproperty value + */ + public void setVerifyproperty( String verifyProperty ) + { + this.verifyProperty = verifyProperty; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Calculate the checksum(s) + * + * @return Returns true if the checksum verification test passed, false + * otherwise. + * @exception BuildException Description of Exception + */ + public boolean eval() + throws BuildException + { + isCondition = true; + return validateAndExecute(); + } + + /** + * Calculate the checksum(s). + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + boolean value = validateAndExecute(); + if( verifyProperty != null ) + { + project.setNewProperty( verifyProperty, + new Boolean( value ).toString() ); + } + } + + /** + * Add key-value pair to the hashtable upon which to later operate upon. + * + * @param file The feature to be added to the ToIncludeFileMap attribute + * @exception BuildException Description of Exception + */ + private void addToIncludeFileMap( File file ) + throws BuildException + { + if( file != null ) + { + if( file.exists() ) + { + if( property == null ) + { + File dest = new File( file.getParent(), file.getName() + fileext ); + if( forceOverwrite || isCondition || + ( file.lastModified() > dest.lastModified() ) ) + { + includeFileMap.put( file, dest ); + } + else + { + log( file + " omitted as " + dest + " is up to date.", + Project.MSG_VERBOSE ); + } + } + else + { + includeFileMap.put( file, property ); + } + } + else + { + String message = "Could not find file " + + file.getAbsolutePath() + + " to generate checksum for."; + log( message ); + throw new BuildException( message, location ); + } + } + } + + /** + * Generate checksum(s) using the message digest created earlier. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private boolean generateChecksums() + throws BuildException + { + boolean checksumMatches = true; + FileInputStream fis = null; + FileOutputStream fos = null; + try + { + for( Enumeration e = includeFileMap.keys(); e.hasMoreElements(); ) + { + messageDigest.reset(); + File src = ( File )e.nextElement(); + if( !isCondition ) + { + log( "Calculating " + algorithm + " checksum for " + src ); + } + fis = new FileInputStream( src ); + DigestInputStream dis = new DigestInputStream( fis, + messageDigest ); + while( dis.read() != -1 ) + ; + dis.close(); + fis.close(); + fis = null; + byte[] fileDigest = messageDigest.digest(); + String checksum = ""; + for( int i = 0; i < fileDigest.length; i++ ) + { + String hexStr = Integer.toHexString( 0x00ff & fileDigest[i] ); + if( hexStr.length() < 2 ) + { + checksum += "0"; + } + checksum += hexStr; + } + //can either be a property name string or a file + Object destination = includeFileMap.get( src ); + if( destination instanceof java.lang.String ) + { + String prop = ( String )destination; + if( isCondition ) + { + checksumMatches = checksum.equals( property ); + } + else + { + project.setProperty( prop, checksum ); + } + } + else if( destination instanceof java.io.File ) + { + if( isCondition ) + { + File existingFile = ( File )destination; + if( existingFile.exists() && + existingFile.length() == checksum.length() ) + { + fis = new FileInputStream( existingFile ); + InputStreamReader isr = new InputStreamReader( fis ); + BufferedReader br = new BufferedReader( isr ); + String suppliedChecksum = br.readLine(); + fis.close(); + fis = null; + br.close(); + isr.close(); + checksumMatches = + checksum.equals( suppliedChecksum ); + } + else + { + checksumMatches = false; + } + } + else + { + File dest = ( File )destination; + fos = new FileOutputStream( dest ); + fos.write( checksum.getBytes() ); + fos.close(); + fos = null; + } + } + } + } + catch( Exception e ) + { + throw new BuildException( e ); + } + finally + { + if( fis != null ) + { + try + { + fis.close(); + } + catch( IOException e ) + {} + } + if( fos != null ) + { + try + { + fos.close(); + } + catch( IOException e ) + {} + } + } + return checksumMatches; + } + + /** + * Validate attributes and get down to business. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private boolean validateAndExecute() + throws BuildException + { + + if( file == null && filesets.size() == 0 ) + { + throw new BuildException( + "Specify at least one source - a file or a fileset." ); + } + + if( file != null && file.exists() && file.isDirectory() ) + { + throw new BuildException( + "Checksum cannot be generated for directories" ); + } + + if( property != null && fileext != null ) + { + throw new BuildException( + "Property and FileExt cannot co-exist." ); + } + + if( property != null ) + { + if( forceOverwrite ) + { + throw new BuildException( + "ForceOverwrite cannot be used when Property is specified" ); + } + + if( file != null ) + { + if( filesets.size() > 0 ) + { + throw new BuildException( + "Multiple files cannot be used when Property is specified" ); + } + } + else + { + if( filesets.size() > 1 ) + { + throw new BuildException( + "Multiple files cannot be used when Property is specified" ); + } + } + } + + if( verifyProperty != null ) + { + isCondition = true; + } + + if( verifyProperty != null && forceOverwrite ) + { + throw new BuildException( + "VerifyProperty and ForceOverwrite cannot co-exist." ); + } + + if( isCondition && forceOverwrite ) + { + throw new BuildException( + "ForceOverwrite cannot be used when conditions are being used." ); + } + + if( fileext == null ) + { + fileext = "." + algorithm; + } + else if( fileext.trim().length() == 0 ) + { + throw new BuildException( + "File extension when specified must not be an empty string" ); + } + + messageDigest = null; + if( provider != null ) + { + try + { + messageDigest = MessageDigest.getInstance( algorithm, provider ); + } + catch( NoSuchAlgorithmException noalgo ) + { + throw new BuildException( noalgo ); + } + catch( NoSuchProviderException noprovider ) + { + throw new BuildException( noprovider ); + } + } + else + { + try + { + messageDigest = MessageDigest.getInstance( algorithm ); + } + catch( NoSuchAlgorithmException noalgo ) + { + throw new BuildException( noalgo ); + } + } + + if( messageDigest == null ) + { + throw new BuildException( "Unable to create Message Digest", + location ); + } + + addToIncludeFileMap( file ); + + int sizeofFileSet = filesets.size(); + for( int i = 0; i < sizeofFileSet; i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] srcFiles = ds.getIncludedFiles(); + for( int j = 0; j < srcFiles.length; j++ ) + { + File src = new File( fs.getDir( project ), srcFiles[j] ); + addToIncludeFileMap( src ); + } + } + + return generateChecksums(); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Chmod.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Chmod.java new file mode 100644 index 000000000..96f736e03 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Chmod.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; + + +/** + * Chmod equivalent for unix-like environments. + * + * @author costin@eng.sun.com + * @author Mariusz Nowostawski (Marni) + * mnowostawski@infoscience.otago.ac.nz + * @author Stefan Bodewig + */ + +public class Chmod extends ExecuteOn +{ + + private FileSet defaultSet = new FileSet(); + private boolean defaultSetDefined = false; + private boolean havePerm = false; + + public Chmod() + { + super.setExecutable( "chmod" ); + super.setParallel( true ); + super.setSkipEmptyFilesets( true ); + } + + public void setCommand( String e ) + { + throw new BuildException( taskType + " doesn\'t support the command attribute", location ); + } + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + defaultSetDefined = true; + defaultSet.setDefaultexcludes( useDefaultExcludes ); + } + + public void setDir( File src ) + { + defaultSet.setDir( src ); + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + defaultSetDefined = true; + defaultSet.setExcludes( excludes ); + } + + + public void setExecutable( String e ) + { + throw new BuildException( taskType + " doesn\'t support the executable attribute", location ); + } + + public void setFile( File src ) + { + FileSet fs = new FileSet(); + fs.setDir( new File( src.getParent() ) ); + fs.createInclude().setName( src.getName() ); + addFileset( fs ); + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space. + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + defaultSetDefined = true; + defaultSet.setIncludes( includes ); + } + + public void setPerm( String perm ) + { + createArg().setValue( perm ); + havePerm = true; + } + + public void setSkipEmptyFilesets( boolean skip ) + { + throw new BuildException( taskType + " doesn\'t support the skipemptyfileset attribute", location ); + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + defaultSetDefined = true; + return defaultSet.createExclude(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + defaultSetDefined = true; + return defaultSet.createInclude(); + } + + /** + * add a set of patterns + * + * @return Description of the Returned Value + */ + public PatternSet createPatternSet() + { + defaultSetDefined = true; + return defaultSet.createPatternSet(); + } + + public void execute() + throws BuildException + { + if( defaultSetDefined || defaultSet.getDir( project ) == null ) + { + super.execute(); + } + else if( isValidOs() ) + { + // we are chmodding the given directory + createArg().setValue( defaultSet.getDir( project ).getPath() ); + Execute execute = prepareExec(); + try + { + execute.setCommandline( cmdl.getCommandline() ); + runExecute( execute ); + } + catch( IOException e ) + { + throw new BuildException( "Execute failed: " + e, e, location ); + } + finally + { + // close the output file if required + logFlush(); + } + } + } + + protected boolean isValidOs() + { + return Os.isFamily( "unix" ) && super.isValidOs(); + } + + protected void checkConfiguration() + { + if( !havePerm ) + { + throw new BuildException( "Required attribute perm not set in chmod", + location ); + } + + if( defaultSetDefined && defaultSet.getDir( project ) != null ) + { + addFileset( defaultSet ); + } + super.checkConfiguration(); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CompileTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CompileTask.java new file mode 100644 index 000000000..787a43f73 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/CompileTask.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.types.PatternSet; + +/** + * This task will compile and load a new taskdef all in one step. At times, this + * is useful for eliminating ordering dependencies which otherwise would require + * multiple executions of Ant. + * + * @author Sam Ruby rubys@us.ibm.com + * @deprecated use <taskdef> elements nested into <target>s instead + */ + +public class CompileTask extends Javac +{ + + protected Vector taskList = new Vector(); + + /** + * add a new task entry on the task list + * + * @return Description of the Returned Value + */ + public Taskdef createTaskdef() + { + Taskdef task = new Taskdef(); + taskList.addElement( task ); + return task; + } + + /** + * have execute do nothing + */ + public void execute() { } + + /** + * do all the real work in init + */ + public void init() + { + log( "!! CompileTask is deprecated. !!" ); + log( "Use elements nested into s instead" ); + + // create all the include entries from the task defs + for( Enumeration e = taskList.elements(); e.hasMoreElements(); ) + { + Taskdef task = ( Taskdef )e.nextElement(); + String source = task.getClassname().replace( '.', '/' ) + ".java"; + PatternSet.NameEntry include = super.createInclude(); + include.setName( "**/" + source ); + } + + // execute Javac + super.init(); + super.execute(); + + // now define all the new tasks + for( Enumeration e = taskList.elements(); e.hasMoreElements(); ) + { + Taskdef task = ( Taskdef )e.nextElement(); + task.init(); + } + + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ConditionTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ConditionTask.java new file mode 100644 index 000000000..da72ebc93 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ConditionTask.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.taskdefs.condition.ConditionBase; + +/** + * <condition> task as a generalization of <available> and + * <uptodate>

+ * + * This task supports boolean logic as well as pluggable conditions to decide, + * whether a property should be set.

+ * + * This task does not extend Task to take advantage of ConditionBase.

+ * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ConditionTask extends ConditionBase +{ + private String value = "true"; + + private String property; + + /** + * The name of the property to set. Required. + * + * @param p The new Property value + * @since 1.1 + */ + public void setProperty( String p ) + { + property = p; + } + + /** + * The value for the property to set. Defaults to "true". + * + * @param v The new Value value + * @since 1.1 + */ + public void setValue( String v ) + { + value = v; + } + + /** + * See whether our nested condition holds and set the property. + * + * @exception BuildException Description of Exception + * @since 1.1 + */ + public void execute() + throws BuildException + { + if( countConditions() > 1 ) + { + throw new BuildException( "You must not nest more than one condition into " ); + } + if( countConditions() < 1 ) + { + throw new BuildException( "You must nest a condition into " ); + } + Condition c = ( Condition )getConditions().nextElement(); + if( c.eval() ) + { + getProject().setNewProperty( property, value ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copy.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copy.java new file mode 100644 index 000000000..055c978fd --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copy.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.FlatFileNameMapper; +import org.apache.tools.ant.util.IdentityMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * A consolidated copy task. Copies a file or directory to a new file or + * directory. Files are only copied if the source file is newer than the + * destination file, or when the destination file does not exist. It is possible + * to explicitly overwrite existing files.

+ * + * This implementation is based on Arnout Kuiper's initial design document, the + * following mailing list discussions, and the copyfile/copydir tasks.

+ * + * @author Glenn McAllister glennm@ca.ibm.com + * + * @author Stefan Bodewig + * @author Michael McCallum + * @author Magesh Umasankar + */ +public class Copy extends Task +{ + protected File file = null;// the source file + protected File destFile = null;// the destination file + protected File destDir = null;// the destination directory + protected Vector filesets = new Vector(); + + protected boolean filtering = false; + protected boolean preserveLastModified = false; + protected boolean forceOverwrite = false; + protected boolean flatten = false; + protected int verbosity = Project.MSG_VERBOSE; + protected boolean includeEmpty = true; + + protected Hashtable fileCopyMap = new Hashtable(); + protected Hashtable dirCopyMap = new Hashtable(); + protected Hashtable completeDirMap = new Hashtable(); + + protected Mapper mapperElement = null; + private Vector filterSets = new Vector(); + private FileUtils fileUtils; + + public Copy() + { + fileUtils = FileUtils.newFileUtils(); + } + + /** + * Sets a single source file to copy. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Sets filtering. + * + * @param filtering The new Filtering value + */ + public void setFiltering( boolean filtering ) + { + this.filtering = filtering; + } + + /** + * When copying directory trees, the files can be "flattened" into a single + * directory. If there are multiple files with the same name in the source + * directory tree, only the first file will be copied into the "flattened" + * directory, unless the forceoverwrite attribute is true. + * + * @param flatten The new Flatten value + */ + public void setFlatten( boolean flatten ) + { + this.flatten = flatten; + } + + /** + * Used to copy empty directories. + * + * @param includeEmpty The new IncludeEmptyDirs value + */ + public void setIncludeEmptyDirs( boolean includeEmpty ) + { + this.includeEmpty = includeEmpty; + } + + /** + * Overwrite any existing destination file(s). + * + * @param overwrite The new Overwrite value + */ + public void setOverwrite( boolean overwrite ) + { + this.forceOverwrite = overwrite; + } + + /** + * Give the copied files the same last modified time as the original files. + * + * @param preserve The new PreserveLastModified value + * @deprecated setPreserveLastModified(String) has been deprecated and + * replaced with setPreserveLastModified(boolean) to consistently let + * the Introspection mechanism work. + */ + public void setPreserveLastModified( String preserve ) + { + setPreserveLastModified( Project.toBoolean( preserve ) ); + } + + /** + * Give the copied files the same last modified time as the original files. + * + * @param preserve The new PreserveLastModified value + */ + public void setPreserveLastModified( boolean preserve ) + { + preserveLastModified = preserve; + } + + /** + * Sets the destination directory. + * + * @param destDir The new Todir value + */ + public void setTodir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Sets the destination file. + * + * @param destFile The new Tofile value + */ + public void setTofile( File destFile ) + { + this.destFile = destFile; + } + + /** + * Used to force listing of all names of copied files. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + if( verbose ) + { + this.verbosity = Project.MSG_INFO; + } + else + { + this.verbosity = Project.MSG_VERBOSE; + } + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Create a nested filterset + * + * @return Description of the Returned Value + */ + public FilterSet createFilterSet() + { + FilterSet filterSet = new FilterSet(); + filterSets.addElement( filterSet ); + return filterSet; + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapperElement != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapperElement = new Mapper( project ); + return mapperElement; + } + + /** + * Performs the copy operation. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // make sure we don't have an illegal set of options + validateAttributes(); + + // deal with the single file + if( file != null ) + { + if( file.exists() ) + { + if( destFile == null ) + { + destFile = new File( destDir, file.getName() ); + } + + if( forceOverwrite || + ( file.lastModified() > destFile.lastModified() ) ) + { + fileCopyMap.put( file.getAbsolutePath(), destFile.getAbsolutePath() ); + } + else + { + log( file + " omitted as " + destFile + " is up to date.", + Project.MSG_VERBOSE ); + } + } + else + { + String message = "Could not find file " + + file.getAbsolutePath() + " to copy."; + log( message ); + throw new BuildException( message ); + } + } + + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File fromDir = fs.getDir( project ); + + String[] srcFiles = ds.getIncludedFiles(); + String[] srcDirs = ds.getIncludedDirectories(); + boolean isEverythingIncluded = ds.isEverythingIncluded(); + if( isEverythingIncluded + && !flatten && mapperElement == null ) + { + completeDirMap.put( fromDir, destDir ); + } + scan( fromDir, destDir, srcFiles, srcDirs ); + } + + // do all the copy operations now... + doFileOperations(); + + // clean up destDir again - so this instance can be used a second + // time without throwing an exception + if( destFile != null ) + { + destDir = null; + } + } + + protected FileUtils getFileUtils() + { + return fileUtils; + } + + /** + * Get the filtersets being applied to this operation. + * + * @return a vector of FilterSet objects + */ + protected Vector getFilterSets() + { + return filterSets; + } + + protected void buildMap( File fromDir, File toDir, String[] names, + FileNameMapper mapper, Hashtable map ) + { + + String[] toCopy = null; + if( forceOverwrite ) + { + Vector v = new Vector(); + for( int i = 0; i < names.length; i++ ) + { + if( mapper.mapFileName( names[i] ) != null ) + { + v.addElement( names[i] ); + } + } + toCopy = new String[v.size()]; + v.copyInto( toCopy ); + } + else + { + SourceFileScanner ds = new SourceFileScanner( this ); + toCopy = ds.restrict( names, fromDir, toDir, mapper ); + } + + for( int i = 0; i < toCopy.length; i++ ) + { + File src = new File( fromDir, toCopy[i] ); + File dest = new File( toDir, mapper.mapFileName( toCopy[i] )[0] ); + map.put( src.getAbsolutePath(), dest.getAbsolutePath() ); + } + } + + /** + * Actually does the file (and possibly empty directory) copies. This is a + * good method for subclasses to override. + */ + protected void doFileOperations() + { + if( fileCopyMap.size() > 0 ) + { + log( "Copying " + fileCopyMap.size() + + " file" + ( fileCopyMap.size() == 1 ? "" : "s" ) + + " to " + destDir.getAbsolutePath() ); + + Enumeration e = fileCopyMap.keys(); + while( e.hasMoreElements() ) + { + String fromFile = ( String )e.nextElement(); + String toFile = ( String )fileCopyMap.get( fromFile ); + + if( fromFile.equals( toFile ) ) + { + log( "Skipping self-copy of " + fromFile, verbosity ); + continue; + } + + try + { + log( "Copying " + fromFile + " to " + toFile, verbosity ); + + FilterSetCollection executionFilters = new FilterSetCollection(); + if( filtering ) + { + executionFilters.addFilterSet( project.getGlobalFilterSet() ); + } + for( Enumeration filterEnum = filterSets.elements(); filterEnum.hasMoreElements(); ) + { + executionFilters.addFilterSet( ( FilterSet )filterEnum.nextElement() ); + } + fileUtils.copyFile( fromFile, toFile, executionFilters, + forceOverwrite, preserveLastModified ); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + + if( includeEmpty ) + { + Enumeration e = dirCopyMap.elements(); + int count = 0; + while( e.hasMoreElements() ) + { + File d = new File( ( String )e.nextElement() ); + if( !d.exists() ) + { + if( !d.mkdirs() ) + { + log( "Unable to create directory " + d.getAbsolutePath(), Project.MSG_ERR ); + } + else + { + count++; + } + } + } + + if( count > 0 ) + { + log( "Copied " + count + + " empty director" + + ( count == 1 ? "y" : "ies" ) + + " to " + destDir.getAbsolutePath() ); + } + } + } + + /** + * Compares source files to destination files to see if they should be + * copied. + * + * @param fromDir Description of Parameter + * @param toDir Description of Parameter + * @param files Description of Parameter + * @param dirs Description of Parameter + */ + protected void scan( File fromDir, File toDir, String[] files, String[] dirs ) + { + FileNameMapper mapper = null; + if( mapperElement != null ) + { + mapper = mapperElement.getImplementation(); + } + else if( flatten ) + { + mapper = new FlatFileNameMapper(); + } + else + { + mapper = new IdentityMapper(); + } + + buildMap( fromDir, toDir, files, mapper, fileCopyMap ); + + if( includeEmpty ) + { + buildMap( fromDir, toDir, dirs, mapper, dirCopyMap ); + } + } + +//************************************************************************ +// protected and private methods +//************************************************************************ + + /** + * Ensure we have a consistent and legal set of attributes, and set any + * internal flags necessary based on different combinations of attributes. + * + * @exception BuildException Description of Exception + */ + protected void validateAttributes() + throws BuildException + { + if( file == null && filesets.size() == 0 ) + { + throw new BuildException( "Specify at least one source - a file or a fileset." ); + } + + if( destFile != null && destDir != null ) + { + throw new BuildException( "Only one of tofile and todir may be set." ); + } + + if( destFile == null && destDir == null ) + { + throw new BuildException( "One of tofile or todir must be set." ); + } + + if( file != null && file.exists() && file.isDirectory() ) + { + throw new BuildException( "Use a fileset to copy directories." ); + } + + if( destFile != null && filesets.size() > 0 ) + { + if( filesets.size() > 1 ) + { + throw new BuildException( + "Cannot concatenate multiple files into a single file." ); + } + else + { + FileSet fs = ( FileSet )filesets.elementAt( 0 ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] srcFiles = ds.getIncludedFiles(); + + if( srcFiles.length > 0 ) + { + if( file == null ) + { + file = new File( srcFiles[0] ); + filesets.removeElementAt( 0 ); + } + else + { + throw new BuildException( + "Cannot concatenate multiple files into a single file." ); + } + } + else + { + throw new BuildException( + "Cannot perform operation from directory to file." ); + } + } + } + + if( destFile != null ) + { + destDir = new File( destFile.getParent() );// be 1.1 friendly + } + + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copydir.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copydir.java new file mode 100644 index 000000000..f539fffe2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copydir.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; + +/** + * Copies a directory. + * + * @author James Davidson duncan@x180.com + * @deprecated The copydir task is deprecated. Use copy instead. + */ + +public class Copydir extends MatchingTask +{ + private boolean filtering = false; + private boolean flatten = false; + private boolean forceOverwrite = false; + private Hashtable filecopyList = new Hashtable(); + private File destDir; + + private File srcDir; + + public void setDest( File dest ) + { + destDir = dest; + } + + public void setFiltering( boolean filter ) + { + filtering = filter; + } + + public void setFlatten( boolean flatten ) + { + this.flatten = flatten; + } + + public void setForceoverwrite( boolean force ) + { + forceOverwrite = force; + } + + public void setSrc( File src ) + { + srcDir = src; + } + + public void execute() + throws BuildException + { + log( "DEPRECATED - The copydir task is deprecated. Use copy instead." ); + + if( srcDir == null ) + { + throw new BuildException( "src attribute must be set!", + location ); + } + + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir " + srcDir.toString() + + " does not exist!", location ); + } + + if( destDir == null ) + { + throw new BuildException( "The dest attribute must be set.", location ); + } + + if( srcDir.equals( destDir ) ) + { + log( "Warning: src == dest" ); + } + + DirectoryScanner ds = super.getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + scanDir( srcDir, destDir, files ); + if( filecopyList.size() > 0 ) + { + log( "Copying " + filecopyList.size() + " file" + + ( filecopyList.size() == 1 ? "" : "s" ) + + " to " + destDir.getAbsolutePath() ); + Enumeration enum = filecopyList.keys(); + while( enum.hasMoreElements() ) + { + String fromFile = ( String )enum.nextElement(); + String toFile = ( String )filecopyList.get( fromFile ); + try + { + project.copyFile( fromFile, toFile, filtering, + forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + } + + private void scanDir( File from, File to, String[] files ) + { + for( int i = 0; i < files.length; i++ ) + { + String filename = files[i]; + File srcFile = new File( from, filename ); + File destFile; + if( flatten ) + { + destFile = new File( to, new File( filename ).getName() ); + } + else + { + destFile = new File( to, filename ); + } + if( forceOverwrite || + ( srcFile.lastModified() > destFile.lastModified() ) ) + { + filecopyList.put( srcFile.getAbsolutePath(), + destFile.getAbsolutePath() ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copyfile.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copyfile.java new file mode 100644 index 000000000..8d3398975 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Copyfile.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Copies a file. + * + * @author duncan@x180.com + * @deprecated The copyfile task is deprecated. Use copy instead. + */ + +public class Copyfile extends Task +{ + private boolean filtering = false; + private boolean forceOverwrite = false; + private File destFile; + + private File srcFile; + + public void setDest( File dest ) + { + destFile = dest; + } + + public void setFiltering( String filter ) + { + filtering = Project.toBoolean( filter ); + } + + public void setForceoverwrite( boolean force ) + { + forceOverwrite = force; + } + + public void setSrc( File src ) + { + srcFile = src; + } + + public void execute() + throws BuildException + { + log( "DEPRECATED - The copyfile task is deprecated. Use copy instead." ); + + if( srcFile == null ) + { + throw new BuildException( "The src attribute must be present.", location ); + } + + if( !srcFile.exists() ) + { + throw new BuildException( "src " + srcFile.toString() + + " does not exist.", location ); + } + + if( destFile == null ) + { + throw new BuildException( "The dest attribute must be present.", location ); + } + + if( srcFile.equals( destFile ) ) + { + log( "Warning: src == dest" ); + } + + if( forceOverwrite || srcFile.lastModified() > destFile.lastModified() ) + { + try + { + project.copyFile( srcFile, destFile, filtering, forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Error copying file: " + srcFile.getAbsolutePath() + + " due to " + ioe.getMessage(); + throw new BuildException( msg ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Cvs.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Cvs.java new file mode 100644 index 000000000..63bda456c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Cvs.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Environment; + +/** + * @author costin@dnt.ro + * @author stefano@apache.org + * @author Wolfgang Werner + * wwerner@picturesafe.de + */ + +public class Cvs extends Task +{ + + private Commandline cmd = new Commandline(); + + /** + * the CVS command to execute. + */ + private String command = "checkout"; + + /** + * suppress information messages. + */ + private boolean quiet = false; + + /** + * report only, don't change any files. + */ + private boolean noexec = false; + + /** + * CVS port + */ + private int port = 0; + + /** + * CVS password file + */ + private File passFile = null; + + /** + * If true it will stop the build if cvs exits with error. Default is false. + * (Iulian) + */ + private boolean failOnError = false; + + /** + * the CVSROOT variable. + */ + private String cvsRoot; + + /** + * the CVS_RSH variable. + */ + private String cvsRsh; + + /** + * the directory where the checked out files should be placed. + */ + private File dest; + + /** + * the file to direct standard error from the command. + */ + private File error; + + /** + * the file to direct standard output from the command. + */ + private File output; + + /** + * the package/module to check out. + */ + private String pack; + + public void setCommand( String c ) + { + this.command = c; + } + + public void setCvsRoot( String root ) + { + // Check if not real cvsroot => set it to null + if( root != null ) + { + if( root.trim().equals( "" ) ) + root = null; + } + + this.cvsRoot = root; + } + + public void setCvsRsh( String rsh ) + { + // Check if not real cvsrsh => set it to null + if( rsh != null ) + { + if( rsh.trim().equals( "" ) ) + rsh = null; + } + + this.cvsRsh = rsh; + } + + + public void setDate( String p ) + { + if( p != null && p.trim().length() > 0 ) + { + cmd.createArgument().setValue( "-D" ); + cmd.createArgument().setValue( p ); + } + } + + public void setDest( File dest ) + { + this.dest = dest; + } + + public void setError( File error ) + { + this.error = error; + } + + public void setFailOnError( boolean failOnError ) + { + this.failOnError = failOnError; + } + + public void setNoexec( boolean ne ) + { + noexec = ne; + } + + public void setOutput( File output ) + { + this.output = output; + } + + public void setPackage( String p ) + { + this.pack = p; + } + + public void setPassfile( File passFile ) + { + this.passFile = passFile; + } + + public void setPort( int port ) + { + this.port = port; + } + + public void setQuiet( boolean q ) + { + quiet = q; + } + + public void setTag( String p ) + { + // Check if not real tag => set it to null + if( p != null && p.trim().length() > 0 ) + { + cmd.createArgument().setValue( "-r" ); + cmd.createArgument().setValue( p ); + } + } + + + public void execute() + throws BuildException + { + + // XXX: we should use JCVS (www.ice.com/JCVS) instead of command line + // execution so that we don't rely on having native CVS stuff around (SM) + + // We can't do it ourselves as jCVS is GPLed, a third party task + // outside of jakarta repositories would be possible though (SB). + + Commandline toExecute = new Commandline(); + + toExecute.setExecutable( "cvs" ); + if( cvsRoot != null ) + { + toExecute.createArgument().setValue( "-d" ); + toExecute.createArgument().setValue( cvsRoot ); + } + if( noexec ) + { + toExecute.createArgument().setValue( "-n" ); + } + if( quiet ) + { + toExecute.createArgument().setValue( "-q" ); + } + toExecute.createArgument().setLine( command ); + toExecute.addArguments( cmd.getCommandline() ); + + if( pack != null ) + { + toExecute.createArgument().setLine( pack ); + } + + Environment env = new Environment(); + + if( port > 0 ) + { + Environment.Variable var = new Environment.Variable(); + var.setKey( "CVS_CLIENT_PORT" ); + var.setValue( String.valueOf( port ) ); + env.addVariable( var ); + } + + if( passFile != null ) + { + Environment.Variable var = new Environment.Variable(); + var.setKey( "CVS_PASSFILE" ); + var.setValue( String.valueOf( passFile ) ); + env.addVariable( var ); + } + + if( cvsRsh != null ) + { + Environment.Variable var = new Environment.Variable(); + var.setKey( "CVS_RSH" ); + var.setValue( String.valueOf( cvsRsh ) ); + env.addVariable( var ); + } + + ExecuteStreamHandler streamhandler = null; + OutputStream outputstream = null; + OutputStream errorstream = null; + if( error == null && output == null ) + { + streamhandler = new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ); + } + else + { + if( output != null ) + { + try + { + outputstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( output ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + outputstream = new LogOutputStream( this, Project.MSG_INFO ); + } + if( error != null ) + { + try + { + errorstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( error ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + errorstream = new LogOutputStream( this, Project.MSG_WARN ); + } + streamhandler = new PumpStreamHandler( outputstream, errorstream ); + } + + Execute exe = new Execute( streamhandler, + null ); + + exe.setAntRun( project ); + if( dest == null ) + dest = project.getBaseDir(); + exe.setWorkingDirectory( dest ); + + exe.setCommandline( toExecute.getCommandline() ); + exe.setEnvironment( env.getVariables() ); + try + { + int retCode = exe.execute(); + /* + * Throw an exception if cvs exited with error. (Iulian) + */ + if( failOnError && retCode != 0 ) + throw new BuildException( "cvs exited with error code " + retCode ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + if( output != null ) + { + try + { + outputstream.close(); + } + catch( IOException e ) + {} + } + if( error != null ) + { + try + { + errorstream.close(); + } + catch( IOException e ) + {} + } + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Definer.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Definer.java new file mode 100644 index 000000000..0186ee465 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Definer.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Properties; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Base class for Taskdef and Typedef - does all the classpath handling and and + * class loading. + * + * @author Costin Manolache + * @author Stefan Bodewig + */ +public abstract class Definer extends Task +{ + private boolean reverseLoader = false; + private Path classpath; + private File file; + private String name; + private String resource; + private String value; + + public void setClassname( String v ) + { + value = v; + } + + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setFile( File file ) + { + this.file = file; + } + + public void setName( String name ) + { + this.name = name; + } + + public void setResource( String res ) + { + this.resource = res; + } + + public void setReverseLoader( boolean reverseLoader ) + { + this.reverseLoader = reverseLoader; + log( "The reverseloader attribute is DEPRECATED. It will be removed", Project.MSG_WARN ); + } + + public String getClassname() + { + return value; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + public void execute() + throws BuildException + { + AntClassLoader al = createLoader(); + + if( file == null && resource == null ) + { + + // simple case - one definition + if( name == null || value == null ) + { + String msg = "name or classname attributes of " + + getTaskName() + " element " + + "are undefined"; + throw new BuildException( msg ); + } + addDefinition( al, name, value ); + + } + else + { + + try + { + if( name != null || value != null ) + { + String msg = "You must not specify name or value " + + "together with file or resource."; + throw new BuildException( msg, location ); + } + + if( file != null && resource != null ) + { + String msg = "You must not specify both, file and resource."; + throw new BuildException( msg, location ); + } + + Properties props = new Properties(); + InputStream is = null; + if( file != null ) + { + log( "Loading definitions from file " + file, + Project.MSG_VERBOSE ); + is = new FileInputStream( file ); + if( is == null ) + { + log( "Could not load definitions from file " + file + + ". It doesn\'t exist.", Project.MSG_WARN ); + } + } + if( resource != null ) + { + log( "Loading definitions from resource " + resource, + Project.MSG_VERBOSE ); + is = al.getResourceAsStream( resource ); + if( is == null ) + { + log( "Could not load definitions from resource " + + resource + ". It could not be found.", + Project.MSG_WARN ); + } + } + + if( is != null ) + { + props.load( is ); + Enumeration keys = props.keys(); + while( keys.hasMoreElements() ) + { + String n = ( String )keys.nextElement(); + String v = props.getProperty( n ); + addDefinition( al, n, v ); + } + } + } + catch( IOException ex ) + { + throw new BuildException( ex); + } + } + } + + protected abstract void addDefinition( String name, Class c ); + + private void addDefinition( ClassLoader al, String name, String value ) + throws BuildException + { + try + { + Class c = al.loadClass( value ); + AntClassLoader.initializeClass( c ); + addDefinition( name, c ); + } + catch( ClassNotFoundException cnfe ) + { + String msg = getTaskName() + " class " + value + + " cannot be found"; + throw new BuildException( msg, cnfe, location ); + } + catch( NoClassDefFoundError ncdfe ) + { + String msg = getTaskName() + " class " + value + + " cannot be found"; + throw new BuildException( msg, ncdfe, location ); + } + } + + + private AntClassLoader createLoader() + { + AntClassLoader al = null; + if( classpath != null ) + { + al = new AntClassLoader( project, classpath, !reverseLoader ); + } + else + { + al = new AntClassLoader( project, Path.systemClasspath, !reverseLoader ); + } + // need to load Task via system classloader or the new + // task we want to define will never be a Task but always + // be wrapped into a TaskAdapter. + al.addSystemPackageRoot( "org.apache.tools.ant" ); + return al; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Delete.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Delete.java new file mode 100644 index 000000000..e7df0b543 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Delete.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; + +/** + * Deletes a file or directory, or set of files defined by a fileset. The + * original delete task would delete a file, or a set of files using the + * include/exclude syntax. The deltree task would delete a directory tree. This + * task combines the functionality of these two originally distinct tasks.

+ * + * Currently Delete extends MatchingTask. This is intend only to provide + * backwards compatibility for a release. The future position is to use nested + * filesets exclusively.

+ * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Tom Dimock tad1@cornell.edu + * @author Glenn McAllister glennm@ca.ibm.com + * + * @author Jon S. Stevens jon@latchkey.com + */ +public class Delete extends MatchingTask +{ + protected File file = null; + protected File dir = null; + protected Vector filesets = new Vector(); + protected boolean usedMatchingTask = false; + protected boolean includeEmpty = false;// by default, remove matching empty dirs + + private int verbosity = Project.MSG_VERBOSE; + private boolean quiet = false; + private boolean failonerror = true; + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + usedMatchingTask = true; + super.setDefaultexcludes( useDefaultExcludes ); + } + + /** + * Set the directory from which files are to be deleted + * + * @param dir the directory path. + */ + public void setDir( File dir ) + { + this.dir = dir; + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + usedMatchingTask = true; + super.setExcludes( excludes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param excludesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setExcludesfile( File excludesfile ) + { + usedMatchingTask = true; + super.setExcludesfile( excludesfile ); + } + + /** + * this flag means 'note errors to the output, but keep going' + * + * @param failonerror true or false + */ + public void setFailOnError( boolean failonerror ) + { + this.failonerror = failonerror; + } + + /** + * Set the name of a single file to be removed. + * + * @param file the file to be deleted + */ + public void setFile( File file ) + { + this.file = file; + } + + + /** + * Used to delete empty directories. + * + * @param includeEmpty The new IncludeEmptyDirs value + */ + public void setIncludeEmptyDirs( boolean includeEmpty ) + { + this.includeEmpty = includeEmpty; + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space. + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + usedMatchingTask = true; + super.setIncludes( includes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param includesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setIncludesfile( File includesfile ) + { + usedMatchingTask = true; + super.setIncludesfile( includesfile ); + } + + /** + * If the file does not exist, do not display a diagnostic message or modify + * the exit status to reflect an error. This means that if a file or + * directory cannot be deleted, then no error is reported. This setting + * emulates the -f option to the Unix "rm" command. Default is + * false meaning things are "noisy" + * + * @param quiet "true" or "on" + */ + public void setQuiet( boolean quiet ) + { + this.quiet = quiet; + if( quiet ) + { + this.failonerror = false; + } + } + + /** + * Used to force listing of all names of deleted files. + * + * @param verbose "true" or "on" + */ + public void setVerbose( boolean verbose ) + { + if( verbose ) + { + this.verbosity = Project.MSG_INFO; + } + else + { + this.verbosity = Project.MSG_VERBOSE; + } + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + usedMatchingTask = true; + return super.createExclude(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + usedMatchingTask = true; + return super.createInclude(); + } + + /** + * add a set of patterns + * + * @return Description of the Returned Value + */ + public PatternSet createPatternSet() + { + usedMatchingTask = true; + return super.createPatternSet(); + } + + /** + * Delete the file(s). + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( usedMatchingTask ) + { + log( "DEPRECATED - Use of the implicit FileSet is deprecated. Use a nested fileset element instead." ); + } + + if( file == null && dir == null && filesets.size() == 0 ) + { + throw new BuildException( "At least one of the file or dir attributes, or a fileset element, must be set." ); + } + + if( quiet && failonerror ) + { + throw new BuildException( "quiet and failonerror cannot both be set to true", + location ); + } + + // delete the single file + if( file != null ) + { + if( file.exists() ) + { + if( file.isDirectory() ) + { + log( "Directory " + file.getAbsolutePath() + " cannot be removed using the file attribute. Use dir instead." ); + } + else + { + log( "Deleting: " + file.getAbsolutePath() ); + + if( !file.delete() ) + { + String message = "Unable to delete file " + file.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + else + { + log( "Could not find file " + file.getAbsolutePath() + " to delete.", + Project.MSG_VERBOSE ); + } + } + + // delete the directory + if( dir != null && dir.exists() && dir.isDirectory() && !usedMatchingTask ) + { + /* + * If verbosity is MSG_VERBOSE, that mean we are doing regular logging + * (backwards as that sounds). In that case, we want to print one + * message about deleting the top of the directory tree. Otherwise, + * the removeDir method will handle messages for _all_ directories. + */ + if( verbosity == Project.MSG_VERBOSE ) + { + log( "Deleting directory " + dir.getAbsolutePath() ); + } + removeDir( dir ); + } + + // delete the files in the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + try + { + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] files = ds.getIncludedFiles(); + String[] dirs = ds.getIncludedDirectories(); + removeFiles( fs.getDir( project ), files, dirs ); + } + catch( BuildException be ) + { + // directory doesn't exist or is not readable + if( failonerror ) + { + throw be; + } + else + { + log( be.getMessage(), + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + + // delete the files from the default fileset + if( usedMatchingTask && dir != null ) + { + try + { + DirectoryScanner ds = super.getDirectoryScanner( dir ); + String[] files = ds.getIncludedFiles(); + String[] dirs = ds.getIncludedDirectories(); + removeFiles( dir, files, dirs ); + } + catch( BuildException be ) + { + // directory doesn't exist or is not readable + if( failonerror ) + { + throw be; + } + else + { + log( be.getMessage(), + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + } + +//************************************************************************ +// protected and private methods +//************************************************************************ + + protected void removeDir( File d ) + { + String[] list = d.list(); + if( list == null ) + list = new String[0]; + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( d, s ); + if( f.isDirectory() ) + { + removeDir( f ); + } + else + { + log( "Deleting " + f.getAbsolutePath(), verbosity ); + if( !f.delete() ) + { + String message = "Unable to delete file " + f.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + log( "Deleting directory " + d.getAbsolutePath(), verbosity ); + if( !d.delete() ) + { + String message = "Unable to delete directory " + dir.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + + /** + * remove an array of files in a directory, and a list of subdirectories + * which will only be deleted if 'includeEmpty' is true + * + * @param d directory to work from + * @param files array of files to delete; can be of zero length + * @param dirs array of directories to delete; can of zero length + */ + protected void removeFiles( File d, String[] files, String[] dirs ) + { + if( files.length > 0 ) + { + log( "Deleting " + files.length + " files from " + d.getAbsolutePath() ); + for( int j = 0; j < files.length; j++ ) + { + File f = new File( d, files[j] ); + log( "Deleting " + f.getAbsolutePath(), verbosity ); + if( !f.delete() ) + { + String message = "Unable to delete file " + f.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + + if( dirs.length > 0 && includeEmpty ) + { + int dirCount = 0; + for( int j = dirs.length - 1; j >= 0; j-- ) + { + File dir = new File( d, dirs[j] ); + String[] dirFiles = dir.list(); + if( dirFiles == null || dirFiles.length == 0 ) + { + log( "Deleting " + dir.getAbsolutePath(), verbosity ); + if( !dir.delete() ) + { + String message = "Unable to delete directory " + + dir.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + else + { + dirCount++; + } + } + } + + if( dirCount > 0 ) + { + log( "Deleted " + dirCount + " director" + + ( dirCount == 1 ? "y" : "ies" ) + + " from " + d.getAbsolutePath() ); + } + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Deltree.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Deltree.java new file mode 100644 index 000000000..f6e746e7b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Deltree.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * @author duncan@x180.com + * @deprecated The deltree task is deprecated. Use delete instead. + */ + +public class Deltree extends Task +{ + + private File dir; + + public void setDir( File dir ) + { + this.dir = dir; + } + + public void execute() + throws BuildException + { + log( "DEPRECATED - The deltree task is deprecated. Use delete instead." ); + + if( dir == null ) + { + throw new BuildException( "dir attribute must be set!", location ); + } + + if( dir.exists() ) + { + if( !dir.isDirectory() ) + { + if( !dir.delete() ) + { + throw new BuildException( "Unable to delete directory " + + dir.getAbsolutePath(), + location ); + } + return; + // String msg = "Given dir: " + dir.getAbsolutePath() + + // " is not a dir"; + // throw new BuildException(msg); + } + + log( "Deleting: " + dir.getAbsolutePath() ); + + try + { + removeDir( dir ); + } + catch( IOException ioe ) + { + String msg = "Unable to delete " + dir.getAbsolutePath(); + throw new BuildException( msg, location ); + } + } + } + + private void removeDir( File dir ) + throws IOException + { + + // check to make sure that the given dir isn't a symlink + // the comparison of absolute path and canonical path + // catches this + + // if (dir.getCanonicalPath().equals(dir.getAbsolutePath())) { + // (costin) It will not work if /home/costin is symlink to /da0/home/costin ( taz + // for example ) + String[] list = dir.list(); + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( dir, s ); + if( f.isDirectory() ) + { + removeDir( f ); + } + else + { + if( !f.delete() ) + { + throw new BuildException( "Unable to delete file " + f.getAbsolutePath() ); + } + } + } + if( !dir.delete() ) + { + throw new BuildException( "Unable to delete directory " + dir.getAbsolutePath() ); + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/DependSet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/DependSet.java new file mode 100644 index 000000000..0768f88e9 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/DependSet.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Date; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.FileList; +import org.apache.tools.ant.types.FileSet; + +/** + * A Task to record explicit dependencies. If any of the target files are out of + * date with respect to any of the source files, all target files are removed. + * This is useful where dependencies cannot be computed (for example, + * dynamically interpreted parameters or files that need to stay in synch but + * are not directly linked) or where the ant task in question could compute them + * but does not (for example, the linked DTD for an XML file using the style + * task). nested arguments: + *
    + *
  • srcfileset (fileset describing the source files to examine) + *
  • srcfilelist (filelist describing the source files to examine) + *
  • targetfileset (fileset describing the target files to examine) + *
  • targetfilelist (filelist describing the target files to examine) + *
+ * At least one instance of either a fileset or filelist for both source and + * target are required.

+ * + * This task will examine each of the source files against each of the target + * files. If any target files are out of date with respect to any of the source + * files, all targets are removed. If any files named in a (src or target) + * filelist do not exist, all targets are removed. Hint: If missing files should + * be ignored, specify them as include patterns in filesets, rather than using + * filelists.

+ * + * This task attempts to optimize speed of dependency checking. It will stop + * after the first out of date file is found and remove all targets, rather than + * exhaustively checking every source vs target combination unnecessarily.

+ *

+ * + * Example uses: + *

    + *
  • Record the fact that an XML file must be up to date with respect to + * its XSD (Schema file), even though the XML file itself includes no + * reference to its XSD.
  • + *
  • Record the fact that an XSL stylesheet includes other sub-stylesheets + *
  • + *
  • Record the fact that java files must be recompiled if the ant build + * file changes
  • + *
+ * + * + * @author Craeg Strong + * @version $Revision$ $Date$ + */ +public class DependSet extends MatchingTask +{ + + private Vector sourceFileSets = new Vector(); + private Vector sourceFileLists = new Vector(); + private Vector targetFileSets = new Vector(); + private Vector targetFileLists = new Vector(); + + /** + * Creates a new DependSet Task. + */ + public DependSet() { } + + /** + * Nested <srcfilelist> element. + * + * @param fl The feature to be added to the Srcfilelist attribute + */ + public void addSrcfilelist( FileList fl ) + { + sourceFileLists.addElement( fl ); + }//-- DependSet + + /** + * Nested <srcfileset> element. + * + * @param fs The feature to be added to the Srcfileset attribute + */ + public void addSrcfileset( FileSet fs ) + { + sourceFileSets.addElement( fs ); + } + + /** + * Nested <targetfilelist> element. + * + * @param fl The feature to be added to the Targetfilelist attribute + */ + public void addTargetfilelist( FileList fl ) + { + targetFileLists.addElement( fl ); + } + + /** + * Nested <targetfileset> element. + * + * @param fs The feature to be added to the Targetfileset attribute + */ + public void addTargetfileset( FileSet fs ) + { + targetFileSets.addElement( fs ); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + + public void execute() + throws BuildException + { + + if( ( sourceFileSets.size() == 0 ) && ( sourceFileLists.size() == 0 ) ) + { + throw new BuildException( "At least one or element must be set" ); + } + + if( ( targetFileSets.size() == 0 ) && ( targetFileLists.size() == 0 ) ) + { + throw new BuildException( "At least one or element must be set" ); + } + + long now = ( new Date() ).getTime(); + /* + * If we're on Windows, we have to munge the time up to 2 secs to + * be able to check file modification times. + * (Windows has a max resolution of two secs for modification times) + */ + if( Os.isFamily( "windows" ) ) + { + now += 2000; + } + + // + // Grab all the target files specified via filesets + // + Vector allTargets = new Vector(); + Enumeration enumTargetSets = targetFileSets.elements(); + while( enumTargetSets.hasMoreElements() ) + { + + FileSet targetFS = ( FileSet )enumTargetSets.nextElement(); + DirectoryScanner targetDS = targetFS.getDirectoryScanner( project ); + String[] targetFiles = targetDS.getIncludedFiles(); + + for( int i = 0; i < targetFiles.length; i++ ) + { + + File dest = new File( targetFS.getDir( project ), targetFiles[i] ); + allTargets.addElement( dest ); + + if( dest.lastModified() > now ) + { + log( "Warning: " + targetFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + } + } + + // + // Grab all the target files specified via filelists + // + boolean upToDate = true; + Enumeration enumTargetLists = targetFileLists.elements(); + while( enumTargetLists.hasMoreElements() ) + { + + FileList targetFL = ( FileList )enumTargetLists.nextElement(); + String[] targetFiles = targetFL.getFiles( project ); + + for( int i = 0; i < targetFiles.length; i++ ) + { + + File dest = new File( targetFL.getDir( project ), targetFiles[i] ); + if( !dest.exists() ) + { + log( targetFiles[i] + " does not exist.", Project.MSG_VERBOSE ); + upToDate = false; + continue; + } + else + { + allTargets.addElement( dest ); + } + if( dest.lastModified() > now ) + { + log( "Warning: " + targetFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + } + } + + // + // Check targets vs source files specified via filesets + // + if( upToDate ) + { + Enumeration enumSourceSets = sourceFileSets.elements(); + while( upToDate && enumSourceSets.hasMoreElements() ) + { + + FileSet sourceFS = ( FileSet )enumSourceSets.nextElement(); + DirectoryScanner sourceDS = sourceFS.getDirectoryScanner( project ); + String[] sourceFiles = sourceDS.getIncludedFiles(); + + for( int i = 0; upToDate && i < sourceFiles.length; i++ ) + { + File src = new File( sourceFS.getDir( project ), sourceFiles[i] ); + + if( src.lastModified() > now ) + { + log( "Warning: " + sourceFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + + Enumeration enumTargets = allTargets.elements(); + while( upToDate && enumTargets.hasMoreElements() ) + { + + File dest = ( File )enumTargets.nextElement(); + if( src.lastModified() > dest.lastModified() ) + { + log( dest.getPath() + " is out of date with respect to " + + sourceFiles[i], Project.MSG_VERBOSE ); + upToDate = false; + + } + } + } + } + } + + // + // Check targets vs source files specified via filelists + // + if( upToDate ) + { + Enumeration enumSourceLists = sourceFileLists.elements(); + while( upToDate && enumSourceLists.hasMoreElements() ) + { + + FileList sourceFL = ( FileList )enumSourceLists.nextElement(); + String[] sourceFiles = sourceFL.getFiles( project ); + + int i = 0; + do + { + File src = new File( sourceFL.getDir( project ), sourceFiles[i] ); + + if( src.lastModified() > now ) + { + log( "Warning: " + sourceFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + + if( !src.exists() ) + { + log( sourceFiles[i] + " does not exist.", Project.MSG_VERBOSE ); + upToDate = false; + break; + } + + Enumeration enumTargets = allTargets.elements(); + while( upToDate && enumTargets.hasMoreElements() ) + { + + File dest = ( File )enumTargets.nextElement(); + + if( src.lastModified() > dest.lastModified() ) + { + log( dest.getPath() + " is out of date with respect to " + + sourceFiles[i], Project.MSG_VERBOSE ); + upToDate = false; + + } + } + }while ( upToDate && ( ++i < sourceFiles.length ) ); + } + } + + if( !upToDate ) + { + log( "Deleting all target files. ", Project.MSG_VERBOSE ); + for( Enumeration e = allTargets.elements(); e.hasMoreElements(); ) + { + File fileToRemove = ( File )e.nextElement(); + log( "Deleting file " + fileToRemove.getAbsolutePath(), Project.MSG_VERBOSE ); + fileToRemove.delete(); + } + } + + }//-- execute + +}//-- DependSet.java diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ear.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ear.java new file mode 100644 index 000000000..955cbb6ca --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Ear.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.zip.ZipOutputStream; + + +/** + * Creates a EAR archive. Based on WAR task + * + * @author Stefan Bodewig + * @author Les Hughes + */ +public class Ear extends Jar +{ + + private File deploymentDescriptor; + private boolean descriptorAdded; + + public Ear() + { + super(); + archiveType = "ear"; + emptyBehavior = "create"; + } + + public void setAppxml( File descr ) + { + deploymentDescriptor = descr; + if( !deploymentDescriptor.exists() ) + throw new BuildException( "Deployment descriptor: " + deploymentDescriptor + " does not exist." ); + + // Create a ZipFileSet for this file, and pass it up. + ZipFileSet fs = new ZipFileSet(); + fs.setDir( new File( deploymentDescriptor.getParent() ) ); + fs.setIncludes( deploymentDescriptor.getName() ); + fs.setFullpath( "META-INF/application.xml" ); + super.addFileset( fs ); + } + + public void setEarfile( File earFile ) + { + log( "DEPRECATED - The earfile attribute is deprecated. Use file attribute instead." ); + setFile( earFile ); + } + + + public void addArchives( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + // Do we need to do this? LH + log( "addArchives called", Project.MSG_DEBUG ); + fs.setPrefix( "/" ); + super.addFileset( fs ); + } + + /** + * Make sure we don't think we already have a web.xml next time this task + * gets executed. + */ + protected void cleanUp() + { + descriptorAdded = false; + super.cleanUp(); + } + + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + // If no webxml file is specified, it's an error. + if( deploymentDescriptor == null && !isInUpdateMode() ) + { + throw new BuildException( "appxml attribute is required", location ); + } + + super.initZipOutputStream( zOut ); + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + // If the file being added is WEB-INF/web.xml, we warn if it's not the + // one specified in the "webxml" attribute - or if it's being added twice, + // meaning the same file is specified by the "webxml" attribute and in + // a element. + if( vPath.equalsIgnoreCase( "META-INF/aplication.xml" ) ) + { + if( deploymentDescriptor == null || !deploymentDescriptor.equals( file ) || descriptorAdded ) + { + log( "Warning: selected " + archiveType + " files include a META-INF/application.xml which will be ignored " + + "(please use appxml attribute to " + archiveType + " task)", Project.MSG_WARN ); + } + else + { + super.zipFile( file, zOut, vPath ); + descriptorAdded = true; + } + } + else + { + super.zipFile( file, zOut, vPath ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Echo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Echo.java new file mode 100644 index 000000000..1e57b259a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Echo.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Echo + * + * @author costin@dnt.ro + */ +public class Echo extends Task +{ + protected String message = "";// required + protected File file = null; + protected boolean append = false; + + // by default, messages are always displayed + protected int logLevel = Project.MSG_WARN; + + /** + * Shall we append to an existing file? + * + * @param append The new Append value + */ + public void setAppend( boolean append ) + { + this.append = append; + } + + /** + * Sets the file attribute. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Set the logging level to one of + *
    + *
  • error
  • + *
  • warning
  • + *
  • info
  • + *
  • verbose
  • + *
  • debug
  • + *

      + * + * The default is "warning" to ensure that messages are + * displayed by default when using the -quiet command line option.

      + * + * @param echoLevel The new Level value + */ + public void setLevel( EchoLevel echoLevel ) + { + String option = echoLevel.getValue(); + if( option.equals( "error" ) ) + { + logLevel = Project.MSG_ERR; + } + else if( option.equals( "warning" ) ) + { + logLevel = Project.MSG_WARN; + } + else if( option.equals( "info" ) ) + { + logLevel = Project.MSG_INFO; + } + else if( option.equals( "verbose" ) ) + { + logLevel = Project.MSG_VERBOSE; + } + else + { + // must be "debug" + logLevel = Project.MSG_DEBUG; + } + } + + /** + * Sets the message variable. + * + * @param msg Sets the value for the message variable. + */ + public void setMessage( String msg ) + { + this.message = msg; + } + + /** + * Set a multiline message. + * + * @param msg The feature to be added to the Text attribute + */ + public void addText( String msg ) + { + message += project.replaceProperties( msg ); + } + + /** + * Does the work. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( file == null ) + { + log( message, logLevel ); + } + else + { + FileWriter out = null; + try + { + out = new FileWriter( file.getAbsolutePath(), append ); + out.write( message, 0, message.length() ); + } + catch( IOException ioe ) + { + throw new BuildException( ioe); + } + finally + { + if( out != null ) + { + try + { + out.close(); + } + catch( IOException ioex ) + {} + } + } + } + } + + public static class EchoLevel extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"error", "warning", "info", "verbose", "debug"}; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exec.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exec.java new file mode 100644 index 000000000..076de6fde --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exec.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Executes a given command if the os platform is appropriate. + * + * @author duncan@x180.com + * @author rubys@us.ibm.com + * @deprecated Instead of using this class, please extend ExecTask or delegate + * to Execute. + */ +public class Exec extends Task +{ + + private final static int BUFFER_SIZE = 512; + protected PrintWriter fos = null; + private boolean failOnError = false; + private String command; + private File dir; + private String os; + private String out; + + public void setCommand( String command ) + { + this.command = command; + } + + public void setDir( String d ) + { + this.dir = project.resolveFile( d ); + } + + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + public void setOs( String os ) + { + this.os = os; + } + + public void setOutput( String out ) + { + this.out = out; + } + + public void execute() + throws BuildException + { + run( command ); + } + + protected void logFlush() + { + if( fos != null ) + fos.close(); + } + + protected void outputLog( String line, int messageLevel ) + { + if( fos == null ) + { + log( line, messageLevel ); + } + else + { + fos.println( line ); + } + } + + protected int run( String command ) + throws BuildException + { + + int err = -1;// assume the worst + + // test if os match + String myos = System.getProperty( "os.name" ); + log( "Myos = " + myos, Project.MSG_VERBOSE ); + if( ( os != null ) && ( os.indexOf( myos ) < 0 ) ) + { + // this command will be executed only on the specified OS + log( "Not found in " + os, Project.MSG_VERBOSE ); + return 0; + } + + // default directory to the project's base directory + if( dir == null ) + dir = project.getBaseDir(); + + if( myos.toLowerCase().indexOf( "windows" ) >= 0 ) + { + if( !dir.equals( project.resolveFile( "." ) ) ) + { + if( myos.toLowerCase().indexOf( "nt" ) >= 0 ) + { + command = "cmd /c cd " + dir + " && " + command; + } + else + { + String ant = project.getProperty( "ant.home" ); + if( ant == null ) + { + throw new BuildException( "Property 'ant.home' not found", location ); + } + + String antRun = project.resolveFile( ant + "/bin/antRun.bat" ).toString(); + command = antRun + " " + dir + " " + command; + } + } + } + else + { + String ant = project.getProperty( "ant.home" ); + if( ant == null ) + throw new BuildException( "Property 'ant.home' not found", location ); + String antRun = project.resolveFile( ant + "/bin/antRun" ).toString(); + + command = antRun + " " + dir + " " + command; + } + + try + { + // show the command + log( command, Project.MSG_VERBOSE ); + + // exec command on system runtime + Process proc = Runtime.getRuntime().exec( command ); + + if( out != null ) + { + fos = new PrintWriter( new FileWriter( out ) ); + log( "Output redirected to " + out, Project.MSG_VERBOSE ); + } + + // copy input and error to the output stream + StreamPumper inputPumper = + new StreamPumper( proc.getInputStream(), Project.MSG_INFO, this ); + StreamPumper errorPumper = + new StreamPumper( proc.getErrorStream(), Project.MSG_WARN, this ); + + // starts pumping away the generated output/error + inputPumper.start(); + errorPumper.start(); + + // Wait for everything to finish + proc.waitFor(); + inputPumper.join(); + errorPumper.join(); + proc.destroy(); + + // close the output file if required + logFlush(); + + // check its exit value + err = proc.exitValue(); + if( err != 0 ) + { + if( failOnError ) + { + throw new BuildException( "Exec returned: " + err, location ); + } + else + { + log( "Result: " + err, Project.MSG_ERR ); + } + } + } + catch( IOException ioe ) + { + throw new BuildException( "Error exec: " + command, ioe, location ); + } + catch( InterruptedException ex ) + {} + + return err; + } + + // Inner class for continually pumping the input stream during + // Process's runtime. + class StreamPumper extends Thread + { + private boolean endOfStream = false; + private int SLEEP_TIME = 5; + private BufferedReader din; + private int messageLevel; + private Exec parent; + + public StreamPumper( InputStream is, int messageLevel, Exec parent ) + { + this.din = new BufferedReader( new InputStreamReader( is ) ); + this.messageLevel = messageLevel; + this.parent = parent; + } + + public void pumpStream() + throws IOException + { + byte[] buf = new byte[BUFFER_SIZE]; + if( !endOfStream ) + { + String line = din.readLine(); + + if( line != null ) + { + outputLog( line, messageLevel ); + } + else + { + endOfStream = true; + } + } + } + + public void run() + { + try + { + try + { + while( !endOfStream ) + { + pumpStream(); + sleep( SLEEP_TIME ); + } + } + catch( InterruptedException ie ) + {} + din.close(); + } + catch( IOException ioe ) + {} + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecTask.java new file mode 100644 index 000000000..f236d8eae --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecTask.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringReader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Environment; + +/** + * Executes a given command if the os platform is appropriate. + * + * @author duncan@x180.com + * @author rubys@us.ibm.com + * @author thomas.haas@softwired-inc.com + * @author Stefan Bodewig + * @author Mariusz Nowostawski + */ +public class ExecTask extends Task +{ + + private static String lSep = System.getProperty( "line.separator" ); + protected boolean failOnError = false; + protected boolean newEnvironment = false; + private Integer timeout = null; + private Environment env = new Environment(); + protected Commandline cmdl = new Commandline(); + private FileOutputStream fos = null; + private ByteArrayOutputStream baos = null; + private boolean failIfExecFails = true; + + /** + * Controls whether the VM (1.3 and above) is used to execute the command + */ + private boolean vmLauncher = true; + private File dir; + + private String os; + private File out; + private String outputprop; + private String resultProperty; + + /** + * The full commandline to execute, executable + arguments. + * + * @param cmdl The new Command value + */ + public void setCommand( Commandline cmdl ) + { + log( "The command attribute is deprecated. " + + "Please use the executable attribute and nested arg elements.", + Project.MSG_WARN ); + this.cmdl = cmdl; + } + + /** + * The working directory of the process + * + * @param d The new Dir value + */ + public void setDir( File d ) + { + this.dir = d; + } + + /** + * The command to execute. + * + * @param value The new Executable value + */ + public void setExecutable( String value ) + { + cmdl.setExecutable( value ); + } + + /** + * ant attribute + * + * @param flag The new FailIfExecutionFails value + */ + public void setFailIfExecutionFails( boolean flag ) + { + failIfExecFails = flag; + } + + /** + * Throw a BuildException if process returns non 0. + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Use a completely new environment + * + * @param newenv The new Newenvironment value + */ + public void setNewenvironment( boolean newenv ) + { + newEnvironment = newenv; + } + + /** + * Only execute the process if os.name is included in this + * string. + * + * @param os The new Os value + */ + public void setOs( String os ) + { + this.os = os; + } + + /** + * File the output of the process is redirected to. + * + * @param out The new Output value + */ + public void setOutput( File out ) + { + this.out = out; + } + + /** + * Property name whose value should be set to the output of the process + * + * @param outputprop The new Outputproperty value + */ + public void setOutputproperty( String outputprop ) + { + this.outputprop = outputprop; + } + + /** + * fill a property in with a result. when no property is defined: failure to + * execute + * + * @param resultProperty The new ResultProperty value + * @since 1.5 + */ + public void setResultProperty( String resultProperty ) + { + this.resultProperty = resultProperty; + } + + /** + * Timeout in milliseconds after which the process will be killed. + * + * @param value The new Timeout value + */ + public void setTimeout( Integer value ) + { + timeout = value; + } + + /** + * Control whether the VM is used to launch the new process or whether the + * OS's shell is used. + * + * @param vmLauncher The new VMLauncher value + */ + public void setVMLauncher( boolean vmLauncher ) + { + this.vmLauncher = vmLauncher; + } + + /** + * Add a nested env element - an environment variable. + * + * @param var The feature to be added to the Env attribute + */ + public void addEnv( Environment.Variable var ) + { + env.addVariable( var ); + } + + /** + * Add a nested arg element - a command line argument. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createArg() + { + return cmdl.createArgument(); + } + + /** + * Do the work. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + checkConfiguration(); + if( isValidOs() ) + { + runExec( prepareExec() ); + } + } + + /** + * Is this the OS the user wanted? + * + * @return The ValidOs value + */ + protected boolean isValidOs() + { + // test if os match + String myos = System.getProperty( "os.name" ); + log( "Current OS is " + myos, Project.MSG_VERBOSE ); + if( ( os != null ) && ( os.indexOf( myos ) < 0 ) ) + { + // this command will be executed only on the specified OS + log( "This OS, " + myos + " was not found in the specified list of valid OSes: " + os, Project.MSG_VERBOSE ); + return false; + } + return true; + } + + /** + * A Utility method for this classes and subclasses to run an Execute + * instance (an external command). + * + * @param exe Description of Parameter + * @exception IOException Description of Exception + */ + protected final void runExecute( Execute exe ) + throws IOException + { + int err = -1;// assume the worst + + err = exe.execute(); + //test for and handle a forced process death + if( exe.killedProcess() ) + { + log( "Timeout: killed the sub-process", Project.MSG_WARN ); + } + maybeSetResultPropertyValue( err ); + if( err != 0 ) + { + if( failOnError ) + { + throw new BuildException( taskType + " returned: " + err, location ); + } + else + { + log( "Result: " + err, Project.MSG_ERR ); + } + } + if( baos != null ) + { + BufferedReader in = + new BufferedReader( new StringReader( baos.toString() ) ); + String line = null; + StringBuffer val = new StringBuffer(); + while( ( line = in.readLine() ) != null ) + { + if( val.length() != 0 ) + { + val.append( lSep ); + } + val.append( line ); + } + project.setNewProperty( outputprop, val.toString() ); + } + } + + /** + * Has the user set all necessary attributes? + * + * @exception BuildException Description of Exception + */ + protected void checkConfiguration() + throws BuildException + { + if( cmdl.getExecutable() == null ) + { + throw new BuildException( "no executable specified", location ); + } + if( dir != null && !dir.exists() ) + { + throw new BuildException( "The directory you specified does not exist" ); + } + if( dir != null && !dir.isDirectory() ) + { + throw new BuildException( "The directory you specified is not a directory" ); + } + } + + /** + * Create the StreamHandler to use with our Execute instance. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected ExecuteStreamHandler createHandler() + throws BuildException + { + if( out != null ) + { + try + { + fos = new FileOutputStream( out ); + log( "Output redirected to " + out, Project.MSG_VERBOSE ); + return new PumpStreamHandler( fos ); + } + catch( FileNotFoundException fne ) + { + throw new BuildException( "Cannot write to " + out, fne, location ); + } + catch( IOException ioe ) + { + throw new BuildException( "Cannot write to " + out, ioe, location ); + } + } + else if( outputprop != null ) + { + baos = new ByteArrayOutputStream(); + log( "Output redirected to ByteArray", Project.MSG_VERBOSE ); + return new PumpStreamHandler( baos ); + } + else + { + return new LogStreamHandler( this, + Project.MSG_INFO, Project.MSG_WARN ); + } + } + + /** + * Create the Watchdog to kill a runaway process. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected ExecuteWatchdog createWatchdog() + throws BuildException + { + if( timeout == null ) + return null; + return new ExecuteWatchdog( timeout.intValue() ); + } + + /** + * Flush the output stream - if there is one. + */ + protected void logFlush() + { + try + { + if( fos != null ) + fos.close(); + if( baos != null ) + baos.close(); + } + catch( IOException io ) + {} + } + + /** + * helper method to set result property to the passed in value if + * appropriate + * + * @param result Description of Parameter + */ + protected void maybeSetResultPropertyValue( int result ) + { + String res = Integer.toString( result ); + if( resultProperty != null ) + { + project.setNewProperty( resultProperty, res ); + } + } + + /** + * Create an Execute instance with the correct working directory set. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected Execute prepareExec() + throws BuildException + { + // default directory to the project's base directory + if( dir == null ) + dir = project.getBaseDir(); + // show the command + log( cmdl.toString(), Project.MSG_VERBOSE ); + + Execute exe = new Execute( createHandler(), createWatchdog() ); + exe.setAntRun( project ); + exe.setWorkingDirectory( dir ); + exe.setVMLauncher( vmLauncher ); + String[] environment = env.getVariables(); + if( environment != null ) + { + for( int i = 0; i < environment.length; i++ ) + { + log( "Setting environment variable: " + environment[i], + Project.MSG_VERBOSE ); + } + } + exe.setNewenvironment( newEnvironment ); + exe.setEnvironment( environment ); + return exe; + } + + /** + * Run the command using the given Execute instance. This may be overidden + * by subclasses + * + * @param exe Description of Parameter + * @exception BuildException Description of Exception + */ + protected void runExec( Execute exe ) + throws BuildException + { + exe.setCommandline( cmdl.getCommandline() ); + try + { + runExecute( exe ); + } + catch( IOException e ) + { + if( failIfExecFails ) + { + throw new BuildException( "Execute failed: " + e.toString(), e, location ); + } + else + { + log( "Execute failed: " + e.toString(), Project.MSG_ERR ); + } + } + finally + { + // close the output file if required + logFlush(); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Execute.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Execute.java new file mode 100644 index 000000000..c1fdd0c72 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Execute.java @@ -0,0 +1,947 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Locale; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; + +/** + * Runs an external program. + * + * @author thomas.haas@softwired-inc.com + */ +public class Execute +{ + /** + * Invalid exit code. + */ + public final static int INVALID = Integer.MAX_VALUE; + + private static String antWorkingDirectory = System.getProperty( "user.dir" ); + private static CommandLauncher vmLauncher; + private static CommandLauncher shellLauncher; + private static Vector procEnvironment; + + /** + * Used to destroy processes when the VM exits. + */ + private static ProcessDestroyer processDestroyer = new ProcessDestroyer(); + + private String[] cmdl = null; + private String[] env = null; + private int exitValue = INVALID; + private File workingDirectory = null; + private Project project = null; + private boolean newEnvironment = false; + + /** + * Controls whether the VM is used to launch commands, where possible + */ + private boolean useVMLauncher = true; + private ExecuteStreamHandler streamHandler; + private ExecuteWatchdog watchdog; + + /** + * Builds a command launcher for the OS and JVM we are running under + */ + static + { + // Try using a JDK 1.3 launcher + try + { + vmLauncher = new Java13CommandLauncher(); + } + catch( NoSuchMethodException exc ) + { + // Ignore and keep try + } + + if( Os.isFamily( "mac" ) ) + { + // Mac + shellLauncher = new MacCommandLauncher( new CommandLauncher() ); + } + else if( Os.isFamily( "os/2" ) ) + { + // OS/2 - use same mechanism as Windows 2000 + shellLauncher = new WinNTCommandLauncher( new CommandLauncher() ); + } + else if( Os.isFamily( "windows" ) ) + { + // Windows. Need to determine which JDK we're running in + + CommandLauncher baseLauncher; + if( System.getProperty( "java.version" ).startsWith( "1.1" ) ) + { + // JDK 1.1 + baseLauncher = new Java11CommandLauncher(); + } + else + { + // JDK 1.2 + baseLauncher = new CommandLauncher(); + } + + // Determine if we're running under 2000/NT or 98/95 + String osname = + System.getProperty( "os.name" ).toLowerCase( Locale.US ); + + if( osname.indexOf( "nt" ) >= 0 || osname.indexOf( "2000" ) >= 0 ) + { + // Windows 2000/NT + shellLauncher = new WinNTCommandLauncher( baseLauncher ); + } + else + { + // Windows 98/95 - need to use an auxiliary script + shellLauncher = new ScriptCommandLauncher( "bin/antRun.bat", baseLauncher ); + } + } + else if( ( new Os( "netware" ) ).eval() ) + { + // NetWare. Need to determine which JDK we're running in + CommandLauncher baseLauncher; + if( System.getProperty( "java.version" ).startsWith( "1.1" ) ) + { + // JDK 1.1 + baseLauncher = new Java11CommandLauncher(); + } + else + { + // JDK 1.2 + baseLauncher = new CommandLauncher(); + } + + shellLauncher = new PerlScriptCommandLauncher( "bin/antRun.pl", baseLauncher ); + } + else + { + // Generic + shellLauncher = new ScriptCommandLauncher( "bin/antRun", new CommandLauncher() ); + } + } + + /** + * Creates a new execute object using PumpStreamHandler for + * stream handling. + */ + public Execute() + { + this( new PumpStreamHandler(), null ); + } + + /** + * Creates a new execute object. + * + * @param streamHandler the stream handler used to handle the input and + * output streams of the subprocess. + */ + public Execute( ExecuteStreamHandler streamHandler ) + { + this( streamHandler, null ); + } + + /** + * Creates a new execute object. + * + * @param streamHandler the stream handler used to handle the input and + * output streams of the subprocess. + * @param watchdog a watchdog for the subprocess or null to to + * disable a timeout for the subprocess. + */ + public Execute( ExecuteStreamHandler streamHandler, ExecuteWatchdog watchdog ) + { + this.streamHandler = streamHandler; + this.watchdog = watchdog; + } + + /** + * Find the list of environment variables for this process. + * + * @return The ProcEnvironment value + */ + public static synchronized Vector getProcEnvironment() + { + if( procEnvironment != null ) + return procEnvironment; + + procEnvironment = new Vector(); + try + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Execute exe = new Execute( new PumpStreamHandler( out ) ); + exe.setCommandline( getProcEnvCommand() ); + // Make sure we do not recurse forever + exe.setNewenvironment( true ); + int retval = exe.execute(); + if( retval != 0 ) + { + // Just try to use what we got + } + + BufferedReader in = + new BufferedReader( new StringReader( out.toString() ) ); + String var = null; + String line; + String lineSep = System.getProperty( "line.separator" ); + while( ( line = in.readLine() ) != null ) + { + if( line.indexOf( '=' ) == -1 ) + { + // Chunk part of previous env var (UNIX env vars can + // contain embedded new lines). + if( var == null ) + { + var = lineSep + line; + } + else + { + var += lineSep + line; + } + } + else + { + // New env var...append the previous one if we have it. + if( var != null ) + { + procEnvironment.addElement( var ); + } + var = line; + } + } + // Since we "look ahead" before adding, there's one last env var. + procEnvironment.addElement( var ); + } + catch( java.io.IOException exc ) + { + exc.printStackTrace(); + // Just try to see how much we got + } + return procEnvironment; + } + + /** + * A utility method that runs an external command. Writes the output and + * error streams of the command to the project log. + * + * @param task The task that the command is part of. Used for logging + * @param cmdline The command to execute. + * @throws BuildException if the command does not return 0. + */ + public static void runCommand( Task task, String[] cmdline ) + throws BuildException + { + try + { + task.log( Commandline.toString( cmdline ), Project.MSG_VERBOSE ); + Execute exe = new Execute( new LogStreamHandler( task, + Project.MSG_INFO, + Project.MSG_ERR ) ); + exe.setAntRun( task.getProject() ); + exe.setCommandline( cmdline ); + int retval = exe.execute(); + if( retval != 0 ) + { + throw new BuildException( cmdline[ 0 ] + " failed with return code " + retval, task.getLocation() ); + } + } + catch( java.io.IOException exc ) + { + throw new BuildException( "Could not launch " + cmdline[ 0 ] + ": " + exc, task.getLocation() ); + } + } + + private static String[] getProcEnvCommand() + { + if( Os.isFamily( "os/2" ) ) + { + // OS/2 - use same mechanism as Windows 2000 + // Not sure + String[] cmd = {"cmd", "/c", "set"}; + return cmd; + } + else if( Os.isFamily( "windows" ) ) + { + String osname = + System.getProperty( "os.name" ).toLowerCase( Locale.US ); + // Determine if we're running under 2000/NT or 98/95 + if( osname.indexOf( "nt" ) >= 0 || osname.indexOf( "2000" ) >= 0 ) + { + // Windows 2000/NT + String[] cmd = {"cmd", "/c", "set"}; + return cmd; + } + else + { + // Windows 98/95 - need to use an auxiliary script + String[] cmd = {"command.com", "/c", "set"}; + return cmd; + } + } + else if( Os.isFamily( "unix" ) ) + { + // Generic UNIX + // Alternatively one could use: /bin/sh -c env + String[] cmd = {"/usr/bin/env"}; + return cmd; + } + else if( Os.isFamily( "netware" ) ) + { + String[] cmd = {"env"}; + return cmd; + } + else + { + // MAC OS 9 and previous + // TODO: I have no idea how to get it, someone must fix it + String[] cmd = null; + return cmd; + } + } + + /** + * Set the name of the antRun script using the project's value. + * + * @param project the current project. + * @exception BuildException Description of Exception + */ + public void setAntRun( Project project ) + throws BuildException + { + this.project = project; + } + + /** + * Sets the commandline of the subprocess to launch. + * + * @param commandline the commandline of the subprocess to launch + */ + public void setCommandline( String[] commandline ) + { + cmdl = commandline; + } + + /** + * Sets the environment variables for the subprocess to launch. + * + * @param env The new Environment value + */ + public void setEnvironment( String[] env ) + { + this.env = env; + } + + /** + * Set whether to propagate the default environment or not. + * + * @param newenv whether to propagate the process environment. + */ + public void setNewenvironment( boolean newenv ) + { + newEnvironment = newenv; + } + + /** + * Launch this execution through the VM, where possible, rather than through + * the OS's shell. In some cases and operating systems using the shell will + * allow the shell to perform additional processing such as associating an + * executable with a script, etc + * + * @param useVMLauncher The new VMLauncher value + */ + public void setVMLauncher( boolean useVMLauncher ) + { + this.useVMLauncher = useVMLauncher; + } + + /** + * Sets the working directory of the process to execute.

      + * + * This is emulated using the antRun scripts unless the OS is Windows NT in + * which case a cmd.exe is spawned, or MRJ and setting user.dir works, or + * JDK 1.3 and there is official support in java.lang.Runtime. + * + * @param wd the working directory of the process. + */ + public void setWorkingDirectory( File wd ) + { + if( wd == null || wd.getAbsolutePath().equals( antWorkingDirectory ) ) + workingDirectory = null; + else + workingDirectory = wd; + } + + /** + * Returns the commandline used to create a subprocess. + * + * @return the commandline used to create a subprocess + */ + public String[] getCommandline() + { + return cmdl; + } + + /** + * Returns the environment used to create a subprocess. + * + * @return the environment used to create a subprocess + */ + public String[] getEnvironment() + { + if( env == null || newEnvironment ) + return env; + return patchEnvironment(); + } + + /** + * query the exit value of the process. + * + * @return the exit value, 1 if the process was killed, or Project.INVALID + * if no exit value has been received + */ + public int getExitValue() + { + return exitValue; + } + + /** + * Runs a process defined by the command line and returns its exit status. + * + * @return the exit status of the subprocess or INVALID + * @exception IOException Description of Exception + */ + public int execute() + throws IOException + { + CommandLauncher launcher = vmLauncher != null ? vmLauncher : shellLauncher; + if( !useVMLauncher ) + { + launcher = shellLauncher; + } + + final Process process = launcher.exec( project, getCommandline(), getEnvironment(), workingDirectory ); + try + { + streamHandler.setProcessInputStream( process.getOutputStream() ); + streamHandler.setProcessOutputStream( process.getInputStream() ); + streamHandler.setProcessErrorStream( process.getErrorStream() ); + } + catch( IOException e ) + { + process.destroy(); + throw e; + } + streamHandler.start(); + + // add the process to the list of those to destroy if the VM exits + // + processDestroyer.add( process ); + + if( watchdog != null ) + watchdog.start( process ); + waitFor( process ); + + // remove the process to the list of those to destroy if the VM exits + // + processDestroyer.remove( process ); + + if( watchdog != null ) + watchdog.stop(); + streamHandler.stop(); + if( watchdog != null ) + watchdog.checkException(); + return getExitValue(); + } + + /** + * test for an untimely death of the process + * + * @return true iff a watchdog had to kill the process + * @since 1.5 + */ + public boolean killedProcess() + { + return watchdog != null && watchdog.killedProcess(); + } + + protected void setExitValue( int value ) + { + exitValue = value; + } + + protected void waitFor( Process process ) + { + try + { + process.waitFor(); + setExitValue( process.exitValue() ); + } + catch( InterruptedException e ) + { + } + } + + /** + * Patch the current environment with the new values from the user. + * + * @return the patched environment + */ + private String[] patchEnvironment() + { + Vector osEnv = (Vector)getProcEnvironment().clone(); + for( int i = 0; i < env.length; i++ ) + { + int pos = env[ i ].indexOf( '=' ); + // Get key including "=" + String key = env[ i ].substring( 0, pos + 1 ); + int size = osEnv.size(); + for( int j = 0; j < size; j++ ) + { + if( ( (String)osEnv.elementAt( j ) ).startsWith( key ) ) + { + osEnv.removeElementAt( j ); + break; + } + } + osEnv.addElement( env[ i ] ); + } + String[] result = new String[ osEnv.size() ]; + osEnv.copyInto( result ); + return result; + } + + /** + * A command launcher for a particular JVM/OS platform. This class is a + * general purpose command launcher which can only launch commands in the + * current working directory. + * + * @author RT + */ + private static class CommandLauncher + { + /** + * Launches the given command in a new process. + * + * @param project The project that the command is part of + * @param cmd The command to execute + * @param env The environment for the new process. If null, the + * environment of the current proccess is used. + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env ) + throws IOException + { + if( project != null ) + { + project.log( "Execute:CommandLauncher: " + + Commandline.toString( cmd ), Project.MSG_DEBUG ); + } + return Runtime.getRuntime().exec( cmd, env ); + } + + /** + * Launches the given command in a new process, in the given working + * directory. + * + * @param project The project that the command is part of + * @param cmd The command to execute + * @param env The environment for the new process. If null, the + * environment of the current proccess is used. + * @param workingDir The directory to start the command in. If null, the + * current directory is used + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + throw new IOException( "Cannot execute a process in different directory under this JVM" ); + } + } + + /** + * A command launcher that proxies another command launcher. Sub-classes + * override exec(args, env, workdir) + * + * @author RT + */ + private static class CommandLauncherProxy extends CommandLauncher + { + + private CommandLauncher _launcher; + + CommandLauncherProxy( CommandLauncher launcher ) + { + _launcher = launcher; + } + + /** + * Launches the given command in a new process. Delegates this method to + * the proxied launcher + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env ) + throws IOException + { + return _launcher.exec( project, cmd, env ); + } + } + + /** + * A command launcher for JDK/JRE 1.1 under Windows. Fixes quoting problems + * in Runtime.exec(). Can only launch commands in the current working + * directory + * + * @author RT + */ + private static class Java11CommandLauncher extends CommandLauncher + { + /** + * Launches the given command in a new process. Needs to quote arguments + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env ) + throws IOException + { + // Need to quote arguments with spaces, and to escape quote characters + String[] newcmd = new String[ cmd.length ]; + for( int i = 0; i < cmd.length; i++ ) + { + newcmd[ i ] = Commandline.quoteArgument( cmd[ i ] ); + } + if( project != null ) + { + project.log( "Execute:Java11CommandLauncher: " + + Commandline.toString( newcmd ), Project.MSG_DEBUG ); + } + return Runtime.getRuntime().exec( newcmd, env ); + } + } + + /** + * A command launcher for JDK/JRE 1.3 (and higher). Uses the built-in + * Runtime.exec() command + * + * @author RT + */ + private static class Java13CommandLauncher extends CommandLauncher + { + + private Method _execWithCWD; + + public Java13CommandLauncher() + throws NoSuchMethodException + { + // Locate method Runtime.exec(String[] cmdarray, String[] envp, File dir) + _execWithCWD = Runtime.class.getMethod( "exec", new Class[]{String[].class, String[].class, File.class} ); + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + try + { + if( project != null ) + { + project.log( "Execute:Java13CommandLauncher: " + + Commandline.toString( cmd ), Project.MSG_DEBUG ); + } + Object[] arguments = {cmd, env, workingDir}; + return (Process)_execWithCWD.invoke( Runtime.getRuntime(), arguments ); + } + catch( InvocationTargetException exc ) + { + Throwable realexc = exc.getTargetException(); + if( realexc instanceof ThreadDeath ) + { + throw (ThreadDeath)realexc; + } + else if( realexc instanceof IOException ) + { + throw (IOException)realexc; + } + else + { + throw new BuildException( "Unable to execute command", realexc ); + } + } + catch( Exception exc ) + { + // IllegalAccess, IllegalArgument, ClassCast + throw new BuildException( "Unable to execute command", exc ); + } + } + } + + /** + * A command launcher for Mac that uses a dodgy mechanism to change working + * directory before launching commands. + * + * @author RT + */ + private static class MacCommandLauncher extends CommandLauncherProxy + { + MacCommandLauncher( CommandLauncher launcher ) + { + super( launcher ); + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + + System.getProperties().put( "user.dir", workingDir.getAbsolutePath() ); + try + { + return exec( project, cmd, env ); + } + finally + { + System.getProperties().put( "user.dir", antWorkingDirectory ); + } + } + } + + /** + * A command launcher that uses an auxiliary perl script to launch commands + * in directories other than the current working directory. + * + * @author RT + */ + private static class PerlScriptCommandLauncher extends CommandLauncherProxy + { + + private String _script; + + PerlScriptCommandLauncher( String script, CommandLauncher launcher ) + { + super( launcher ); + _script = script; + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( project == null ) + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + throw new IOException( "Cannot locate antRun script: No project provided" ); + } + + // Locate the auxiliary script + String antHome = project.getProperty( "ant.home" ); + if( antHome == null ) + { + throw new IOException( "Cannot locate antRun script: Property 'ant.home' not found" ); + } + String antRun = project.resolveFile( antHome + File.separator + _script ).toString(); + + // Build the command + File commandDir = workingDir; + if( workingDir == null && project != null ) + { + commandDir = project.getBaseDir(); + } + + String[] newcmd = new String[ cmd.length + 3 ]; + newcmd[ 0 ] = "perl"; + newcmd[ 1 ] = antRun; + newcmd[ 2 ] = commandDir.getAbsolutePath(); + System.arraycopy( cmd, 0, newcmd, 3, cmd.length ); + + return exec( project, newcmd, env ); + } + } + + /** + * A command launcher that uses an auxiliary script to launch commands in + * directories other than the current working directory. + * + * @author RT + */ + private static class ScriptCommandLauncher extends CommandLauncherProxy + { + + private String _script; + + ScriptCommandLauncher( String script, CommandLauncher launcher ) + { + super( launcher ); + _script = script; + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( project == null ) + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + throw new IOException( "Cannot locate antRun script: No project provided" ); + } + + // Locate the auxiliary script + String antHome = project.getProperty( "ant.home" ); + if( antHome == null ) + { + throw new IOException( "Cannot locate antRun script: Property 'ant.home' not found" ); + } + String antRun = project.resolveFile( antHome + File.separator + _script ).toString(); + + // Build the command + File commandDir = workingDir; + if( workingDir == null && project != null ) + { + commandDir = project.getBaseDir(); + } + + String[] newcmd = new String[ cmd.length + 2 ]; + newcmd[ 0 ] = antRun; + newcmd[ 1 ] = commandDir.getAbsolutePath(); + System.arraycopy( cmd, 0, newcmd, 2, cmd.length ); + + return exec( project, newcmd, env ); + } + } + + /** + * A command launcher for Windows 2000/NT that uses 'cmd.exe' when launching + * commands in directories other than the current working directory. + * + * @author RT + */ + private static class WinNTCommandLauncher extends CommandLauncherProxy + { + WinNTCommandLauncher( CommandLauncher launcher ) + { + super( launcher ); + } + + /** + * Launches the given command in a new process, in the given working + * directory. + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + File commandDir = workingDir; + if( workingDir == null ) + { + if( project != null ) + { + commandDir = project.getBaseDir(); + } + else + { + return exec( project, cmd, env ); + } + } + + // Use cmd.exe to change to the specified directory before running + // the command + final int preCmdLength = 6; + String[] newcmd = new String[ cmd.length + preCmdLength ]; + newcmd[ 0 ] = "cmd"; + newcmd[ 1 ] = "/c"; + newcmd[ 2 ] = "cd"; + newcmd[ 3 ] = "/d"; + newcmd[ 4 ] = commandDir.getAbsolutePath(); + newcmd[ 5 ] = "&&"; + System.arraycopy( cmd, 0, newcmd, preCmdLength, cmd.length ); + + return exec( project, newcmd, env ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteJava.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteJava.java new file mode 100644 index 000000000..6fc077991 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteJava.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Path; + +/* + * @author thomas.haas@softwired-inc.com + * @author Stefan Bodewig + */ +public class ExecuteJava +{ + + private Commandline javaCommand = null; + private Path classpath = null; + private CommandlineJava.SysProperties sysProperties = null; + + public void setClasspath( Path p ) + { + classpath = p; + } + + public void setJavaCommand( Commandline javaCommand ) + { + this.javaCommand = javaCommand; + } + + /** + * All output (System.out as well as System.err) will be written to this + * Stream. + * + * @param out The new Output value + * @deprecated manage output at the task level + */ + public void setOutput( PrintStream out ) { } + + public void setSystemProperties( CommandlineJava.SysProperties s ) + { + sysProperties = s; + } + + public void execute( Project project ) + throws BuildException + { + final String classname = javaCommand.getExecutable(); + final Object[] argument = {javaCommand.getArguments()}; + + AntClassLoader loader = null; + try + { + if( sysProperties != null ) + { + sysProperties.setSystem(); + } + + final Class[] param = {Class.forName( "[Ljava.lang.String;" )}; + Class target = null; + if( classpath == null ) + { + target = Class.forName( classname ); + } + else + { + loader = new AntClassLoader( project.getCoreLoader(), project, classpath, false ); + loader.setIsolated( true ); + loader.setThreadContextLoader(); + target = loader.forceLoadClass( classname ); + AntClassLoader.initializeClass( target ); + } + final Method main = target.getMethod( "main", param ); + main.invoke( null, argument ); + } + catch( NullPointerException e ) + { + throw new BuildException( "Could not find main() method in " + classname ); + } + catch( ClassNotFoundException e ) + { + throw new BuildException( "Could not find " + classname + ". Make sure you have it in your classpath" ); + } + catch( InvocationTargetException e ) + { + Throwable t = e.getTargetException(); + if( !( t instanceof SecurityException ) ) + { + throw new BuildException( t ); + } + else + { + throw ( SecurityException )t; + } + } + catch( Exception e ) + { + throw new BuildException( e ); + } + finally + { + if( loader != null ) + { + loader.resetThreadContextLoader(); + loader.cleanup(); + } + if( sysProperties != null ) + { + sysProperties.restoreSystem(); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteOn.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteOn.java new file mode 100644 index 000000000..0a8bd9d53 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteOn.java @@ -0,0 +1,473 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Executes a given command, supplying a set of files as arguments. + * + * @author Stefan Bodewig + * @author Mariusz Nowostawski + */ +public class ExecuteOn extends ExecTask +{ + + protected Vector filesets = new Vector(); + private boolean relative = false; + private boolean parallel = false; + protected String type = "file"; + protected Commandline.Marker srcFilePos = null; + private boolean skipEmpty = false; + protected Commandline.Marker targetFilePos = null; + protected Mapper mapperElement = null; + protected FileNameMapper mapper = null; + protected File destDir = null; + + /** + * Has <srcfile> been specified before <targetfile> + */ + protected boolean srcIsFirst = true; + + /** + * Set the destination directory. + * + * @param destDir The new Dest value + */ + public void setDest( File destDir ) + { + this.destDir = destDir; + } + + + /** + * Shall the command work on all specified files in parallel? + * + * @param parallel The new Parallel value + */ + public void setParallel( boolean parallel ) + { + this.parallel = parallel; + } + + /** + * Should filenames be returned as relative path names? + * + * @param relative The new Relative value + */ + public void setRelative( boolean relative ) + { + this.relative = relative; + } + + /** + * Should empty filesets be ignored? + * + * @param skip The new SkipEmptyFilesets value + */ + public void setSkipEmptyFilesets( boolean skip ) + { + skipEmpty = skip; + } + + /** + * Shall the command work only on files, directories or both? + * + * @param type The new Type value + */ + public void setType( FileDirBoth type ) + { + this.type = type.getValue(); + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapperElement != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapperElement = new Mapper( project ); + return mapperElement; + } + + /** + * Marker that indicates where the name of the source file should be put on + * the command line. + * + * @return Description of the Returned Value + */ + public Commandline.Marker createSrcfile() + { + if( srcFilePos != null ) + { + throw new BuildException( taskType + " doesn\'t support multiple srcfile elements.", + location ); + } + srcFilePos = cmdl.createMarker(); + return srcFilePos; + } + + /** + * Marker that indicates where the name of the target file should be put on + * the command line. + * + * @return Description of the Returned Value + */ + public Commandline.Marker createTargetfile() + { + if( targetFilePos != null ) + { + throw new BuildException( taskType + " doesn\'t support multiple targetfile elements.", + location ); + } + targetFilePos = cmdl.createMarker(); + srcIsFirst = ( srcFilePos != null ); + return targetFilePos; + } + + /** + * Construct the command line for parallel execution. + * + * @param srcFiles The filenames to add to the commandline + * @param baseDirs Description of Parameter + * @return The Commandline value + */ + protected String[] getCommandline( String[] srcFiles, File[] baseDirs ) + { + Vector targets = new Vector(); + if( targetFilePos != null ) + { + Hashtable addedFiles = new Hashtable(); + for( int i = 0; i < srcFiles.length; i++ ) + { + String[] subTargets = mapper.mapFileName( srcFiles[i] ); + if( subTargets != null ) + { + for( int j = 0; j < subTargets.length; j++ ) + { + String name = null; + if( !relative ) + { + name = + ( new File( destDir, subTargets[j] ) ).getAbsolutePath(); + } + else + { + name = subTargets[j]; + } + if( !addedFiles.contains( name ) ) + { + targets.addElement( name ); + addedFiles.put( name, name ); + } + } + } + } + } + String[] targetFiles = new String[targets.size()]; + targets.copyInto( targetFiles ); + + String[] orig = cmdl.getCommandline(); + String[] result = new String[orig.length + srcFiles.length + targetFiles.length]; + + int srcIndex = orig.length; + if( srcFilePos != null ) + { + srcIndex = srcFilePos.getPosition(); + } + + if( targetFilePos != null ) + { + int targetIndex = targetFilePos.getPosition(); + + if( srcIndex < targetIndex + || ( srcIndex == targetIndex && srcIsFirst ) ) + { + + // 0 --> srcIndex + System.arraycopy( orig, 0, result, 0, srcIndex ); + + // srcIndex --> targetIndex + System.arraycopy( orig, srcIndex, result, + srcIndex + srcFiles.length, + targetIndex - srcIndex ); + + // targets are already absolute file names + System.arraycopy( targetFiles, 0, result, + targetIndex + srcFiles.length, + targetFiles.length ); + + // targetIndex --> end + System.arraycopy( orig, targetIndex, result, + targetIndex + srcFiles.length + targetFiles.length, + orig.length - targetIndex ); + } + else + { + // 0 --> targetIndex + System.arraycopy( orig, 0, result, 0, targetIndex ); + + // targets are already absolute file names + System.arraycopy( targetFiles, 0, result, + targetIndex, + targetFiles.length ); + + // targetIndex --> srcIndex + System.arraycopy( orig, targetIndex, result, + targetIndex + targetFiles.length, + srcIndex - targetIndex ); + + // srcIndex --> end + System.arraycopy( orig, srcIndex, result, + srcIndex + srcFiles.length + targetFiles.length, + orig.length - srcIndex ); + srcIndex += targetFiles.length; + } + + } + else + {// no targetFilePos + + // 0 --> srcIndex + System.arraycopy( orig, 0, result, 0, srcIndex ); + // srcIndex --> end + System.arraycopy( orig, srcIndex, result, + srcIndex + srcFiles.length, + orig.length - srcIndex ); + + } + + // fill in source file names + for( int i = 0; i < srcFiles.length; i++ ) + { + if( !relative ) + { + result[srcIndex + i] = + ( new File( baseDirs[i], srcFiles[i] ) ).getAbsolutePath(); + } + else + { + result[srcIndex + i] = srcFiles[i]; + } + } + return result; + } + + /** + * Construct the command line for serial execution. + * + * @param srcFile The filename to add to the commandline + * @param baseDir filename is relative to this dir + * @return The Commandline value + */ + protected String[] getCommandline( String srcFile, File baseDir ) + { + return getCommandline( new String[]{srcFile}, new File[]{baseDir} ); + } + + /** + * Return the list of Directories from this DirectoryScanner that should be + * included on the command line. + * + * @param baseDir Description of Parameter + * @param ds Description of Parameter + * @return The Dirs value + */ + protected String[] getDirs( File baseDir, DirectoryScanner ds ) + { + if( mapper != null ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + return sfs.restrict( ds.getIncludedDirectories(), baseDir, destDir, + mapper ); + } + else + { + return ds.getIncludedDirectories(); + } + } + + /** + * Return the list of files from this DirectoryScanner that should be + * included on the command line. + * + * @param baseDir Description of Parameter + * @param ds Description of Parameter + * @return The Files value + */ + protected String[] getFiles( File baseDir, DirectoryScanner ds ) + { + if( mapper != null ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + return sfs.restrict( ds.getIncludedFiles(), baseDir, destDir, + mapper ); + } + else + { + return ds.getIncludedFiles(); + } + } + + protected void checkConfiguration() + { + if( "execon".equals( taskName ) ) + { + log( "!! execon is deprecated. Use apply instead. !!" ); + } + + super.checkConfiguration(); + if( filesets.size() == 0 ) + { + throw new BuildException( "no filesets specified", location ); + } + + if( targetFilePos != null || mapperElement != null + || destDir != null ) + { + + if( mapperElement == null ) + { + throw new BuildException( "no mapper specified", location ); + } + if( mapperElement == null ) + { + throw new BuildException( "no dest attribute specified", + location ); + } + mapper = mapperElement.getImplementation(); + } + } + + protected void runExec( Execute exe ) + throws BuildException + { + try + { + + Vector fileNames = new Vector(); + Vector baseDirs = new Vector(); + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + File base = fs.getDir( project ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + + if( !"dir".equals( type ) ) + { + String[] s = getFiles( base, ds ); + for( int j = 0; j < s.length; j++ ) + { + fileNames.addElement( s[j] ); + baseDirs.addElement( base ); + } + } + + if( !"file".equals( type ) ) + { + String[] s = getDirs( base, ds ); + ; + for( int j = 0; j < s.length; j++ ) + { + fileNames.addElement( s[j] ); + baseDirs.addElement( base ); + } + } + + if( fileNames.size() == 0 && skipEmpty ) + { + log( "Skipping fileset for directory " + + base + ". It is empty.", Project.MSG_INFO ); + continue; + } + + if( !parallel ) + { + String[] s = new String[fileNames.size()]; + fileNames.copyInto( s ); + for( int j = 0; j < s.length; j++ ) + { + String[] command = getCommandline( s[j], base ); + log( "Executing " + Commandline.toString( command ), + Project.MSG_VERBOSE ); + exe.setCommandline( command ); + runExecute( exe ); + } + fileNames.removeAllElements(); + baseDirs.removeAllElements(); + } + } + + if( parallel && ( fileNames.size() > 0 || !skipEmpty ) ) + { + String[] s = new String[fileNames.size()]; + fileNames.copyInto( s ); + File[] b = new File[baseDirs.size()]; + baseDirs.copyInto( b ); + String[] command = getCommandline( s, b ); + log( "Executing " + Commandline.toString( command ), + Project.MSG_VERBOSE ); + exe.setCommandline( command ); + runExecute( exe ); + } + + } + catch( IOException e ) + { + throw new BuildException( "Execute failed: " + e, e, location ); + } + finally + { + // close the output file if required + logFlush(); + } + } + + /** + * Enumerated attribute with the values "file", "dir" and "both" for the + * type attribute. + * + * @author RT + */ + public static class FileDirBoth extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"file", "dir", "both"}; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java new file mode 100644 index 000000000..f9f64cca6 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Used by Execute to handle input and output stream of + * subprocesses. + * + * @author thomas.haas@softwired-inc.com + */ +public interface ExecuteStreamHandler +{ + + /** + * Install a handler for the input stream of the subprocess. + * + * @param os output stream to write to the standard input stream of the + * subprocess + * @exception IOException Description of Exception + */ + void setProcessInputStream( OutputStream os ) + throws IOException; + + /** + * Install a handler for the error stream of the subprocess. + * + * @param is input stream to read from the error stream from the subprocess + * @exception IOException Description of Exception + */ + void setProcessErrorStream( InputStream is ) + throws IOException; + + /** + * Install a handler for the output stream of the subprocess. + * + * @param is input stream to read from the error stream from the subprocess + * @exception IOException Description of Exception + */ + void setProcessOutputStream( InputStream is ) + throws IOException; + + /** + * Start handling of the streams. + * + * @exception IOException Description of Exception + */ + void start() + throws IOException; + + /** + * Stop handling of the streams - will not be restarted. + */ + void stop(); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java new file mode 100644 index 000000000..318e622aa --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; + +/** + * Destroys a process running for too long. For example:

      + * ExecuteWatchdog watchdog = new ExecuteWatchdog(30000);
      + * Execute exec = new Execute(myloghandler, watchdog);
      + * exec.setCommandLine(mycmdline);
      + * int exitvalue = exec.execute();
      + * if (exitvalue != SUCCESS && watchdog.killedProcess()){
      + *              // it was killed on purpose by the watchdog
      + * }
      + * 
      + * + * @author thomas.haas@softwired-inc.com + * @author Stephane Bailliez + * @see Execute + */ +public class ExecuteWatchdog implements Runnable +{ + + /** + * say whether or not the watchog is currently monitoring a process + */ + private boolean watch = false; + + /** + * exception that might be thrown during the process execution + */ + private Exception caught = null; + + /** + * say whether or not the process was killed due to running overtime + */ + private boolean killedProcess = false; + + /** + * the process to execute and watch for duration + */ + private Process process; + + /** + * timeout duration. Once the process running time exceeds this it should be + * killed + */ + private int timeout; + + /** + * Creates a new watchdog with a given timeout. + * + * @param timeout the timeout for the process in milliseconds. It must be + * greather than 0. + */ + public ExecuteWatchdog( int timeout ) + { + if( timeout < 1 ) + { + throw new IllegalArgumentException( "timeout lesser than 1." ); + } + this.timeout = timeout; + } + + /** + * Indicates whether or not the watchdog is still monitoring the process. + * + * @return true if the process is still running, otherwise + * false . + */ + public boolean isWatching() + { + return watch; + } + + /** + * This method will rethrow the exception that was possibly caught during + * the run of the process. It will only remains valid once the process has + * been terminated either by 'error', timeout or manual intervention. + * Information will be discarded once a new process is ran. + * + * @throws BuildException a wrapped exception over the one that was silently + * swallowed and stored during the process run. + */ + public void checkException() + throws BuildException + { + if( caught != null ) + { + throw new BuildException( "Exception in ExecuteWatchdog.run: " + + caught.getMessage(), caught ); + } + } + + /** + * Indicates whether the last process run was killed on timeout or not. + * + * @return true if the process was killed otherwise false + * . + */ + public boolean killedProcess() + { + return killedProcess; + } + + + /** + * Watches the process and terminates it, if it runs for to long. + */ + public synchronized void run() + { + try + { + // This isn't a Task, don't have a Project object to log. + // project.log("ExecuteWatchdog: timeout = "+timeout+" msec", Project.MSG_VERBOSE); + final long until = System.currentTimeMillis() + timeout; + long now; + while( watch && until > ( now = System.currentTimeMillis() ) ) + { + try + { + wait( until - now ); + } + catch( InterruptedException e ) + {} + } + + // if we are here, either someone stopped the watchdog, + // we are on timeout and the process must be killed, or + // we are on timeout and the process has already stopped. + try + { + // We must check if the process was not stopped + // before being here + process.exitValue(); + } + catch( IllegalThreadStateException e ) + { + // the process is not terminated, if this is really + // a timeout and not a manual stop then kill it. + if( watch ) + { + killedProcess = true; + process.destroy(); + } + } + } + catch( Exception e ) + { + caught = e; + } + finally + { + cleanUp(); + } + } + + /** + * Watches the given process and terminates it, if it runs for too long. All + * information from the previous run are reset. + * + * @param process the process to monitor. It cannot be null + * @throws IllegalStateException thrown if a process is still being + * monitored. + */ + public synchronized void start( Process process ) + { + if( process == null ) + { + throw new NullPointerException( "process is null." ); + } + if( this.process != null ) + { + throw new IllegalStateException( "Already running." ); + } + this.caught = null; + this.killedProcess = false; + this.watch = true; + this.process = process; + final Thread thread = new Thread( this, "WATCHDOG" ); + thread.setDaemon( true ); + thread.start(); + } + + /** + * Stops the watcher. It will notify all threads possibly waiting on this + * object. + */ + public synchronized void stop() + { + watch = false; + notifyAll(); + } + + /** + * reset the monitor flag and the process. + */ + protected void cleanUp() + { + watch = false; + process = null; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exit.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exit.java new file mode 100644 index 000000000..60ae24009 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Exit.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; + +/** + * Just exit the active build, giving an additional message if available. + * + * @author Nico Seessle + */ +public class Exit extends Task +{ + private String ifCondition, unlessCondition; + private String message; + + public void setIf( String c ) + { + ifCondition = c; + } + + public void setMessage( String value ) + { + this.message = value; + } + + public void setUnless( String c ) + { + unlessCondition = c; + } + + /** + * Set a multiline message. + * + * @param msg The feature to be added to the Text attribute + */ + public void addText( String msg ) + { + message += project.replaceProperties( msg ); + } + + public void execute() + throws BuildException + { + if( testIfCondition() && testUnlessCondition() ) + { + if( message != null && message.length() > 0 ) + { + throw new BuildException( message ); + } + else + { + throw new BuildException( "No message" ); + } + } + } + + private boolean testIfCondition() + { + if( ifCondition == null || "".equals( ifCondition ) ) + { + return true; + } + + return project.getProperty( ifCondition ) != null; + } + + private boolean testUnlessCondition() + { + if( unlessCondition == null || "".equals( unlessCondition ) ) + { + return true; + } + return project.getProperty( unlessCondition ) == null; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Expand.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Expand.java new file mode 100644 index 000000000..38ebc9404 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Expand.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; +import org.apache.tools.ant.util.FileUtils; + +/** + * Unzip a file. + * + * @author costin@dnt.ro + * @author Stefan Bodewig + * @author Magesh Umasankar + */ +public class Expand extends MatchingTask +{// req + private boolean overwrite = true; + private Vector patternsets = new Vector(); + private Vector filesets = new Vector(); + private File dest;//req + private File source; + + /** + * Set the destination directory. File will be unzipped into the destination + * directory. + * + * @param d Path to the directory. + */ + public void setDest( File d ) + { + this.dest = d; + } + + /** + * Should we overwrite files in dest, even if they are newer than the + * corresponding entries in the archive? + * + * @param b The new Overwrite value + */ + public void setOverwrite( boolean b ) + { + overwrite = b; + } + + /** + * Set the path to zip-file. + * + * @param s Path to zip-file. + */ + public void setSrc( File s ) + { + this.source = s; + } + + /** + * Add a fileset + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Add a patternset + * + * @param set The feature to be added to the Patternset attribute + */ + public void addPatternset( PatternSet set ) + { + patternsets.addElement( set ); + } + + /** + * Do the work. + * + * @exception BuildException Thrown in unrecoverable error. + */ + public void execute() + throws BuildException + { + if( "expand".equals( taskType ) ) + { + log( "!! expand is deprecated. Use unzip instead. !!" ); + } + + if( source == null && filesets.size() == 0 ) + { + throw new BuildException( "src attribute and/or filesets must be specified" ); + } + + if( dest == null ) + { + throw new BuildException( + "Dest attribute must be specified" ); + } + + if( dest.exists() && !dest.isDirectory() ) + { + throw new BuildException( "Dest must be a directory.", location ); + } + + FileUtils fileUtils = FileUtils.newFileUtils(); + + if( source != null ) + { + if( source.isDirectory() ) + { + throw new BuildException( "Src must not be a directory." + + " Use nested filesets instead.", location ); + } + else + { + expandFile( fileUtils, source, dest ); + } + } + if( filesets.size() > 0 ) + { + for( int j = 0; j < filesets.size(); j++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( j ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File fromDir = fs.getDir( project ); + + String[] files = ds.getIncludedFiles(); + for( int i = 0; i < files.length; ++i ) + { + File file = new File( fromDir, files[i] ); + expandFile( fileUtils, file, dest ); + } + } + } + } + + /* + * This method is to be overridden by extending unarchival tasks. + */ + protected void expandFile( FileUtils fileUtils, File srcF, File dir ) + { + ZipInputStream zis = null; + try + { + // code from WarExpand + zis = new ZipInputStream( new FileInputStream( srcF ) ); + ZipEntry ze = null; + + while( ( ze = zis.getNextEntry() ) != null ) + { + extractFile( fileUtils, srcF, dir, zis, + ze.getName(), + new Date( ze.getTime() ), + ze.isDirectory() ); + } + + log( "expand complete", Project.MSG_VERBOSE ); + } + catch( IOException ioe ) + { + throw new BuildException( "Error while expanding " + srcF.getPath(), ioe ); + } + finally + { + if( zis != null ) + { + try + { + zis.close(); + } + catch( IOException e ) + {} + } + } + } + + protected void extractFile( FileUtils fileUtils, File srcF, File dir, + InputStream compressedInputStream, + String entryName, + Date entryDate, boolean isDirectory ) + throws IOException + { + + if( patternsets != null && patternsets.size() > 0 ) + { + String name = entryName; + boolean included = false; + for( int v = 0; v < patternsets.size(); v++ ) + { + PatternSet p = ( PatternSet )patternsets.elementAt( v ); + String[] incls = p.getIncludePatterns( project ); + if( incls != null ) + { + for( int w = 0; w < incls.length; w++ ) + { + boolean isIncl = DirectoryScanner.match( incls[w], name ); + if( isIncl ) + { + included = true; + break; + } + } + } + String[] excls = p.getExcludePatterns( project ); + if( excls != null ) + { + for( int w = 0; w < excls.length; w++ ) + { + boolean isExcl = DirectoryScanner.match( excls[w], name ); + if( isExcl ) + { + included = false; + break; + } + } + } + } + if( !included ) + { + //Do not process this file + return; + } + } + + File f = fileUtils.resolveFile( dir, entryName ); + try + { + if( !overwrite && f.exists() + && f.lastModified() >= entryDate.getTime() ) + { + log( "Skipping " + f + " as it is up-to-date", + Project.MSG_DEBUG ); + return; + } + + log( "expanding " + entryName + " to " + f, + Project.MSG_VERBOSE ); + // create intermediary directories - sometimes zip don't add them + File dirF = fileUtils.getParentFile( f ); + dirF.mkdirs(); + + if( isDirectory ) + { + f.mkdirs(); + } + else + { + byte[] buffer = new byte[1024]; + int length = 0; + FileOutputStream fos = null; + try + { + fos = new FileOutputStream( f ); + + while( ( length = + compressedInputStream.read( buffer ) ) >= 0 ) + { + fos.write( buffer, 0, length ); + } + + fos.close(); + fos = null; + } + finally + { + if( fos != null ) + { + try + { + fos.close(); + } + catch( IOException e ) + {} + } + } + } + + fileUtils.setFileLastModified( f, entryDate.getTime() ); + } + catch( FileNotFoundException ex ) + { + log( "Unable to expand to file " + f.getPath(), Project.MSG_WARN ); + } + + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Filter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Filter.java new file mode 100644 index 000000000..6970d0ab4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Filter.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * This task sets a token filter that is used by the file copy methods of the + * project to do token substitution, or sets mutiple tokens by reading these + * from a file. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Gero Vermaas gero@xs4all.nl + * @author Michael McCallum + */ +public class Filter extends Task +{ + private File filtersFile; + + private String token; + private String value; + + public void setFiltersfile( File filtersFile ) + { + this.filtersFile = filtersFile; + } + + public void setToken( String token ) + { + this.token = token; + } + + public void setValue( String value ) + { + this.value = value; + } + + public void execute() + throws BuildException + { + boolean isFiltersFromFile = filtersFile != null && token == null && value == null; + boolean isSingleFilter = filtersFile == null && token != null && value != null; + + if( !isFiltersFromFile && !isSingleFilter ) + { + throw new BuildException( "both token and value parameters, or only a filtersFile parameter is required", location ); + } + + if( isSingleFilter ) + { + project.getGlobalFilterSet().addFilter( token, value ); + } + + if( isFiltersFromFile ) + { + readFilters(); + } + } + + protected void readFilters() + throws BuildException + { + log( "Reading filters from " + filtersFile, Project.MSG_VERBOSE ); + project.getGlobalFilterSet().readFiltersFromFile( filtersFile ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/FixCRLF.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/FixCRLF.java new file mode 100644 index 000000000..2cd21df0a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/FixCRLF.java @@ -0,0 +1,1159 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Enumeration; +import java.util.NoSuchElementException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.util.FileUtils; + +/** + * Task to convert text source files to local OS formatting conventions, as well + * as repair text files damaged by misconfigured or misguided editors or file + * transfer programs.

      + * + * This task can take the following arguments: + *

        + *
      • srcdir + *
      • destdir + *
      • include + *
      • exclude + *
      • cr + *
      • eol + *
      • tab + *
      • eof + *
      • encoding + *
      + * Of these arguments, only sourcedir is required.

      + * + * When this task executes, it will scan the srcdir based on the include and + * exclude properties.

      + * + * This version generalises the handling of EOL characters, and allows for + * CR-only line endings (which I suspect is the standard on Macs.) Tab handling + * has also been generalised to accommodate any tabwidth from 2 to 80, + * inclusive. Importantly, it will leave untouched any literal TAB characters + * embedded within string or character constants.

      + * + * Warning: do not run on binary files. Caution: run with care + * on carefully formatted files. This may sound obvious, but if you don't + * specify asis, presume that your files are going to be modified. If "tabs" is + * "add" or "remove", whitespace characters may be added or removed as + * necessary. Similarly, for CR's - in fact "eol"="crlf" or cr="add" can result + * in cr characters being removed in one special case accommodated, i.e., CRCRLF + * is regarded as a single EOL to handle cases where other programs have + * converted CRLF into CRCRLF. + * + * @author Sam Ruby rubys@us.ibm.com + * @author Peter B. West + * @version $Revision$ $Name$ + */ + +public class FixCRLF extends MatchingTask +{ + + private final static int UNDEF = -1; + private final static int NOTJAVA = 0; + private final static int LOOKING = 1; + private final static int IN_CHAR_CONST = 2; + private final static int IN_STR_CONST = 3; + private final static int IN_SINGLE_COMMENT = 4; + private final static int IN_MULTI_COMMENT = 5; + + private final static int ASIS = 0; + private final static int CR = 1; + private final static int LF = 2; + private final static int CRLF = 3; + private final static int ADD = 1; + private final static int REMOVE = -1; + private final static int SPACES = -1; + private final static int TABS = 1; + + private final static int INBUFLEN = 8192; + private final static int LINEBUFLEN = 200; + + private final static char CTRLZ = '\u001A'; + + private int tablength = 8; + private String spaces = " "; + private StringBuffer linebuf = new StringBuffer( 1024 ); + private StringBuffer linebuf2 = new StringBuffer( 1024 ); + private boolean javafiles = false; + private File destDir = null; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + + /** + * Encoding to assume for the files + */ + private String encoding = null; + private int ctrlz; + private int eol; + private String eolstr; + + private File srcDir; + private int tabs; + + /** + * Defaults the properties based on the system type. + *

        + *
      • Unix: eol="LF" tab="asis" eof="remove" + *
      • Mac: eol="CR" tab="asis" eof="remove" + *
      • DOS: eol="CRLF" tab="asis" eof="asis" + *
      + * + */ + public FixCRLF() + { + tabs = ASIS; + if( System.getProperty( "path.separator" ).equals( ":" ) ) + { + ctrlz = REMOVE; + if( System.getProperty( "os.name" ).indexOf( "Mac" ) > -1 ) + { + eol = CR; + eolstr = "\r"; + } + else + { + eol = LF; + eolstr = "\n"; + } + } + else + { + ctrlz = ASIS; + eol = CRLF; + eolstr = "\r\n"; + } + } + + /** + * Specify how carriage return (CR) characters are to be handled + * + * @param attr The new Cr value + * @deprecated use {@link #setEol setEol} instead. + */ + public void setCr( AddAsisRemove attr ) + { + log( "DEPRECATED: The cr attribute has been deprecated,", + Project.MSG_WARN ); + log( "Please us the eol attribute instead", Project.MSG_WARN ); + String option = attr.getValue(); + CrLf c = new CrLf(); + if( option.equals( "remove" ) ) + { + c.setValue( "lf" ); + } + else if( option.equals( "asis" ) ) + { + c.setValue( "asis" ); + } + else + { + // must be "add" + c.setValue( "crlf" ); + } + setEol( c ); + } + + /** + * Set the destination where the fixed files should be placed. Default is to + * replace the original file. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Specifies the encoding Ant expects the files to be in - defaults to the + * platforms default encoding. + * + * @param encoding The new Encoding value + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Specify how DOS EOF (control-z) charaters are to be handled + * + * @param attr The new Eof value + */ + public void setEof( AddAsisRemove attr ) + { + String option = attr.getValue(); + if( option.equals( "remove" ) ) + { + ctrlz = REMOVE; + } + else if( option.equals( "asis" ) ) + { + ctrlz = ASIS; + } + else + { + // must be "add" + ctrlz = ADD; + } + } + + + /** + * Specify how EndOfLine characters are to be handled + * + * @param attr The new Eol value + */ + public void setEol( CrLf attr ) + { + String option = attr.getValue(); + if( option.equals( "asis" ) ) + { + eol = ASIS; + } + else if( option.equals( "cr" ) ) + { + eol = CR; + eolstr = "\r"; + } + else if( option.equals( "lf" ) ) + { + eol = LF; + eolstr = "\n"; + } + else + { + // Must be "crlf" + eol = CRLF; + eolstr = "\r\n"; + } + } + + /** + * Fixing Java source files? + * + * @param javafiles The new Javafiles value + */ + public void setJavafiles( boolean javafiles ) + { + this.javafiles = javafiles; + } + + /** + * Set the source dir to find the source text files. + * + * @param srcDir The new Srcdir value + */ + public void setSrcdir( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * Specify how tab characters are to be handled + * + * @param attr The new Tab value + */ + public void setTab( AddAsisRemove attr ) + { + String option = attr.getValue(); + if( option.equals( "remove" ) ) + { + tabs = SPACES; + } + else if( option.equals( "asis" ) ) + { + tabs = ASIS; + } + else + { + // must be "add" + tabs = TABS; + } + } + + /** + * Specify tab length in characters + * + * @param tlength specify the length of tab in spaces, + * @exception BuildException Description of Exception + */ + public void setTablength( int tlength ) + throws BuildException + { + if( tlength < 2 || tlength > 80 ) + { + throw new BuildException( "tablength must be between 2 and 80", + location ); + } + tablength = tlength; + StringBuffer sp = new StringBuffer(); + for( int i = 0; i < tablength; i++ ) + { + sp.append( ' ' ); + } + spaces = sp.toString(); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir and destdir + + if( srcDir == null ) + { + throw new BuildException( "srcdir attribute must be set!" ); + } + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir does not exist!" ); + } + if( !srcDir.isDirectory() ) + { + throw new BuildException( "srcdir is not a directory!" ); + } + if( destDir != null ) + { + if( !destDir.exists() ) + { + throw new BuildException( "destdir does not exist!" ); + } + if( !destDir.isDirectory() ) + { + throw new BuildException( "destdir is not a directory!" ); + } + } + + // log options used + log( "options:" + + " eol=" + + ( eol == ASIS ? "asis" : eol == CR ? "cr" : eol == LF ? "lf" : "crlf" ) + + " tab=" + ( tabs == TABS ? "add" : tabs == ASIS ? "asis" : "remove" ) + + " eof=" + ( ctrlz == ADD ? "add" : ctrlz == ASIS ? "asis" : "remove" ) + + " tablength=" + tablength + + " encoding=" + ( encoding == null ? "default" : encoding ), + Project.MSG_VERBOSE ); + + DirectoryScanner ds = super.getDirectoryScanner( srcDir ); + String[] files = ds.getIncludedFiles(); + + for( int i = 0; i < files.length; i++ ) + { + processFile( files[i] ); + } + } + + /** + * Creates a Reader reading from a given file an taking the user defined + * encoding into account. + * + * @param f Description of Parameter + * @return The Reader value + * @exception IOException Description of Exception + */ + private Reader getReader( File f ) + throws IOException + { + return ( encoding == null ) ? new FileReader( f ) + : new InputStreamReader( new FileInputStream( f ), encoding ); + } + + + /** + * Scan a BufferLine forward from the 'next' pointer for the end of a + * character constant. Set 'lookahead' pointer to the character following + * the terminating quote. + * + * @param bufline Description of Parameter + * @param terminator Description of Parameter + * @exception BuildException Description of Exception + */ + private void endOfCharConst( OneLiner.BufferLine bufline, char terminator ) + throws BuildException + { + int ptr = bufline.getNext(); + int eol = bufline.length(); + char c; + ptr++;// skip past initial quote + while( ptr < eol ) + { + if( ( c = bufline.getChar( ptr++ ) ) == '\\' ) + { + ptr++; + } + else + { + if( c == terminator ) + { + bufline.setLookahead( ptr ); + return; + } + } + }// end of while (ptr < eol) + // Must have fallen through to the end of the line + throw new BuildException( "endOfCharConst: unterminated char constant" ); + } + + /** + * Scan a BufferLine for the next state changing token: the beginning of a + * single or multi-line comment, a character or a string constant. As a + * side-effect, sets the buffer state to the next state, and sets field + * lookahead to the first character of the state-changing token, or to the + * next eol character. + * + * @param bufline Description of Parameter + * @exception BuildException Description of Exception + */ + private void nextStateChange( OneLiner.BufferLine bufline ) + throws BuildException + { + int eol = bufline.length(); + int ptr = bufline.getNext(); + + // Look for next single or double quote, double slash or slash star + while( ptr < eol ) + { + switch ( bufline.getChar( ptr++ ) ) + { + case '\'': + bufline.setState( IN_CHAR_CONST ); + bufline.setLookahead( --ptr ); + return; + case '\"': + bufline.setState( IN_STR_CONST ); + bufline.setLookahead( --ptr ); + return; + case '/': + if( ptr < eol ) + { + if( bufline.getChar( ptr ) == '*' ) + { + bufline.setState( IN_MULTI_COMMENT ); + bufline.setLookahead( --ptr ); + return; + } + else if( bufline.getChar( ptr ) == '/' ) + { + bufline.setState( IN_SINGLE_COMMENT ); + bufline.setLookahead( --ptr ); + return; + } + } + break; + }// end of switch (bufline.getChar(ptr++)) + + }// end of while (ptr < eol) + // Eol is the next token + bufline.setLookahead( ptr ); + } + + + /** + * Process a BufferLine string which is not part of of a string constant. + * The start position of the string is given by the 'next' field. Sets the + * 'next' and 'column' fields in the BufferLine. + * + * @param bufline Description of Parameter + * @param end Description of Parameter + * @param outWriter Description of Parameter + */ + private void notInConstant( OneLiner.BufferLine bufline, int end, + BufferedWriter outWriter ) + { + // N.B. both column and string index are zero-based + // Process a string not part of a constant; + // i.e. convert tabs<->spaces as required + // This is NOT called for ASIS tab handling + int nextTab; + int nextStop; + int tabspaces; + String line = bufline.substring( bufline.getNext(), end ); + int place = 0;// Zero-based + int col = bufline.getColumn();// Zero-based + + // process sequences of white space + // first convert all tabs to spaces + linebuf.setLength( 0 ); + while( ( nextTab = line.indexOf( ( int )'\t', place ) ) >= 0 ) + { + linebuf.append( line.substring( place, nextTab ) );// copy to the TAB + col += nextTab - place; + tabspaces = tablength - ( col % tablength ); + linebuf.append( spaces.substring( 0, tabspaces ) ); + col += tabspaces; + place = nextTab + 1; + }// end of while + linebuf.append( line.substring( place, line.length() ) ); + // if converting to spaces, all finished + String linestring = new String( linebuf.toString() ); + if( tabs == REMOVE ) + { + try + { + outWriter.write( linestring ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + } + else + {// tabs == ADD + int tabCol; + linebuf2.setLength( 0 ); + place = 0; + col = bufline.getColumn(); + int placediff = col - 0; + // for the length of the string, cycle through the tab stop + // positions, checking for a space preceded by at least one + // other space at the tab stop. if so replace the longest possible + // preceding sequence of spaces with a tab. + nextStop = col + ( tablength - col % tablength ); + if( nextStop - col < 2 ) + { + linebuf2.append( linestring.substring( + place, nextStop - placediff ) ); + place = nextStop - placediff; + nextStop += tablength; + } + + for( ; nextStop - placediff <= linestring.length() + ; nextStop += tablength ) + { + for( tabCol = nextStop; + --tabCol - placediff >= place + && linestring.charAt( tabCol - placediff ) == ' ' + ; ) + { + ;// Loop for the side-effects + } + // tabCol is column index of the last non-space character + // before the next tab stop + if( nextStop - tabCol > 2 ) + { + linebuf2.append( linestring.substring( + place, ++tabCol - placediff ) ); + linebuf2.append( '\t' ); + } + else + { + linebuf2.append( linestring.substring( + place, nextStop - placediff ) ); + }// end of else + + place = nextStop - placediff; + }// end of for (nextStop ... ) + + // pick up that last bit, if any + linebuf2.append( linestring.substring( place, linestring.length() ) ); + + try + { + outWriter.write( linebuf2.toString() ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + + }// end of else tabs == ADD + + // Set column position as modified by this method + bufline.setColumn( bufline.getColumn() + linestring.length() ); + bufline.setNext( end ); + + } + + + private void processFile( String file ) + throws BuildException + { + File srcFile = new File( srcDir, file ); + File destD = destDir == null ? srcDir : destDir; + File tmpFile = null; + BufferedWriter outWriter; + OneLiner.BufferLine line; + + // read the contents of the file + OneLiner lines = new OneLiner( srcFile ); + + try + { + // Set up the output Writer + try + { + tmpFile = fileUtils.createTempFile( "fixcrlf", "", destD ); + Writer writer = ( encoding == null ) ? new FileWriter( tmpFile ) + : new OutputStreamWriter( new FileOutputStream( tmpFile ), encoding ); + outWriter = new BufferedWriter( writer ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + while( lines.hasMoreElements() ) + { + // In-line states + int endComment; + + try + { + line = ( OneLiner.BufferLine )lines.nextElement(); + } + catch( NoSuchElementException e ) + { + throw new BuildException( e ); + } + + String lineString = line.getLineString(); + int linelen = line.length(); + + // Note - all of the following processing NOT done for + // tabs ASIS + + if( tabs == ASIS ) + { + // Just copy the body of the line across + try + { + outWriter.write( lineString ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + + } + else + {// (tabs != ASIS) + int ptr; + + while( ( ptr = line.getNext() ) < linelen ) + { + + switch ( lines.getState() ) + { + + case NOTJAVA: + notInConstant( line, line.length(), outWriter ); + break; + case IN_MULTI_COMMENT: + if( ( endComment = + lineString.indexOf( "*/", line.getNext() ) + ) >= 0 ) + { + // End of multiLineComment on this line + endComment += 2;// Include the end token + lines.setState( LOOKING ); + } + else + { + endComment = linelen; + } + + notInConstant( line, endComment, outWriter ); + break; + case IN_SINGLE_COMMENT: + notInConstant( line, line.length(), outWriter ); + lines.setState( LOOKING ); + break; + case IN_CHAR_CONST: + case IN_STR_CONST: + // Got here from LOOKING by finding an opening "\'" + // next points to that quote character. + // Find the end of the constant. Watch out for + // backslashes. Literal tabs are left unchanged, and + // the column is adjusted accordingly. + + int begin = line.getNext(); + char terminator = ( lines.getState() == IN_STR_CONST + ? '\"' + : '\'' ); + endOfCharConst( line, terminator ); + while( line.getNext() < line.getLookahead() ) + { + if( line.getNextCharInc() == '\t' ) + { + line.setColumn( + line.getColumn() + + tablength - + line.getColumn() % tablength ); + } + else + { + line.incColumn(); + } + } + + // Now output the substring + try + { + outWriter.write( line.substring( begin, line.getNext() ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + lines.setState( LOOKING ); + + break; + + case LOOKING: + nextStateChange( line ); + notInConstant( line, line.getLookahead(), outWriter ); + break; + }// end of switch (state) + + }// end of while (line.getNext() < linelen) + + }// end of else (tabs != ASIS) + + try + { + outWriter.write( eolstr ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + + }// end of while (lines.hasNext()) + + try + { + // Handle CTRLZ + if( ctrlz == ASIS ) + { + outWriter.write( lines.getEofStr() ); + } + else if( ctrlz == ADD ) + { + outWriter.write( CTRLZ ); + } + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + try + { + outWriter.close(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + File destFile = new File( destD, file ); + + try + { + lines.close(); + lines = null; + } + catch( IOException e ) + { + throw new BuildException( "Unable to close source file " + srcFile ); + } + + if( destFile.exists() ) + { + // Compare the destination with the temp file + log( "destFile exists", Project.MSG_DEBUG ); + if( !fileUtils.contentEquals( destFile, tmpFile ) ) + { + log( destFile + " is being written", Project.MSG_DEBUG ); + if( !destFile.delete() ) + { + throw new BuildException( "Unable to delete " + + destFile ); + } + if( !tmpFile.renameTo( destFile ) ) + { + throw new BuildException( + "Failed to transform " + srcFile + + " to " + destFile + + ". Couldn't rename temporary file: " + + tmpFile ); + } + + } + else + {// destination is equal to temp file + log( destFile + + " is not written, as the contents are identical", + Project.MSG_DEBUG ); + if( !tmpFile.delete() ) + { + throw new BuildException( "Unable to delete " + + tmpFile ); + } + } + } + else + {// destFile does not exist - write the temp file + log( "destFile does not exist", Project.MSG_DEBUG ); + if( !tmpFile.renameTo( destFile ) ) + { + throw new BuildException( + "Failed to transform " + srcFile + + " to " + destFile + + ". Couldn't rename temporary file: " + + tmpFile ); + } + } + + tmpFile = null; + + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + try + { + if( lines != null ) + { + lines.close(); + } + } + catch( IOException io ) + { + log( "Error closing " + srcFile, Project.MSG_ERR ); + }// end of catch + + if( tmpFile != null ) + { + tmpFile.delete(); + } + }// end of finally + } + + /** + * Enumerated attribute with the values "asis", "add" and "remove". + * + * @author RT + */ + public static class AddAsisRemove extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"add", "asis", "remove"}; + } + } + + /** + * Enumerated attribute with the values "asis", "cr", "lf" and "crlf". + * + * @author RT + */ + public static class CrLf extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"asis", "cr", "lf", "crlf"}; + } + } + + + class OneLiner implements Enumeration + { + + private int state = javafiles ? LOOKING : NOTJAVA; + + private StringBuffer eolStr = new StringBuffer( LINEBUFLEN ); + private StringBuffer eofStr = new StringBuffer(); + private StringBuffer line = new StringBuffer(); + private boolean reachedEof = false; + + private BufferedReader reader; + + public OneLiner( File srcFile ) + throws BuildException + { + try + { + reader = new BufferedReader + ( getReader( srcFile ), INBUFLEN ); + nextLine(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + public void setState( int state ) + { + this.state = state; + } + + public String getEofStr() + { + return eofStr.toString(); + } + + public int getState() + { + return state; + } + + public void close() + throws IOException + { + if( reader != null ) + { + reader.close(); + } + } + + public boolean hasMoreElements() + { + return !reachedEof; + } + + public Object nextElement() + throws NoSuchElementException + { + if( !hasMoreElements() ) + { + throw new NoSuchElementException( "OneLiner" ); + } + BufferLine tmpLine = + new BufferLine( line.toString(), eolStr.toString() ); + nextLine(); + return tmpLine; + } + + protected void nextLine() + throws BuildException + { + int ch = -1; + int eolcount = 0; + + eolStr.setLength( 0 ); + line.setLength( 0 ); + + try + { + ch = reader.read(); + while( ch != -1 && ch != '\r' && ch != '\n' ) + { + line.append( ( char )ch ); + ch = reader.read(); + } + + if( ch == -1 && line.length() == 0 ) + { + // Eof has been reached + reachedEof = true; + return; + } + + switch ( ( char )ch ) + { + case '\r': + // Check for \r, \r\n and \r\r\n + // Regard \r\r not followed by \n as two lines + ++eolcount; + eolStr.append( '\r' ); + switch ( ( char )( ch = reader.read() ) ) + { + case '\r': + if( ( char )( ch = reader.read() ) == '\n' ) + { + eolcount += 2; + eolStr.append( "\r\n" ); + } + break; + case '\n': + ++eolcount; + eolStr.append( '\n' ); + break; + }// end of switch ((char)(ch = reader.read())) + break; + case '\n': + ++eolcount; + eolStr.append( '\n' ); + break; + }// end of switch ((char) ch) + + // if at eolcount == 0 and trailing characters of string + // are CTRL-Zs, set eofStr + if( eolcount == 0 ) + { + int i = line.length(); + while( --i >= 0 && line.charAt( i ) == CTRLZ ) + { + // keep searching for the first ^Z + } + if( i < line.length() - 1 ) + { + // Trailing characters are ^Zs + // Construct new line and eofStr + eofStr.append( line.toString().substring( i + 1 ) ); + if( i < 0 ) + { + line.setLength( 0 ); + reachedEof = true; + } + else + { + line.setLength( i + 1 ); + } + } + + }// end of if (eolcount == 0) + + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + class BufferLine + { + private int next = 0; + private int column = 0; + private int lookahead = UNDEF; + private String eolStr; + private String line; + + public BufferLine( String line, String eolStr ) + throws BuildException + { + next = 0; + column = 0; + this.line = line; + this.eolStr = eolStr; + } + + public void setColumn( int col ) + { + column = col; + } + + public void setLookahead( int lookahead ) + { + this.lookahead = lookahead; + } + + public void setNext( int next ) + { + this.next = next; + } + + public void setState( int state ) + { + OneLiner.this.setState( state ); + } + + public char getChar( int i ) + { + return line.charAt( i ); + } + + public int getColumn() + { + return column; + } + + public String getEol() + { + return eolStr; + } + + public int getEolLength() + { + return eolStr.length(); + } + + public String getLineString() + { + return line; + } + + public int getLookahead() + { + return lookahead; + } + + public int getNext() + { + return next; + } + + public char getNextChar() + { + return getChar( next ); + } + + public char getNextCharInc() + { + return getChar( next++ ); + } + + public int getState() + { + return OneLiner.this.getState(); + } + + public int incColumn() + { + return column++; + } + + public int length() + { + return line.length(); + } + + public String substring( int begin ) + { + return line.substring( begin ); + } + + public String substring( int begin, int end ) + { + return line.substring( begin, end ); + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GUnzip.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GUnzip.java new file mode 100644 index 000000000..a4001fba5 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GUnzip.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; +import org.apache.tools.ant.BuildException; + +/** + * Expands a file that has been compressed with the GZIP algorithm. Normally + * used to compress non-compressed archives such as TAR files. + * + * @author Stefan Bodewig + * @author Magesh Umasankar + */ + +public class GUnzip extends Unpack +{ + + private final static String DEFAULT_EXTENSION = ".gz"; + + protected String getDefaultExtension() + { + return DEFAULT_EXTENSION; + } + + protected void extract() + { + if( source.lastModified() > dest.lastModified() ) + { + log( "Expanding " + source.getAbsolutePath() + " to " + + dest.getAbsolutePath() ); + + FileOutputStream out = null; + GZIPInputStream zIn = null; + FileInputStream fis = null; + try + { + out = new FileOutputStream( dest ); + fis = new FileInputStream( source ); + zIn = new GZIPInputStream( fis ); + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + out.write( buffer, 0, count ); + count = zIn.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } + catch( IOException ioe ) + { + String msg = "Problem expanding gzip " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( fis != null ) + { + try + { + fis.close(); + } + catch( IOException ioex ) + {} + } + if( out != null ) + { + try + { + out.close(); + } + catch( IOException ioex ) + {} + } + if( zIn != null ) + { + try + { + zIn.close(); + } + catch( IOException ioex ) + {} + } + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GZip.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GZip.java new file mode 100644 index 000000000..df35b0daf --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GZip.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.GZIPOutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Pack; + +/** + * Compresses a file with the GZIP algorithm. Normally used to compress + * non-compressed archives such as TAR files. + * + * @author James Davidson duncan@x180.com + * @author Jon S. Stevens jon@clearink.com + * @author Magesh Umasankar + */ + +public class GZip extends Pack +{ + protected void pack() + { + GZIPOutputStream zOut = null; + try + { + zOut = new GZIPOutputStream( new FileOutputStream( zipFile ) ); + zipFile( source, zOut ); + } + catch( IOException ioe ) + { + String msg = "Problem creating gzip " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( zOut != null ) + { + try + { + // close up + zOut.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GenerateKey.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GenerateKey.java new file mode 100644 index 000000000..06a0425ea --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/GenerateKey.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; + +/** + * Generates a key. + * + * @author Peter Donald + */ +public class GenerateKey extends Task +{ + + /** + * The alias of signer. + */ + protected String alias; + protected String dname; + protected DistinguishedName expandedDname; + protected String keyalg; + protected String keypass; + protected int keysize; + + /** + * The name of keystore file. + */ + protected String keystore; + + protected String sigalg; + protected String storepass; + protected String storetype; + protected int validity; + protected boolean verbose; + + public void setAlias( final String alias ) + { + this.alias = alias; + } + + public void setDname( final String dname ) + { + if( null != expandedDname ) + { + throw new BuildException( "It is not possible to specify dname both " + + "as attribute and element." ); + } + this.dname = dname; + } + + public void setKeyalg( final String keyalg ) + { + this.keyalg = keyalg; + } + + public void setKeypass( final String keypass ) + { + this.keypass = keypass; + } + + public void setKeysize( final String keysize ) + throws BuildException + { + try + { + this.keysize = Integer.parseInt( keysize ); + } + catch( final NumberFormatException nfe ) + { + throw new BuildException( "KeySize attribute should be a integer" ); + } + } + + public void setKeystore( final String keystore ) + { + this.keystore = keystore; + } + + public void setSigalg( final String sigalg ) + { + this.sigalg = sigalg; + } + + public void setStorepass( final String storepass ) + { + this.storepass = storepass; + } + + public void setStoretype( final String storetype ) + { + this.storetype = storetype; + } + + public void setValidity( final String validity ) + throws BuildException + { + try + { + this.validity = Integer.parseInt( validity ); + } + catch( final NumberFormatException nfe ) + { + throw new BuildException( "Validity attribute should be a integer" ); + } + } + + public void setVerbose( final boolean verbose ) + { + this.verbose = verbose; + } + + public DistinguishedName createDname() + throws BuildException + { + if( null != expandedDname ) + { + throw new BuildException( "DName sub-element can only be specified once." ); + } + if( null != dname ) + { + throw new BuildException( "It is not possible to specify dname both " + + "as attribute and element." ); + } + expandedDname = new DistinguishedName(); + return expandedDname; + } + + public void execute() + throws BuildException + { + if( project.getJavaVersion().equals( Project.JAVA_1_1 ) ) + { + throw new BuildException( "The genkey task is only available on JDK" + + " versions 1.2 or greater" ); + } + + if( null == alias ) + { + throw new BuildException( "alias attribute must be set" ); + } + + if( null == storepass ) + { + throw new BuildException( "storepass attribute must be set" ); + } + + if( null == dname && null == expandedDname ) + { + throw new BuildException( "dname must be set" ); + } + + final StringBuffer sb = new StringBuffer(); + + sb.append( "keytool -genkey " ); + + if( verbose ) + { + sb.append( "-v " ); + } + + sb.append( "-alias \"" ); + sb.append( alias ); + sb.append( "\" " ); + + if( null != dname ) + { + sb.append( "-dname \"" ); + sb.append( dname ); + sb.append( "\" " ); + } + + if( null != expandedDname ) + { + sb.append( "-dname \"" ); + sb.append( expandedDname ); + sb.append( "\" " ); + } + + if( null != keystore ) + { + sb.append( "-keystore \"" ); + sb.append( keystore ); + sb.append( "\" " ); + } + + if( null != storepass ) + { + sb.append( "-storepass \"" ); + sb.append( storepass ); + sb.append( "\" " ); + } + + if( null != storetype ) + { + sb.append( "-storetype \"" ); + sb.append( storetype ); + sb.append( "\" " ); + } + + sb.append( "-keypass \"" ); + if( null != keypass ) + { + sb.append( keypass ); + } + else + { + sb.append( storepass ); + } + sb.append( "\" " ); + + if( null != sigalg ) + { + sb.append( "-sigalg \"" ); + sb.append( sigalg ); + sb.append( "\" " ); + } + + if( null != keyalg ) + { + sb.append( "-keyalg \"" ); + sb.append( keyalg ); + sb.append( "\" " ); + } + + if( 0 < keysize ) + { + sb.append( "-keysize \"" ); + sb.append( keysize ); + sb.append( "\" " ); + } + + if( 0 < validity ) + { + sb.append( "-validity \"" ); + sb.append( validity ); + sb.append( "\" " ); + } + + log( "Generating Key for " + alias ); + final ExecTask cmd = ( ExecTask )project.createTask( "exec" ); + cmd.setCommand( new Commandline( sb.toString() ) ); + cmd.setFailonerror( true ); + cmd.setTaskName( getTaskName() ); + cmd.execute(); + } + + public static class DistinguishedName + { + + private Vector params = new Vector(); + private String name; + private String path; + + public Enumeration getParams() + { + return params.elements(); + } + + public Object createParam() + { + DnameParam param = new DnameParam(); + params.addElement( param ); + + return param; + } + + public String encode( final String string ) + { + int end = string.indexOf( ',' ); + + if( -1 == end ) + return string; + + final StringBuffer sb = new StringBuffer(); + + int start = 0; + + while( -1 != end ) + { + sb.append( string.substring( start, end ) ); + sb.append( "\\," ); + start = end + 1; + end = string.indexOf( ',', start ); + } + + sb.append( string.substring( start ) ); + + return sb.toString(); + } + + public String toString() + { + final int size = params.size(); + final StringBuffer sb = new StringBuffer(); + boolean firstPass = true; + + for( int i = 0; i < size; i++ ) + { + if( !firstPass ) + { + sb.append( " ," ); + } + firstPass = false; + + final DnameParam param = ( DnameParam )params.elementAt( i ); + sb.append( encode( param.getName() ) ); + sb.append( '=' ); + sb.append( encode( param.getValue() ) ); + } + + return sb.toString(); + } + } + + public static class DnameParam + { + private String name; + private String value; + + public void setName( String name ) + { + this.name = name; + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getName() + { + return name; + } + + public String getValue() + { + return value; + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Get.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Get.java new file mode 100644 index 000000000..6c1eb4451 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Get.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.Date; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Get a particular file from a URL source. Options include verbose reporting, + * timestamp based fetches and controlling actions on failures. NB: access + * through a firewall only works if the whole Java runtime is correctly + * configured. + * + * @author costin@dnt.ro + * @author gg@grtmail.com (Added Java 1.1 style HTTP basic auth) + */ +public class Get extends Task +{// required + private boolean verbose = false; + private boolean useTimestamp = false;//off by default + private boolean ignoreErrors = false; + private String uname = null; + private String pword = null;// required + private File dest; + private URL source; + + /** + * Where to copy the source file. + * + * @param dest Path to file. + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Don't stop if get fails if set to "true". + * + * @param v if "true" then don't report download errors up to ant + */ + public void setIgnoreErrors( boolean v ) + { + ignoreErrors = v; + } + + /** + * password for the basic auth. + * + * @param p password for authentication + */ + public void setPassword( String p ) + { + this.pword = p; + } + + /** + * Set the URL. + * + * @param u URL for the file. + */ + public void setSrc( URL u ) + { + this.source = u; + } + + /** + * Use timestamps, if set to "true".

      + * + * In this situation, the if-modified-since header is set so that the file + * is only fetched if it is newer than the local file (or there is no local + * file) This flag is only valid on HTTP connections, it is ignored in other + * cases. When the flag is set, the local copy of the downloaded file will + * also have its timestamp set to the remote file time.
      + * Note that remote files of date 1/1/1970 (GMT) are treated as 'no + * timestamp', and web servers often serve files with a timestamp in the + * future by replacing their timestamp with that of the current time. Also, + * inter-computer clock differences can cause no end of grief. + * + * @param v "true" to enable file time fetching + */ + public void setUseTimestamp( boolean v ) + { + if( project.getJavaVersion() != Project.JAVA_1_1 ) + { + useTimestamp = v; + } + } + + + /** + * Username for basic auth. + * + * @param u username for authentication + */ + public void setUsername( String u ) + { + this.uname = u; + } + + /** + * Be verbose, if set to "true". + * + * @param v if "true" then be verbose + */ + public void setVerbose( boolean v ) + { + verbose = v; + } + + + /** + * Does the work. + * + * @exception BuildException Thrown in unrecoverable error. + */ + public void execute() + throws BuildException + { + if( source == null ) + { + throw new BuildException( "src attribute is required", location ); + } + + if( dest == null ) + { + throw new BuildException( "dest attribute is required", location ); + } + + if( dest.exists() && dest.isDirectory() ) + { + throw new BuildException( "The specified destination is a directory", + location ); + } + + if( dest.exists() && !dest.canWrite() ) + { + throw new BuildException( "Can't write to " + dest.getAbsolutePath(), + location ); + } + + try + { + + log( "Getting: " + source ); + + //set the timestamp to the file date. + long timestamp = 0; + + boolean hasTimestamp = false; + if( useTimestamp && dest.exists() ) + { + timestamp = dest.lastModified(); + if( verbose ) + { + Date t = new Date( timestamp ); + log( "local file date : " + t.toString() ); + } + + hasTimestamp = true; + } + + //set up the URL connection + URLConnection connection = source.openConnection(); + //modify the headers + //NB: things like user authentication could go in here too. + if( useTimestamp && hasTimestamp ) + { + connection.setIfModifiedSince( timestamp ); + } + // prepare Java 1.1 style credentials + if( uname != null || pword != null ) + { + String up = uname + ":" + pword; + String encoding; + // check to see if sun's Base64 encoder is available. + try + { + sun.misc.BASE64Encoder encoder = + ( sun.misc.BASE64Encoder )Class.forName( "sun.misc.BASE64Encoder" ).newInstance(); + encoding = encoder.encode( up.getBytes() ); + + } + catch( Exception ex ) + {// sun's base64 encoder isn't available + Base64Converter encoder = new Base64Converter(); + encoding = encoder.encode( up.getBytes() ); + } + connection.setRequestProperty( "Authorization", "Basic " + encoding ); + } + + //connect to the remote site (may take some time) + connection.connect(); + //next test for a 304 result (HTTP only) + if( connection instanceof HttpURLConnection ) + { + HttpURLConnection httpConnection = ( HttpURLConnection )connection; + if( httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED ) + { + //not modified so no file download. just return instead + //and trace out something so the user doesn't think that the + //download happened when it didnt + log( "Not modified - so not downloaded" ); + return; + } + // test for 401 result (HTTP only) + if( httpConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED ) + { + log( "Not authorized - check " + dest + " for details" ); + return; + } + + } + + //REVISIT: at this point even non HTTP connections may support the if-modified-since + //behaviour -we just check the date of the content and skip the write if it is not + //newer. Some protocols (FTP) dont include dates, of course. + + FileOutputStream fos = new FileOutputStream( dest ); + + InputStream is = null; + for( int i = 0; i < 3; i++ ) + { + try + { + is = connection.getInputStream(); + break; + } + catch( IOException ex ) + { + log( "Error opening connection " + ex ); + } + } + if( is == null ) + { + log( "Can't get " + source + " to " + dest ); + if( ignoreErrors ) + return; + throw new BuildException( "Can't get " + source + " to " + dest, + location ); + } + + byte[] buffer = new byte[100 * 1024]; + int length; + + while( ( length = is.read( buffer ) ) >= 0 ) + { + fos.write( buffer, 0, length ); + if( verbose ) + System.out.print( "." ); + } + if( verbose ) + System.out.println(); + fos.close(); + is.close(); + + //if (and only if) the use file time option is set, then the + //saved file now has its timestamp set to that of the downloaded file + if( useTimestamp ) + { + long remoteTimestamp = connection.getLastModified(); + if( verbose ) + { + Date t = new Date( remoteTimestamp ); + log( "last modified = " + t.toString() + + ( ( remoteTimestamp == 0 ) ? " - using current time instead" : "" ) ); + } + if( remoteTimestamp != 0 ) + touchFile( dest, remoteTimestamp ); + } + } + catch( IOException ioe ) + { + log( "Error getting " + source + " to " + dest ); + if( ignoreErrors ) + return; + throw new BuildException( ioe); + } + } + + /** + * set the timestamp of a named file to a specified time. + * + * @param file Description of Parameter + * @param timemillis Description of Parameter + * @return true if it succeeded. False means that this is a java1.1 system + * and that file times can not be set + * @exception BuildException Thrown in unrecoverable error. Likely this + * comes from file access failures. + */ + protected boolean touchFile( File file, long timemillis ) + throws BuildException + { + + if( project.getJavaVersion() != Project.JAVA_1_1 ) + { + Touch touch = ( Touch )project.createTask( "touch" ); + touch.setOwningTarget( target ); + touch.setTaskName( getTaskName() ); + touch.setLocation( getLocation() ); + touch.setFile( file ); + touch.setMillis( timemillis ); + touch.touch(); + return true; + } + else + { + return false; + } + } + + /** + * BASE 64 encoding of a String or an array of bytes. Based on RFC 1421. + * + * @author Unknown + * @author Gautam Guliani + */ + + class Base64Converter + { + + public final char[] alphabet = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0 to 7 + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 8 to 15 + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 16 to 23 + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 24 to 31 + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 32 to 39 + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 40 to 47 + 'w', 'x', 'y', 'z', '0', '1', '2', '3', // 48 to 55 + '4', '5', '6', '7', '8', '9', '+', '/'};// 56 to 63 + + + public String encode( String s ) + { + return encode( s.getBytes() ); + } + + public String encode( byte[] octetString ) + { + int bits24; + int bits6; + + char[] out + = new char[( ( octetString.length - 1 ) / 3 + 1 ) * 4]; + + int outIndex = 0; + int i = 0; + + while( ( i + 3 ) <= octetString.length ) + { + // store the octets + bits24 = ( octetString[i++] & 0xFF ) << 16; + bits24 |= ( octetString[i++] & 0xFF ) << 8; + + bits6 = ( bits24 & 0x00FC0000 ) >> 18; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0003F000 ) >> 12; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x00000FC0 ) >> 6; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0000003F ); + out[outIndex++] = alphabet[bits6]; + } + + if( octetString.length - i == 2 ) + { + // store the octets + bits24 = ( octetString[i] & 0xFF ) << 16; + bits24 |= ( octetString[i + 1] & 0xFF ) << 8; + bits6 = ( bits24 & 0x00FC0000 ) >> 18; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0003F000 ) >> 12; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x00000FC0 ) >> 6; + out[outIndex++] = alphabet[bits6]; + + // padding + out[outIndex++] = '='; + } + else if( octetString.length - i == 1 ) + { + // store the octets + bits24 = ( octetString[i] & 0xFF ) << 16; + bits6 = ( bits24 & 0x00FC0000 ) >> 18; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0003F000 ) >> 12; + out[outIndex++] = alphabet[bits6]; + + // padding + out[outIndex++] = '='; + out[outIndex++] = '='; + } + + return new String( out ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Input.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Input.java new file mode 100644 index 000000000..caf080870 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Input.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.*; + + +/** + * Ant task to read input line from console. + * + * @author Ulrich Schmidt + */ +public class Input extends Task +{ + private String validargs = null; + private String message = ""; + private String addproperty = null; + private String input = null; + + /** + * No arg constructor. + */ + public Input() { } + + /** + * Defines the name of a property to be created from input. Behaviour is + * according to property task which means that existing properties cannot be + * overriden. + * + * @param addproperty Name for the property to be created from input + */ + public void setAddproperty( String addproperty ) + { + this.addproperty = addproperty; + } + + /** + * Sets the Message which gets displayed to the user during the build run. + * + * @param message The message to be displayed. + */ + public void setMessage( String message ) + { + this.message = message; + } + + /** + * Sets surrogate input to allow automated testing. + * + * @param testinput The new Testinput value + */ + public void setTestinput( String testinput ) + { + this.input = testinput; + } + + /** + * Defines valid input parameters as comma separated String. If set, input + * task will reject any input not defined as accepted and requires the user + * to reenter it. Validargs are case sensitive. If you want 'a' and 'A' to + * be accepted you need to define both values as accepted arguments. + * + * @param validargs A comma separated String defining valid input args. + */ + public void setValidargs( String validargs ) + { + this.validargs = validargs; + } + + // copied n' pasted from org.apache.tools.ant.taskdefs.Exit + /** + * Set a multiline message. + * + * @param msg The feature to be added to the Text attribute + */ + public void addText( String msg ) + { + message += project.replaceProperties( msg ); + } + + /** + * Actual test method executed by jakarta-ant. + * + * @exception BuildException + */ + public void execute() + throws BuildException + { + Vector accept = null; + if( validargs != null ) + { + accept = new Vector(); + StringTokenizer stok = new StringTokenizer( validargs, ",", false ); + while( stok.hasMoreTokens() ) + { + accept.addElement( stok.nextToken() ); + } + } + log( message, Project.MSG_WARN ); + if( input == null ) + { + try + { + BufferedReader in = new BufferedReader( new InputStreamReader( System.in ) ); + input = in.readLine(); + if( accept != null ) + { + while( !accept.contains( input ) ) + { + log( message, Project.MSG_WARN ); + input = in.readLine(); + } + } + } + catch( IOException e ) + { + throw new BuildException( "Failed to read input from Console.", e ); + } + } + // not quite the original intention of this task but for the sake + // of testing ;-) + else + { + if( accept != null && ( !accept.contains( input ) ) ) + { + throw new BuildException( "Invalid input please reenter." ); + } + } + // adopted from org.apache.tools.ant.taskdefs.Property + if( addproperty != null ) + { + if( project.getProperty( addproperty ) == null ) + { + project.setProperty( addproperty, input ); + } + else + { + log( "Override ignored for " + addproperty, Project.MSG_VERBOSE ); + } + } + } +} + + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jar.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jar.java new file mode 100644 index 000000000..1b2d8a976 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jar.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.*; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.FileScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.zip.ZipOutputStream; + +/** + * Creates a JAR archive. + * + * @author James Davidson duncan@x180.com + */ +public class Jar extends Zip +{ + /** + * The index file name. + */ + private final static String INDEX_NAME = "META-INF/INDEX.LIST"; + + /** + * true if a manifest has been specified in the task + */ + private boolean buildFileManifest = false; + + /** + * jar index is JDK 1.3+ only + */ + private boolean index = false; + private Manifest execManifest; + private Manifest manifest; + + private File manifestFile; + + /** + * constructor + */ + public Jar() + { + super(); + archiveType = "jar"; + emptyBehavior = "create"; + setEncoding( "UTF8" ); + } + + /** + * Set whether or not to create an index list for classes to speed up + * classloading. + * + * @param flag The new Index value + */ + public void setIndex( boolean flag ) + { + index = flag; + } + + /** + * @param jarFile The new Jarfile value + * @deprecated use setFile(File) instead. + */ + public void setJarfile( File jarFile ) + { + log( "DEPRECATED - The jarfile attribute is deprecated. Use file attribute instead." ); + setFile( jarFile ); + } + + public void setManifest( File manifestFile ) + { + if( !manifestFile.exists() ) + { + throw new BuildException( "Manifest file: " + manifestFile + " does not exist.", + getLocation() ); + } + + this.manifestFile = manifestFile; + + Reader r = null; + try + { + r = new FileReader( manifestFile ); + Manifest newManifest = new Manifest( r ); + if( manifest == null ) + { + manifest = Manifest.getDefaultManifest(); + } + manifest.merge( newManifest ); + } + catch( ManifestException e ) + { + log( "Manifest is invalid: " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( "Invalid Manifest: " + manifestFile, e, getLocation() ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read manifest file: " + manifestFile, e ); + } + finally + { + if( r != null ) + { + try + { + r.close(); + } + catch( IOException e ) + { + // do nothing + } + } + } + } + + public void setWhenempty( WhenEmpty we ) + { + log( "JARs are never empty, they contain at least a manifest file", + Project.MSG_WARN ); + } + + public void addConfiguredManifest( Manifest newManifest ) + throws ManifestException + { + if( manifest == null ) + { + manifest = Manifest.getDefaultManifest(); + } + manifest.merge( newManifest ); + buildFileManifest = true; + } + + public void addMetainf( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "META-INF/" ); + super.addFileset( fs ); + } + + /** + * Check whether the archive is up-to-date; + * + * @param scanners list of prepared scanners containing files to archive + * @param zipFile intended archive file (may or may not exist) + * @return true if nothing need be done (may have done something already); + * false if archive creation should proceed + * @exception BuildException if it likes + */ + protected boolean isUpToDate( FileScanner[] scanners, File zipFile ) + throws BuildException + { + // need to handle manifest as a special check + if( buildFileManifest || manifestFile == null ) + { + java.util.zip.ZipFile theZipFile = null; + try + { + theZipFile = new java.util.zip.ZipFile( zipFile ); + java.util.zip.ZipEntry entry = theZipFile.getEntry( "META-INF/MANIFEST.MF" ); + if( entry == null ) + { + log( "Updating jar since the current jar has no manifest", Project.MSG_VERBOSE ); + return false; + } + Manifest currentManifest = new Manifest( new InputStreamReader( theZipFile.getInputStream( entry ) ) ); + if( manifest == null ) + { + manifest = Manifest.getDefaultManifest(); + } + if( !currentManifest.equals( manifest ) ) + { + log( "Updating jar since jar manifest has changed", Project.MSG_VERBOSE ); + return false; + } + } + catch( Exception e ) + { + // any problems and we will rebuild + log( "Updating jar since cannot read current jar manifest: " + e.getClass().getName() + e.getMessage(), + Project.MSG_VERBOSE ); + return false; + } + finally + { + if( theZipFile != null ) + { + try + { + theZipFile.close(); + } + catch( IOException e ) + { + //ignore + } + } + } + } + else if( manifestFile.lastModified() > zipFile.lastModified() ) + { + return false; + } + return super.isUpToDate( scanners, zipFile ); + } + + /** + * Make sure we don't think we already have a MANIFEST next time this task + * gets executed. + */ + protected void cleanUp() + { + super.cleanUp(); + } + + protected boolean createEmptyZip( File zipFile ) + { + // Jar files always contain a manifest and can never be empty + return false; + } + + protected void finalizeZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + if( index ) + { + createIndexList( zOut ); + } + } + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + try + { + execManifest = Manifest.getDefaultManifest(); + + if( manifest != null ) + { + execManifest.merge( manifest ); + } + for( Enumeration e = execManifest.getWarnings(); e.hasMoreElements(); ) + { + log( "Manifest warning: " + ( String )e.nextElement(), Project.MSG_WARN ); + } + + zipDir( null, zOut, "META-INF/" ); + // time to write the manifest + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter( baos ); + execManifest.write( writer ); + writer.flush(); + + ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() ); + super.zipFile( bais, zOut, "META-INF/MANIFEST.MF", System.currentTimeMillis() ); + super.initZipOutputStream( zOut ); + } + catch( ManifestException e ) + { + log( "Manifest is invalid: " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( "Invalid Manifest", e, getLocation() ); + } + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + // If the file being added is META-INF/MANIFEST.MF, we warn if it's not the + // one specified in the "manifest" attribute - or if it's being added twice, + // meaning the same file is specified by the "manifeset" attribute and in + // a element. + if( vPath.equalsIgnoreCase( "META-INF/MANIFEST.MF" ) ) + { + log( "Warning: selected " + archiveType + " files include a META-INF/MANIFEST.MF which will be ignored " + + "(please use manifest attribute to " + archiveType + " task)", Project.MSG_WARN ); + } + else + { + super.zipFile( file, zOut, vPath ); + } + + } + + protected void zipFile( InputStream is, ZipOutputStream zOut, String vPath, long lastModified ) + throws IOException + { + // If the file being added is META-INF/MANIFEST.MF, we merge it with the + // current manifest + if( vPath.equalsIgnoreCase( "META-INF/MANIFEST.MF" ) ) + { + try + { + zipManifestEntry( is ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read manifest file: ", e ); + } + } + else + { + super.zipFile( is, zOut, vPath, lastModified ); + } + } + + /** + * Create the index list to speed up classloading. This is a JDK 1.3+ + * specific feature and is enabled by default. {@link + * http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#JAR%20Index} + * + * @param zOut the zip stream representing the jar being built. + * @throws IOException thrown if there is an error while creating the index + * and adding it to the zip stream. + */ + private void createIndexList( ZipOutputStream zOut ) + throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // encoding must be UTF8 as specified in the specs. + PrintWriter writer = new PrintWriter( new OutputStreamWriter( baos, "UTF8" ) ); + + // version-info blankline + writer.println( "JarIndex-Version: 1.0" ); + writer.println(); + + // header newline + writer.println( zipFile.getName() ); + + // JarIndex is sorting the directories by ascending order. + // it's painful to do in JDK 1.1 and it has no value but cosmetic + // since it will be read into a hashtable by the classloader. + Enumeration enum = addedDirs.keys(); + while( enum.hasMoreElements() ) + { + String dir = ( String )enum.nextElement(); + + // try to be smart, not to be fooled by a weird directory name + // @fixme do we need to check for directories starting by ./ ? + dir = dir.replace( '\\', '/' ); + int pos = dir.lastIndexOf( '/' ); + if( pos != -1 ) + { + dir = dir.substring( 0, pos ); + } + + // looks like nothing from META-INF should be added + // and the check is not case insensitive. + // see sun.misc.JarIndex + if( dir.startsWith( "META-INF" ) ) + { + continue; + } + // name newline + writer.println( dir ); + } + + writer.flush(); + ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() ); + super.zipFile( bais, zOut, INDEX_NAME, System.currentTimeMillis() ); + } + + + + /** + * Handle situation when we encounter a manifest file If we haven't been + * given one, we use this one. If we have, we merge the manifest in, + * provided it is a new file and not the old one from the JAR we are + * updating + * + * @param is Description of Parameter + * @exception IOException Description of Exception + */ + private void zipManifestEntry( InputStream is ) + throws IOException + { + try + { + if( execManifest == null ) + { + execManifest = new Manifest( new InputStreamReader( is ) ); + } + else if( isAddingNewFiles() ) + { + execManifest.merge( new Manifest( new InputStreamReader( is ) ) ); + } + } + catch( ManifestException e ) + { + log( "Manifest is invalid: " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( "Invalid Manifest", e, getLocation() ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Java.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Java.java new file mode 100644 index 000000000..060a331c6 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Java.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ExitException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * This task acts as a loader for java applications but allows to use the same + * JVM for the called application thus resulting in much faster operation. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Stefan Bodewig + */ +public class Java extends Task +{ + + private CommandlineJava cmdl = new CommandlineJava(); + private boolean fork = false; + private File dir = null; + private PrintStream outStream = null; + private boolean failOnError = false; + private File out; + + /** + * Set the command line arguments for the class. + * + * @param s The new Args value + */ + public void setArgs( String s ) + { + log( "The args attribute is deprecated. " + + "Please use nested arg elements.", + Project.MSG_WARN ); + cmdl.createArgument().setLine( s ); + } + + /** + * Set the class name. + * + * @param s The new Classname value + * @exception BuildException Description of Exception + */ + public void setClassname( String s ) + throws BuildException + { + if( cmdl.getJar() != null ) + { + throw new BuildException( "Cannot use 'jar' and 'classname' attributes in same command" ); + } + cmdl.setClassname( s ); + } + + /** + * Set the classpath to be used for this compilation. + * + * @param s The new Classpath value + */ + public void setClasspath( Path s ) + { + createClasspath().append( s ); + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * The working directory of the process + * + * @param d The new Dir value + */ + public void setDir( File d ) + { + this.dir = d; + } + + /** + * Throw a BuildException if process returns non 0. + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Set the forking flag. + * + * @param s The new Fork value + */ + public void setFork( boolean s ) + { + this.fork = s; + } + + public void setJVMVersion( String value ) + { + cmdl.setVmversion( value ); + } + + /** + * set the jar name... + * + * @param jarfile The new Jar value + * @exception BuildException Description of Exception + */ + public void setJar( File jarfile ) + throws BuildException + { + if( cmdl.getClassname() != null ) + { + throw new BuildException( "Cannot use 'jar' and 'classname' attributes in same command." ); + } + cmdl.setJar( jarfile.getAbsolutePath() ); + } + + /** + * Set the command used to start the VM (only if fork==false). + * + * @param s The new Jvm value + */ + public void setJvm( String s ) + { + cmdl.setVm( s ); + } + + /** + * Set the command line arguments for the JVM. + * + * @param s The new Jvmargs value + */ + public void setJvmargs( String s ) + { + log( "The jvmargs attribute is deprecated. " + + "Please use nested jvmarg elements.", + Project.MSG_WARN ); + cmdl.createVmArgument().setLine( s ); + } + + /** + * -mx or -Xmx depending on VM version + * + * @param max The new Maxmemory value + */ + public void setMaxmemory( String max ) + { + cmdl.setMaxmemory( max ); + } + + /** + * File the output of the process is redirected to. + * + * @param out The new Output value + */ + public void setOutput( File out ) + { + this.out = out; + } + + /** + * Add a nested sysproperty element. + * + * @param sysp The feature to be added to the Sysproperty attribute + */ + public void addSysproperty( Environment.Variable sysp ) + { + cmdl.addSysproperty( sysp ); + } + + /** + * Clear out the arguments to this java task. + */ + public void clearArgs() + { + cmdl.clearJavaArgs(); + } + + /** + * Creates a nested arg element. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createArg() + { + return cmdl.createArgument(); + } + + /** + * Creates a nested classpath element + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + return cmdl.createClasspath( project ).createPath(); + } + + /** + * Creates a nested jvmarg element. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createJvmarg() + { + return cmdl.createVmArgument(); + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + int err = -1; + if( ( err = executeJava() ) != 0 ) + { + if( failOnError ) + { + throw new BuildException( "Java returned: " + err, location ); + } + else + { + log( "Java Result: " + err, Project.MSG_ERR ); + } + } + } + + /** + * Do the execution and return a return code. + * + * @return the return code from the execute java class if it was executed in + * a separate VM (fork = "yes"). + * @exception BuildException Description of Exception + */ + public int executeJava() + throws BuildException + { + String classname = cmdl.getClassname(); + if( classname == null && cmdl.getJar() == null ) + { + throw new BuildException( "Classname must not be null." ); + } + if( !fork && cmdl.getJar() != null ) + { + throw new BuildException( "Cannot execute a jar in non-forked mode. Please set fork='true'. " ); + } + + if( fork ) + { + log( "Forking " + cmdl.toString(), Project.MSG_VERBOSE ); + + return run( cmdl.getCommandline() ); + } + else + { + if( cmdl.getVmCommand().size() > 1 ) + { + log( "JVM args ignored when same JVM is used.", Project.MSG_WARN ); + } + if( dir != null ) + { + log( "Working directory ignored when same JVM is used.", Project.MSG_WARN ); + } + + log( "Running in same VM " + cmdl.getJavaCommand().toString(), + Project.MSG_VERBOSE ); + try + { + run( cmdl ); + return 0; + } + catch( ExitException ex ) + { + return ex.getStatus(); + } + } + } + + protected void handleErrorOutput( String line ) + { + if( outStream != null ) + { + outStream.println( line ); + } + else + { + super.handleErrorOutput( line ); + } + } + + protected void handleOutput( String line ) + { + if( outStream != null ) + { + outStream.println( line ); + } + else + { + super.handleOutput( line ); + } + } + + /** + * Executes the given classname with the given arguments as it was a command + * line application. + * + * @param classname Description of Parameter + * @param args Description of Parameter + * @exception BuildException Description of Exception + */ + protected void run( String classname, Vector args ) + throws BuildException + { + CommandlineJava cmdj = new CommandlineJava(); + cmdj.setClassname( classname ); + for( int i = 0; i < args.size(); i++ ) + { + cmdj.createArgument().setValue( ( String )args.elementAt( i ) ); + } + run( cmdj ); + } + + /** + * Executes the given classname with the given arguments as it was a command + * line application. + * + * @param command Description of Parameter + * @exception BuildException Description of Exception + */ + private void run( CommandlineJava command ) + throws BuildException + { + ExecuteJava exe = new ExecuteJava(); + exe.setJavaCommand( command.getJavaCommand() ); + exe.setClasspath( command.getClasspath() ); + exe.setSystemProperties( command.getSystemProperties() ); + if( out != null ) + { + try + { + outStream = new PrintStream( new FileOutputStream( out ) ); + exe.execute( project ); + } + catch( IOException io ) + { + throw new BuildException( io ); + } + finally + { + if( outStream != null ) + { + outStream.close(); + } + } + } + else + { + exe.execute( project ); + } + } + + /** + * Executes the given classname with the given arguments in a separate VM. + * + * @param command Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int run( String[] command ) + throws BuildException + { + FileOutputStream fos = null; + try + { + Execute exe = null; + if( out == null ) + { + exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ), + null ); + } + else + { + fos = new FileOutputStream( out ); + exe = new Execute( new PumpStreamHandler( fos ), null ); + } + + exe.setAntRun( project ); + + if( dir == null ) + { + dir = project.getBaseDir(); + } + else if( !dir.exists() || !dir.isDirectory() ) + { + throw new BuildException( dir.getAbsolutePath() + " is not a valid directory", + location ); + } + + exe.setWorkingDirectory( dir ); + + exe.setCommandline( command ); + try + { + return exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + catch( IOException io ) + { + throw new BuildException( io ); + } + finally + { + if( fos != null ) + { + try + { + fos.close(); + } + catch( IOException io ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javac.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javac.java new file mode 100644 index 000000000..7793bc4f2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javac.java @@ -0,0 +1,941 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.compilers.CompilerAdapter; +import org.apache.tools.ant.taskdefs.compilers.CompilerAdapterFactory; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.GlobPatternMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Task to compile Java source files. This task can take the following + * arguments: + *

        + *
      • sourcedir + *
      • destdir + *
      • deprecation + *
      • classpath + *
      • bootclasspath + *
      • extdirs + *
      • optimize + *
      • debug + *
      • encoding + *
      • target + *
      • depend + *
      • vebose + *
      • failonerror + *
      • includeantruntime + *
      • includejavaruntime + *
      • source + *
      + * Of these arguments, the sourcedir and destdir are required.

      + * + * When this task executes, it will recursively scan the sourcedir and destdir + * looking for Java source files to compile. This task makes its compile + * decision based on timestamp. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ + +public class Javac extends MatchingTask +{ + + private final static String FAIL_MSG + = "Compile failed, messages should have been provided."; + private boolean debug = false; + private boolean optimize = false; + private boolean deprecation = false; + private boolean depend = false; + private boolean verbose = false; + private boolean includeAntRuntime = true; + private boolean includeJavaRuntime = false; + private String fork = "false"; + private String forkedExecutable = null; + private boolean nowarn = false; + private Vector implementationSpecificArgs = new Vector(); + + protected boolean failOnError = true; + protected File[] compileList = new File[0]; + private Path bootclasspath; + private Path compileClasspath; + private String debugLevel; + private File destDir; + private String encoding; + private Path extdirs; + private String memoryInitialSize; + private String memoryMaximumSize; + + private String source; + + private Path src; + private String target; + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new BootClasspathRef value + */ + public void setBootClasspathRef( Reference r ) + { + createBootclasspath().setRefid( r ); + } + + /** + * Sets the bootclasspath that will be used to compile the classes against. + * + * @param bootclasspath The new Bootclasspath value + */ + public void setBootclasspath( Path bootclasspath ) + { + if( this.bootclasspath == null ) + { + this.bootclasspath = bootclasspath; + } + else + { + this.bootclasspath.append( bootclasspath ); + } + } + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( compileClasspath == null ) + { + compileClasspath = classpath; + } + else + { + compileClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Set the debug flag. + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Set the value of debugLevel. + * + * @param v Value to assign to debugLevel. + */ + public void setDebugLevel( String v ) + { + this.debugLevel = v; + } + + /** + * Set the depend flag. + * + * @param depend The new Depend value + */ + public void setDepend( boolean depend ) + { + this.depend = depend; + } + + /** + * Set the deprecation flag. + * + * @param deprecation The new Deprecation value + */ + public void setDeprecation( boolean deprecation ) + { + this.deprecation = deprecation; + } + + /** + * Set the destination directory into which the Java source files should be + * compiled. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Set the Java source file encoding name. + * + * @param encoding The new Encoding value + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Sets the extension directories that will be used during the compilation. + * + * @param extdirs The new Extdirs value + */ + public void setExtdirs( Path extdirs ) + { + if( this.extdirs == null ) + { + this.extdirs = extdirs; + } + else + { + this.extdirs.append( extdirs ); + } + } + + /** + * Throw a BuildException if compilation fails + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Sets whether to fork the javac compiler. + * + * @param f "true|false|on|off|yes|no" or the name of the javac executable. + */ + public void setFork( String f ) + { + if( f.equalsIgnoreCase( "on" ) + || f.equalsIgnoreCase( "true" ) + || f.equalsIgnoreCase( "yes" ) ) + { + fork = "true"; + forkedExecutable = getSystemJavac(); + } + else if( f.equalsIgnoreCase( "off" ) + || f.equalsIgnoreCase( "false" ) + || f.equalsIgnoreCase( "no" ) ) + { + fork = "false"; + forkedExecutable = null; + } + else + { + fork = "true"; + forkedExecutable = f; + } + } + + /** + * Include ant's own classpath in this task's classpath? + * + * @param include The new Includeantruntime value + */ + public void setIncludeantruntime( boolean include ) + { + includeAntRuntime = include; + } + + /** + * Sets whether or not to include the java runtime libraries to this task's + * classpath. + * + * @param include The new Includejavaruntime value + */ + public void setIncludejavaruntime( boolean include ) + { + includeJavaRuntime = include; + } + + /** + * Set the memoryInitialSize flag. + * + * @param memoryInitialSize The new MemoryInitialSize value + */ + public void setMemoryInitialSize( String memoryInitialSize ) + { + this.memoryInitialSize = memoryInitialSize; + } + + /** + * Set the memoryMaximumSize flag. + * + * @param memoryMaximumSize The new MemoryMaximumSize value + */ + public void setMemoryMaximumSize( String memoryMaximumSize ) + { + this.memoryMaximumSize = memoryMaximumSize; + } + + /** + * Sets whether the -nowarn option should be used. + * + * @param flag The new Nowarn value + */ + public void setNowarn( boolean flag ) + { + this.nowarn = flag; + } + + /** + * Set the optimize flag. + * + * @param optimize The new Optimize value + */ + public void setOptimize( boolean optimize ) + { + this.optimize = optimize; + } + + /** + * Proceed if compilation fails + * + * @param proceed The new Proceed value + */ + public void setProceed( boolean proceed ) + { + failOnError = !proceed; + } + + /** + * Set the value of source. + * + * @param v Value to assign to source. + */ + public void setSource( String v ) + { + this.source = v; + } + + /** + * Set the source dirs to find the source Java files. + * + * @param srcDir The new Srcdir value + */ + public void setSrcdir( Path srcDir ) + { + if( src == null ) + { + src = srcDir; + } + else + { + src.append( srcDir ); + } + } + + /** + * Sets the target VM that the classes will be compiled for. Valid strings + * are "1.1", "1.2", and "1.3". + * + * @param target The new Target value + */ + public void setTarget( String target ) + { + this.target = target; + } + + /** + * Set the verbose flag. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + this.verbose = verbose; + } + + /** + * Gets the bootclasspath that will be used to compile the classes against. + * + * @return The Bootclasspath value + */ + public Path getBootclasspath() + { + return bootclasspath; + } + + /** + * Gets the classpath to be used for this compilation. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return compileClasspath; + } + + /** + * Get the additional implementation specific command line arguments. + * + * @return array of command line arguments, guaranteed to be non-null. + */ + public String[] getCurrentCompilerArgs() + { + Vector args = new Vector(); + for( Enumeration enum = implementationSpecificArgs.elements(); + enum.hasMoreElements(); + ) + { + String[] curr = + ( ( ImplementationSpecificArgument )enum.nextElement() ).getParts(); + for( int i = 0; i < curr.length; i++ ) + { + args.addElement( curr[i] ); + } + } + String[] res = new String[args.size()]; + args.copyInto( res ); + return res; + } + + /** + * Gets the debug flag. + * + * @return The Debug value + */ + public boolean getDebug() + { + return debug; + } + + /** + * Get the value of debugLevel. + * + * @return value of debugLevel. + */ + public String getDebugLevel() + { + return debugLevel; + } + + /** + * Gets the depend flag. + * + * @return The Depend value + */ + public boolean getDepend() + { + return depend; + } + + /** + * Gets the deprecation flag. + * + * @return The Deprecation value + */ + public boolean getDeprecation() + { + return deprecation; + } + + /** + * Gets the destination directory into which the java source files should be + * compiled. + * + * @return The Destdir value + */ + public File getDestdir() + { + return destDir; + } + + /** + * Gets the java source file encoding name. + * + * @return The Encoding value + */ + public String getEncoding() + { + return encoding; + } + + /** + * Gets the extension directories that will be used during the compilation. + * + * @return The Extdirs value + */ + public Path getExtdirs() + { + return extdirs; + } + + /** + * Gets the failonerror flag. + * + * @return The Failonerror value + */ + public boolean getFailonerror() + { + return failOnError; + } + + /** + * Gets the list of files to be compiled. + * + * @return The FileList value + */ + public File[] getFileList() + { + return compileList; + } + + /** + * Gets whether or not the ant classpath is to be included in the task's + * classpath. + * + * @return The Includeantruntime value + */ + public boolean getIncludeantruntime() + { + return includeAntRuntime; + } + + /** + * Gets whether or not the java runtime should be included in this task's + * classpath. + * + * @return The Includejavaruntime value + */ + public boolean getIncludejavaruntime() + { + return includeJavaRuntime; + } + + /** + * The name of the javac executable to use in fork-mode. + * + * @return The JavacExecutable value + */ + public String getJavacExecutable() + { + if( forkedExecutable == null && isForkedJavac() ) + { + forkedExecutable = getSystemJavac(); + } + else if( forkedExecutable != null && !isForkedJavac() ) + { + forkedExecutable = null; + } + return forkedExecutable; + } + + /** + * Gets the memoryInitialSize flag. + * + * @return The MemoryInitialSize value + */ + public String getMemoryInitialSize() + { + return memoryInitialSize; + } + + /** + * Gets the memoryMaximumSize flag. + * + * @return The MemoryMaximumSize value + */ + public String getMemoryMaximumSize() + { + return memoryMaximumSize; + } + + /** + * Should the -nowarn option be used. + * + * @return The Nowarn value + */ + public boolean getNowarn() + { + return nowarn; + } + + /** + * Gets the optimize flag. + * + * @return The Optimize value + */ + public boolean getOptimize() + { + return optimize; + } + + /** + * Get the value of source. + * + * @return value of source. + */ + public String getSource() + { + return source; + } + + /** + * Gets the source dirs to find the source java files. + * + * @return The Srcdir value + */ + public Path getSrcdir() + { + return src; + } + + /** + * Gets the target VM that the classes will be compiled for. + * + * @return The Target value + */ + public String getTarget() + { + return target; + } + + /** + * Gets the verbose flag. + * + * @return The Verbose value + */ + public boolean getVerbose() + { + return verbose; + } + + /** + * Is this a forked invocation of JDK's javac? + * + * @return The ForkedJavac value + */ + public boolean isForkedJavac() + { + return !"false".equals( fork ) || + "extJavac".equals( project.getProperty( "build.compiler" ) ); + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createBootclasspath() + { + if( bootclasspath == null ) + { + bootclasspath = new Path( project ); + } + return bootclasspath.createPath(); + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + return compileClasspath.createPath(); + } + + /** + * Adds an implementation specific command line argument. + * + * @return Description of the Returned Value + */ + public ImplementationSpecificArgument createCompilerArg() + { + ImplementationSpecificArgument arg = + new ImplementationSpecificArgument(); + implementationSpecificArgs.addElement( arg ); + return arg; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createExtdirs() + { + if( extdirs == null ) + { + extdirs = new Path( project ); + } + return extdirs.createPath(); + } + + /** + * Create a nested src element for multiple source path support. + * + * @return a nested src element. + */ + public Path createSrc() + { + if( src == null ) + { + src = new Path( project ); + } + return src.createPath(); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir + + if( src == null ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + String[] list = src.list(); + if( list.length == 0 ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + + if( destDir != null && !destDir.isDirectory() ) + { + throw new BuildException( "destination directory \"" + destDir + "\" does not exist or is not a directory", location ); + } + + // scan source directories and dest directory to build up + // compile lists + resetFileLists(); + for( int i = 0; i < list.length; i++ ) + { + File srcDir = ( File )project.resolveFile( list[i] ); + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir \"" + srcDir.getPath() + "\" does not exist!", location ); + } + + DirectoryScanner ds = this.getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + + scanDir( srcDir, destDir != null ? destDir : srcDir, files ); + } + + // compile the source files + + String compiler = determineCompiler(); + + if( compileList.length > 0 ) + { + + CompilerAdapter adapter = CompilerAdapterFactory.getCompiler( + compiler, this ); + log( "Compiling " + compileList.length + + " source file" + + ( compileList.length == 1 ? "" : "s" ) + + ( destDir != null ? " to " + destDir : "" ) ); + + // now we need to populate the compiler adapter + adapter.setJavac( this ); + + // finally, lets execute the compiler!! + if( !adapter.execute() ) + { + if( failOnError ) + { + throw new BuildException( FAIL_MSG, location ); + } + else + { + log( FAIL_MSG, Project.MSG_ERR ); + } + } + } + } + + protected String getSystemJavac() + { + // This is the most common extension case - exe for windows and OS/2, + // nothing for *nix. + String extension = Os.isFamily( "dos" ) ? ".exe" : ""; + + // Look for java in the java.home/../bin directory. Unfortunately + // on Windows java.home doesn't always refer to the correct location, + // so we need to fall back to assuming java is somewhere on the + // PATH. + java.io.File jExecutable = + new java.io.File( System.getProperty( "java.home" ) + + "/../bin/javac" + extension ); + + if( jExecutable.exists() && !Os.isFamily( "netware" ) ) + { + return jExecutable.getAbsolutePath(); + } + else + { + return "javac"; + } + } + + protected boolean isJdkCompiler( String compiler ) + { + return "modern".equals( compiler ) || + "classic".equals( compiler ) || + "javac1.1".equals( compiler ) || + "javac1.2".equals( compiler ) || + "javac1.3".equals( compiler ) || + "javac1.4".equals( compiler ); + } + + /** + * Recreate src + * + * @return a nested src element. + */ + protected Path recreateSrc() + { + src = null; + return createSrc(); + } + + /** + * Clear the list of files to be compiled and copied.. + */ + protected void resetFileLists() + { + compileList = new File[0]; + } + + /** + * Scans the directory looking for source files to be compiled. The results + * are returned in the class variable compileList + * + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param files Description of Parameter + */ + protected void scanDir( File srcDir, File destDir, String files[] ) + { + GlobPatternMapper m = new GlobPatternMapper(); + m.setFrom( "*.java" ); + m.setTo( "*.class" ); + SourceFileScanner sfs = new SourceFileScanner( this ); + File[] newFiles = sfs.restrictAsFiles( files, srcDir, destDir, m ); + + if( newFiles.length > 0 ) + { + File[] newCompileList = new File[compileList.length + + newFiles.length]; + System.arraycopy( compileList, 0, newCompileList, 0, + compileList.length ); + System.arraycopy( newFiles, 0, newCompileList, + compileList.length, newFiles.length ); + compileList = newCompileList; + } + } + + private String determineCompiler() + { + String compiler = project.getProperty( "build.compiler" ); + + if( !"false".equals( fork ) ) + { + if( compiler != null ) + { + if( isJdkCompiler( compiler ) ) + { + log( "Since fork is true, ignoring build.compiler setting.", + Project.MSG_WARN ); + compiler = "extJavac"; + } + else + { + log( "Since build.compiler setting isn't classic or modern, ignoring fork setting.", Project.MSG_WARN ); + } + } + else + { + compiler = "extJavac"; + } + } + + if( compiler == null ) + { + if( Project.getJavaVersion() != Project.JAVA_1_1 && + Project.getJavaVersion() != Project.JAVA_1_2 ) + { + compiler = "modern"; + } + else + { + compiler = "classic"; + } + } + return compiler; + } + + /** + * Adds an "implementation" attribute to Commandline$Attribute used to + * filter command line attributes based on the current implementation. + * + * @author RT + */ + public class ImplementationSpecificArgument + extends Commandline.Argument + { + + private String impl; + + public void setImplementation( String impl ) + { + this.impl = impl; + } + + public String[] getParts() + { + if( impl == null || impl.equals( determineCompiler() ) ) + { + return super.getParts(); + } + else + { + return new String[0]; + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JavacOutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JavacOutputStream.java new file mode 100644 index 000000000..4d959fbf8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JavacOutputStream.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.tools.ant.Task; + +/** + * Serves as an output stream to Javac. This let's us print messages out to the + * log and detect whether or not Javac had an error while compiling. + * + * @author James Duncan Davidson (duncan@x180.com) + * @deprecated use returnvalue of compile to detect compilation failure. + */ + +class JavacOutputStream extends OutputStream +{ + private boolean errorFlag = false; + private StringBuffer line; + + private Task task; + + /** + * Constructs a new JavacOutputStream with the given task as the output + * source for messages. + * + * @param task Description of Parameter + */ + + JavacOutputStream( Task task ) + { + this.task = task; + line = new StringBuffer(); + } + + /** + * Write a character to the output stream. This method looks to make sure + * that there isn't an error being reported and will flush each line of + * input out to the project's log stream. + * + * @param c Description of Parameter + * @exception IOException Description of Exception + */ + + public void write( int c ) + throws IOException + { + char cc = ( char )c; + if( cc == '\r' || cc == '\n' ) + { + // line feed + if( line.length() > 0 ) + { + processLine(); + } + } + else + { + line.append( cc ); + } + } + + /** + * Returns the error status of the compile. If no errors occured, this + * method will return false, else this method will return true. + * + * @return The ErrorFlag value + */ + + boolean getErrorFlag() + { + return errorFlag; + } + + /** + * Processes a line of input and determines if an error occured. + */ + + private void processLine() + { + String s = line.toString(); + if( s.indexOf( "error" ) > -1 ) + { + errorFlag = true; + } + task.log( s ); + line = new StringBuffer(); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javadoc.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javadoc.java new file mode 100644 index 000000000..e95245de7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Javadoc.java @@ -0,0 +1,1473 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileWriter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileUtils; + +/** + * This task makes it easy to generate Javadoc documentation for a collection of + * source code.

      + * + * Current known limitations are:

      + * + * + *

        + *
      • patterns must be of the form "xxx.*", every other pattern doesn't + * work. + *
      • the java comment-stripper reader is horribly slow + *
      • there is no control on arguments sanity since they are left to the + * javadoc implementation. + *
      • argument J in javadoc1 is not supported (what is that for anyway?) + * + *
      + *

      + * + * If no doclet is set, then the version and author + * are by default "yes".

      + * + * Note: This task is run on another VM because the Javadoc code calls System.exit() + * which would break Ant functionality. + * + * @author Jon S. Stevens jon@clearink.com + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Patrick Chanezon + * chanezon@netscape.com + * @author Ernst de Haan ernst@jollem.com + * @author Stefan Bodewig + */ + +public class Javadoc extends Task +{ + private static boolean javadoc1 = + ( Project.getJavaVersion() == Project.JAVA_1_1 ); + + private Commandline cmd = new Commandline(); + + private boolean foundJavaFile = false; + private boolean failOnError = false; + private Path sourcePath = null; + private File destDir = null; + private Vector sourceFiles = new Vector(); + private Vector packageNames = new Vector( 5 ); + private Vector excludePackageNames = new Vector( 1 ); + private boolean author = true; + private boolean version = true; + private DocletInfo doclet = null; + private Path classpath = null; + private Path bootclasspath = null; + private String group = null; + private Vector compileList = new Vector( 10 ); + private String packageList = null; + private Vector links = new Vector( 2 ); + private Vector groups = new Vector( 2 ); + private boolean useDefaultExcludes = true; + private Html doctitle = null; + private Html header = null; + private Html footer = null; + private Html bottom = null; + private boolean useExternalFile = false; + private File tmpList = null; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + + public void setAccess( AccessType at ) + { + cmd.createArgument().setValue( "-" + at.getValue() ); + } + + public void setAdditionalparam( String add ) + { + cmd.createArgument().setLine( add ); + } + + public void setAuthor( boolean src ) + { + author = src; + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new BootClasspathRef value + */ + public void setBootClasspathRef( Reference r ) + { + createBootclasspath().setRefid( r ); + } + + public void setBootclasspath( Path src ) + { + if( bootclasspath == null ) + { + bootclasspath = src; + } + else + { + bootclasspath.append( src ); + } + } + + public void setBottom( String src ) + { + Html h = new Html(); + h.addText( src ); + addBottom( h ); + } + + public void setCharset( String src ) + { + this.add12ArgIfNotEmpty( "-charset", src ); + } + + public void setClasspath( Path src ) + { + if( classpath == null ) + { + classpath = src; + } + else + { + classpath.append( src ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + public void setDestdir( File dir ) + { + destDir = dir; + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + public void setDocencoding( String enc ) + { + cmd.createArgument().setValue( "-docencoding" ); + cmd.createArgument().setValue( enc ); + } + + public void setDoclet( String src ) + { + if( doclet == null ) + { + doclet = new DocletInfo(); + } + doclet.setName( src ); + } + + public void setDocletPath( Path src ) + { + if( doclet == null ) + { + doclet = new DocletInfo(); + } + doclet.setPath( src ); + } + + public void setDocletPathRef( Reference r ) + { + if( doclet == null ) + { + doclet = new DocletInfo(); + } + doclet.createPath().setRefid( r ); + } + + public void setDoctitle( String src ) + { + Html h = new Html(); + h.addText( src ); + addDoctitle( h ); + } + + public void setEncoding( String enc ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( enc ); + } + + public void setExcludePackageNames( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String p = tok.nextToken(); + PackageName pn = new PackageName(); + pn.setName( p ); + addExcludePackage( pn ); + } + } + + public void setExtdirs( String src ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-extdirs" ); + cmd.createArgument().setValue( src ); + } + } + + /** + * Should the build process fail if javadoc fails (as indicated by a non + * zero return code)?

      + * + * Default is false.

      + * + * @param b The new Failonerror value + */ + public void setFailonerror( boolean b ) + { + failOnError = b; + } + + public void setFooter( String src ) + { + Html h = new Html(); + h.addText( src ); + addFooter( h ); + } + + public void setGroup( String src ) + { + group = src; + } + + public void setHeader( String src ) + { + Html h = new Html(); + h.addText( src ); + addHeader( h ); + } + + public void setHelpfile( File f ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-helpfile" ); + cmd.createArgument().setFile( f ); + } + } + + public void setLink( String src ) + { + if( !javadoc1 ) + { + createLink().setHref( src ); + } + } + + public void setLinkoffline( String src ) + { + if( !javadoc1 ) + { + LinkArgument le = createLink(); + le.setOffline( true ); + String linkOfflineError = "The linkoffline attribute must include a URL and " + + "a package-list file location separated by a space"; + if( src.trim().length() == 0 ) + { + throw new BuildException( linkOfflineError ); + } + StringTokenizer tok = new StringTokenizer( src, " ", false ); + le.setHref( tok.nextToken() ); + + if( !tok.hasMoreTokens() ) + { + throw new BuildException( linkOfflineError ); + } + le.setPackagelistLoc( project.resolveFile( tok.nextToken() ) ); + } + } + + public void setLocale( String src ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-locale" ); + cmd.createArgument().setValue( src ); + } + } + + public void setMaxmemory( String max ) + { + if( javadoc1 ) + { + cmd.createArgument().setValue( "-J-mx" + max ); + } + else + { + cmd.createArgument().setValue( "-J-Xmx" + max ); + } + } + + public void setNodeprecated( boolean b ) + { + addArgIf( b, "-nodeprecated" ); + } + + public void setNodeprecatedlist( boolean b ) + { + add12ArgIf( b, "-nodeprecatedlist" ); + } + + public void setNohelp( boolean b ) + { + add12ArgIf( b, "-nohelp" ); + } + + public void setNoindex( boolean b ) + { + addArgIf( b, "-noindex" ); + } + + public void setNonavbar( boolean b ) + { + add12ArgIf( b, "-nonavbar" ); + } + + public void setNotree( boolean b ) + { + addArgIf( b, "-notree" ); + } + + public void setOld( boolean b ) + { + add12ArgIf( b, "-1.1" ); + } + + public void setOverview( File f ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-overview" ); + cmd.createArgument().setFile( f ); + } + } + + public void setPackage( boolean b ) + { + addArgIf( b, "-package" ); + } + + public void setPackageList( String src ) + { + packageList = src; + } + + public void setPackagenames( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String p = tok.nextToken(); + PackageName pn = new PackageName(); + pn.setName( p ); + addPackage( pn ); + } + } + + public void setPrivate( boolean b ) + { + addArgIf( b, "-private" ); + } + + public void setProtected( boolean b ) + { + addArgIf( b, "-protected" ); + } + + public void setPublic( boolean b ) + { + addArgIf( b, "-public" ); + } + + public void setSerialwarn( boolean b ) + { + add12ArgIf( b, "-serialwarn" ); + } + + public void setSourcefiles( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String f = tok.nextToken(); + SourceFile sf = new SourceFile(); + sf.setFile( project.resolveFile( f ) ); + addSource( sf ); + } + } + + public void setSourcepath( Path src ) + { + if( sourcePath == null ) + { + sourcePath = src; + } + else + { + sourcePath.append( src ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new SourcepathRef value + */ + public void setSourcepathRef( Reference r ) + { + createSourcepath().setRefid( r ); + } + + public void setSplitindex( boolean b ) + { + add12ArgIf( b, "-splitindex" ); + } + + public void setStylesheetfile( File f ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-stylesheetfile" ); + cmd.createArgument().setFile( f ); + } + } + + public void setUse( boolean b ) + { + add12ArgIf( b, "-use" ); + } + + /** + * Work around command line length limit by using an external file for the + * sourcefiles. + * + * @param b The new UseExternalFile value + */ + public void setUseExternalFile( boolean b ) + { + if( !javadoc1 ) + { + useExternalFile = b; + } + } + + public void setVerbose( boolean b ) + { + add12ArgIf( b, "-verbose" ); + } + + public void setVersion( boolean src ) + { + version = src; + } + + public void setWindowtitle( String src ) + { + add12ArgIfNotEmpty( "-windowtitle", src ); + } + + public void addBottom( Html text ) + { + if( !javadoc1 ) + { + bottom = text; + } + } + + public void addDoctitle( Html text ) + { + if( !javadoc1 ) + { + doctitle = text; + } + } + + public void addExcludePackage( PackageName pn ) + { + excludePackageNames.addElement( pn ); + } + + public void addFooter( Html text ) + { + if( !javadoc1 ) + { + footer = text; + } + } + + public void addHeader( Html text ) + { + if( !javadoc1 ) + { + header = text; + } + } + + public void addPackage( PackageName pn ) + { + packageNames.addElement( pn ); + } + + public void addSource( SourceFile sf ) + { + sourceFiles.addElement( sf ); + } + + public Path createBootclasspath() + { + if( bootclasspath == null ) + { + bootclasspath = new Path( project ); + } + return bootclasspath.createPath(); + } + + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + public DocletInfo createDoclet() + { + doclet = new DocletInfo(); + return doclet; + } + + public GroupArgument createGroup() + { + GroupArgument ga = new GroupArgument(); + groups.addElement( ga ); + return ga; + } + + public LinkArgument createLink() + { + LinkArgument la = new LinkArgument(); + links.addElement( la ); + return la; + } + + public Path createSourcepath() + { + if( sourcePath == null ) + { + sourcePath = new Path( project ); + } + return sourcePath.createPath(); + } + + public void execute() + throws BuildException + { + if( "javadoc2".equals( taskType ) ) + { + log( "!! javadoc2 is deprecated. Use javadoc instead. !!" ); + } + + if( sourcePath == null ) + { + String msg = "sourcePath attribute must be set!"; + throw new BuildException( msg ); + } + + log( "Generating Javadoc", Project.MSG_INFO ); + + if( doctitle != null ) + { + cmd.createArgument().setValue( "-doctitle" ); + cmd.createArgument().setValue( expand( doctitle.getText() ) ); + } + if( header != null ) + { + cmd.createArgument().setValue( "-header" ); + cmd.createArgument().setValue( expand( header.getText() ) ); + } + if( footer != null ) + { + cmd.createArgument().setValue( "-footer" ); + cmd.createArgument().setValue( expand( footer.getText() ) ); + } + if( bottom != null ) + { + cmd.createArgument().setValue( "-bottom" ); + cmd.createArgument().setValue( expand( bottom.getText() ) ); + } + + Commandline toExecute = ( Commandline )cmd.clone(); + toExecute.setExecutable( getJavadocExecutableName() ); + +// ------------------------------------------------ general javadoc arguments + if( classpath == null ) + classpath = Path.systemClasspath; + else + classpath = classpath.concatSystemClasspath( "ignore" ); + + if( !javadoc1 ) + { + toExecute.createArgument().setValue( "-classpath" ); + toExecute.createArgument().setPath( classpath ); + toExecute.createArgument().setValue( "-sourcepath" ); + toExecute.createArgument().setPath( sourcePath ); + } + else + { + toExecute.createArgument().setValue( "-classpath" ); + toExecute.createArgument().setValue( sourcePath.toString() + + System.getProperty( "path.separator" ) + classpath.toString() ); + } + + if( version && doclet == null ) + toExecute.createArgument().setValue( "-version" ); + if( author && doclet == null ) + toExecute.createArgument().setValue( "-author" ); + + if( javadoc1 || doclet == null ) + { + if( destDir == null ) + { + String msg = "destDir attribute must be set!"; + throw new BuildException( msg ); + } + } + +// --------------------------------- javadoc2 arguments for default doclet + +// XXX: how do we handle a custom doclet? + + if( !javadoc1 ) + { + if( doclet != null ) + { + if( doclet.getName() == null ) + { + throw new BuildException( "The doclet name must be specified.", location ); + } + else + { + toExecute.createArgument().setValue( "-doclet" ); + toExecute.createArgument().setValue( doclet.getName() ); + if( doclet.getPath() != null ) + { + toExecute.createArgument().setValue( "-docletpath" ); + toExecute.createArgument().setPath( doclet.getPath() ); + } + for( Enumeration e = doclet.getParams(); e.hasMoreElements(); ) + { + DocletParam param = ( DocletParam )e.nextElement(); + if( param.getName() == null ) + { + throw new BuildException( "Doclet parameters must have a name" ); + } + + toExecute.createArgument().setValue( param.getName() ); + if( param.getValue() != null ) + { + toExecute.createArgument().setValue( param.getValue() ); + } + } + } + } + if( bootclasspath != null ) + { + toExecute.createArgument().setValue( "-bootclasspath" ); + toExecute.createArgument().setPath( bootclasspath ); + } + + // add the links arguments + if( links.size() != 0 ) + { + for( Enumeration e = links.elements(); e.hasMoreElements(); ) + { + LinkArgument la = ( LinkArgument )e.nextElement(); + + if( la.getHref() == null ) + { + throw new BuildException( "Links must provide the URL to the external class documentation." ); + } + + if( la.isLinkOffline() ) + { + File packageListLocation = la.getPackagelistLoc(); + if( packageListLocation == null ) + { + throw new BuildException( "The package list location for link " + la.getHref() + + " must be provided because the link is offline" ); + } + File packageList = new File( packageListLocation, "package-list" ); + if( packageList.exists() ) + { + toExecute.createArgument().setValue( "-linkoffline" ); + toExecute.createArgument().setValue( la.getHref() ); + toExecute.createArgument().setValue( packageListLocation.getAbsolutePath() ); + } + else + { + log( "Warning: No package list was found at " + packageListLocation, + Project.MSG_VERBOSE ); + } + } + else + { + toExecute.createArgument().setValue( "-link" ); + toExecute.createArgument().setValue( la.getHref() ); + } + } + } + + // add the single group arguments + // Javadoc 1.2 rules: + // Multiple -group args allowed. + // Each arg includes 3 strings: -group [name] [packagelist]. + // Elements in [packagelist] are colon-delimited. + // An element in [packagelist] may end with the * wildcard. + + // Ant javadoc task rules for group attribute: + // Args are comma-delimited. + // Each arg is 2 space-delimited strings. + // E.g., group="XSLT_Packages org.apache.xalan.xslt*,XPath_Packages org.apache.xalan.xpath*" + if( group != null ) + { + StringTokenizer tok = new StringTokenizer( group, ",", false ); + while( tok.hasMoreTokens() ) + { + String grp = tok.nextToken().trim(); + int space = grp.indexOf( " " ); + if( space > 0 ) + { + String name = grp.substring( 0, space ); + String pkgList = grp.substring( space + 1 ); + toExecute.createArgument().setValue( "-group" ); + toExecute.createArgument().setValue( name ); + toExecute.createArgument().setValue( pkgList ); + } + } + } + + // add the group arguments + if( groups.size() != 0 ) + { + for( Enumeration e = groups.elements(); e.hasMoreElements(); ) + { + GroupArgument ga = ( GroupArgument )e.nextElement(); + String title = ga.getTitle(); + String packages = ga.getPackages(); + if( title == null || packages == null ) + { + throw new BuildException( "The title and packages must be specified for group elements." ); + } + toExecute.createArgument().setValue( "-group" ); + toExecute.createArgument().setValue( expand( title ) ); + toExecute.createArgument().setValue( packages ); + } + } + + } + + tmpList = null; + if( packageNames.size() > 0 ) + { + Vector packages = new Vector(); + Enumeration enum = packageNames.elements(); + while( enum.hasMoreElements() ) + { + PackageName pn = ( PackageName )enum.nextElement(); + String name = pn.getName().trim(); + if( name.endsWith( ".*" ) ) + { + packages.addElement( name ); + } + else + { + toExecute.createArgument().setValue( name ); + } + } + + Vector excludePackages = new Vector(); + if( excludePackageNames.size() > 0 ) + { + enum = excludePackageNames.elements(); + while( enum.hasMoreElements() ) + { + PackageName pn = ( PackageName )enum.nextElement(); + excludePackages.addElement( pn.getName().trim() ); + } + } + if( packages.size() > 0 ) + { + evaluatePackages( toExecute, sourcePath, packages, excludePackages ); + } + } + + if( sourceFiles.size() > 0 ) + { + PrintWriter srcListWriter = null; + try + { + + /** + * Write sourcefiles to a temporary file if requested. + */ + if( useExternalFile ) + { + if( tmpList == null ) + { + tmpList = fileUtils.createTempFile( "javadoc", "", null ); + toExecute.createArgument().setValue( "@" + tmpList.getAbsolutePath() ); + } + srcListWriter = new PrintWriter( new FileWriter( tmpList.getAbsolutePath(), + true ) ); + } + + Enumeration enum = sourceFiles.elements(); + while( enum.hasMoreElements() ) + { + SourceFile sf = ( SourceFile )enum.nextElement(); + String sourceFileName = sf.getFile().getAbsolutePath(); + if( useExternalFile ) + { + srcListWriter.println( sourceFileName ); + } + else + { + toExecute.createArgument().setValue( sourceFileName ); + } + } + + } + catch( IOException e ) + { + throw new BuildException( "Error creating temporary file", + e, location ); + } + finally + { + if( srcListWriter != null ) + { + srcListWriter.close(); + } + } + } + + if( packageList != null ) + { + toExecute.createArgument().setValue( "@" + packageList ); + } + log( "Javadoc args: " + toExecute, Project.MSG_VERBOSE ); + + log( "Javadoc execution", Project.MSG_INFO ); + + JavadocOutputStream out = new JavadocOutputStream( Project.MSG_INFO ); + JavadocOutputStream err = new JavadocOutputStream( Project.MSG_WARN ); + Execute exe = new Execute( new PumpStreamHandler( out, err ) ); + exe.setAntRun( project ); + + /* + * No reason to change the working directory as all filenames and + * path components have been resolved already. + * + * Avoid problems with command line length in some environments. + */ + exe.setWorkingDirectory( null ); + try + { + exe.setCommandline( toExecute.getCommandline() ); + int ret = exe.execute(); + if( ret != 0 && failOnError ) + { + throw new BuildException( "Javadoc returned " + ret, location ); + } + } + catch( IOException e ) + { + throw new BuildException( "Javadoc failed: " + e, e, location ); + } + finally + { + + if( tmpList != null ) + { + tmpList.delete(); + tmpList = null; + } + + out.logFlush(); + err.logFlush(); + try + { + out.close(); + err.close(); + } + catch( IOException e ) + {} + } + } + + /** + * Convenience method to expand properties. + * + * @param content Description of Parameter + * @return Description of the Returned Value + */ + protected String expand( String content ) + { + return project.replaceProperties( content ); + } + + private String getJavadocExecutableName() + { + // This is the most common extension case - exe for windows and OS/2, + // nothing for *nix. + String extension = Os.isFamily( "dos" ) ? ".exe" : ""; + + // Look for javadoc in the java.home/../bin directory. Unfortunately + // on Windows java.home doesn't always refer to the correct location, + // so we need to fall back to assuming javadoc is somewhere on the + // PATH. + File jdocExecutable = new File( System.getProperty( "java.home" ) + + "/../bin/javadoc" + extension ); + + if( jdocExecutable.exists() && !Os.isFamily( "netware" ) ) + { + return jdocExecutable.getAbsolutePath(); + } + else + { + if( !Os.isFamily( "netware" ) ) + { + log( "Unable to locate " + jdocExecutable.getAbsolutePath() + + ". Using \"javadoc\" instead.", Project.MSG_VERBOSE ); + } + return "javadoc"; + } + } + + private void add11ArgIf( boolean b, String arg ) + { + if( javadoc1 && b ) + { + cmd.createArgument().setValue( arg ); + } + } + + private void add12ArgIf( boolean b, String arg ) + { + if( !javadoc1 && b ) + { + cmd.createArgument().setValue( arg ); + } + } + + private void add12ArgIfNotEmpty( String key, String value ) + { + if( !javadoc1 ) + { + if( value != null && value.length() != 0 ) + { + cmd.createArgument().setValue( key ); + cmd.createArgument().setValue( value ); + } + else + { + project.log( this, + "Warning: Leaving out empty argument '" + key + "'", + Project.MSG_WARN ); + } + } + } + + + private void addArgIf( boolean b, String arg ) + { + if( b ) + { + cmd.createArgument().setValue( arg ); + } + } + + /** + * Given a source path, a list of package patterns, fill the given list with + * the packages found in that path subdirs matching one of the given + * patterns. + * + * @param toExecute Description of Parameter + * @param sourcePath Description of Parameter + * @param packages Description of Parameter + * @param excludePackages Description of Parameter + */ + private void evaluatePackages( Commandline toExecute, Path sourcePath, + Vector packages, Vector excludePackages ) + { + log( "Source path = " + sourcePath.toString(), Project.MSG_VERBOSE ); + StringBuffer msg = new StringBuffer( "Packages = " ); + for( int i = 0; i < packages.size(); i++ ) + { + if( i > 0 ) + { + msg.append( "," ); + } + msg.append( packages.elementAt( i ) ); + } + log( msg.toString(), Project.MSG_VERBOSE ); + + msg.setLength( 0 ); + msg.append( "Exclude Packages = " ); + for( int i = 0; i < excludePackages.size(); i++ ) + { + if( i > 0 ) + { + msg.append( "," ); + } + msg.append( excludePackages.elementAt( i ) ); + } + log( msg.toString(), Project.MSG_VERBOSE ); + + Vector addedPackages = new Vector(); + + String[] list = sourcePath.list(); + if( list == null ) + list = new String[0]; + + FileSet fs = new FileSet(); + fs.setDefaultexcludes( useDefaultExcludes ); + + Enumeration e = packages.elements(); + while( e.hasMoreElements() ) + { + String pkg = ( String )e.nextElement(); + pkg = pkg.replace( '.', '/' ); + if( pkg.endsWith( "*" ) ) + { + pkg += "*"; + } + + fs.createInclude().setName( pkg ); + }// while + + e = excludePackages.elements(); + while( e.hasMoreElements() ) + { + String pkg = ( String )e.nextElement(); + pkg = pkg.replace( '.', '/' ); + if( pkg.endsWith( "*" ) ) + { + pkg += "*"; + } + + fs.createExclude().setName( pkg ); + } + + PrintWriter packageListWriter = null; + try + { + if( useExternalFile ) + { + tmpList = fileUtils.createTempFile( "javadoc", "", null ); + toExecute.createArgument().setValue( "@" + tmpList.getAbsolutePath() ); + packageListWriter = new PrintWriter( new FileWriter( tmpList ) ); + } + + for( int j = 0; j < list.length; j++ ) + { + File source = project.resolveFile( list[j] ); + fs.setDir( source ); + + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] packageDirs = ds.getIncludedDirectories(); + + for( int i = 0; i < packageDirs.length; i++ ) + { + File pd = new File( source, packageDirs[i] ); + String[] files = pd.list( + new FilenameFilter() + { + public boolean accept( File dir1, String name ) + { + if( name.endsWith( ".java" ) ) + { + return true; + } + return false;// ignore dirs + } + } ); + + if( files.length > 0 ) + { + String pkgDir = packageDirs[i].replace( '/', '.' ).replace( '\\', '.' ); + if( !addedPackages.contains( pkgDir ) ) + { + if( useExternalFile ) + { + packageListWriter.println( pkgDir ); + } + else + { + toExecute.createArgument().setValue( pkgDir ); + } + addedPackages.addElement( pkgDir ); + } + } + } + } + } + catch( IOException ioex ) + { + throw new BuildException( "Error creating temporary file", + ioex, location ); + } + finally + { + if( packageListWriter != null ) + { + packageListWriter.close(); + } + } + } + + public static class AccessType extends EnumeratedAttribute + { + public String[] getValues() + { + // Protected first so if any GUI tool offers a default + // based on enum #0, it will be right. + return new String[]{"protected", "public", "package", "private"}; + } + } + + public static class Html + { + private StringBuffer text = new StringBuffer(); + + public String getText() + { + return text.toString(); + } + + public void addText( String t ) + { + text.append( t ); + } + } + + public static class PackageName + { + private String name; + + public void setName( String name ) + { + this.name = name; + } + + public String getName() + { + return name; + } + + public String toString() + { + return getName(); + } + } + + public static class SourceFile + { + private File file; + + public void setFile( File file ) + { + this.file = file; + } + + public File getFile() + { + return file; + } + } + + public class DocletInfo + { + + private Vector params = new Vector(); + private String name; + private Path path; + + public void setName( String name ) + { + this.name = name; + } + + public void setPath( Path path ) + { + if( this.path == null ) + { + this.path = path; + } + else + { + this.path.append( path ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new PathRef value + */ + public void setPathRef( Reference r ) + { + createPath().setRefid( r ); + } + + public String getName() + { + return name; + } + + public Enumeration getParams() + { + return params.elements(); + } + + public Path getPath() + { + return path; + } + + public DocletParam createParam() + { + DocletParam param = new DocletParam(); + params.addElement( param ); + + return param; + } + + public Path createPath() + { + if( path == null ) + { + path = new Path( getProject() ); + } + return path.createPath(); + } + } + + public class DocletParam + { + private String name; + private String value; + + public void setName( String name ) + { + this.name = name; + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getName() + { + return name; + } + + public String getValue() + { + return value; + } + } + + public class GroupArgument + { + private Vector packages = new Vector( 3 ); + private Html title; + + public GroupArgument() { } + + public void setPackages( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String p = tok.nextToken(); + PackageName pn = new PackageName(); + pn.setName( p ); + addPackage( pn ); + } + } + + public void setTitle( String src ) + { + Html h = new Html(); + h.addText( src ); + addTitle( h ); + } + + public String getPackages() + { + StringBuffer p = new StringBuffer(); + for( int i = 0; i < packages.size(); i++ ) + { + if( i > 0 ) + { + p.append( ":" ); + } + p.append( packages.elementAt( i ).toString() ); + } + return p.toString(); + } + + public String getTitle() + { + return title != null ? title.getText() : null; + } + + public void addPackage( PackageName pn ) + { + packages.addElement( pn ); + } + + public void addTitle( Html text ) + { + title = text; + } + } + + public class LinkArgument + { + private boolean offline = false; + private String href; + private File packagelistLoc; + + public LinkArgument() { } + + public void setHref( String hr ) + { + href = hr; + } + + public void setOffline( boolean offline ) + { + this.offline = offline; + } + + public void setPackagelistLoc( File src ) + { + packagelistLoc = src; + } + + public String getHref() + { + return href; + } + + public File getPackagelistLoc() + { + return packagelistLoc; + } + + public boolean isLinkOffline() + { + return offline; + } + } + + private class JavadocOutputStream extends LogOutputStream + { + + // + // Override the logging of output in order to filter out Generating + // messages. Generating messages are set to a priority of VERBOSE + // unless they appear after what could be an informational message. + // + private String queuedLine = null; + + JavadocOutputStream( int level ) + { + super( Javadoc.this, level ); + } + + + protected void logFlush() + { + if( queuedLine != null ) + { + super.processLine( queuedLine, Project.MSG_VERBOSE ); + queuedLine = null; + } + } + + protected void processLine( String line, int messageLevel ) + { + if( messageLevel == Project.MSG_INFO && line.startsWith( "Generating " ) ) + { + if( queuedLine != null ) + { + super.processLine( queuedLine, Project.MSG_VERBOSE ); + } + queuedLine = line; + } + else + { + if( queuedLine != null ) + { + if( line.startsWith( "Building " ) ) + super.processLine( queuedLine, Project.MSG_VERBOSE ); + else + super.processLine( queuedLine, Project.MSG_INFO ); + queuedLine = null; + } + super.processLine( line, messageLevel ); + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jikes.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jikes.java new file mode 100644 index 000000000..4e8700635 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Jikes.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Random; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Encapsulates a Jikes compiler, by directly executing an external process. + * + * @author skanthak@muehlheim.de + * @deprecated merged into the class Javac. + */ +public class Jikes +{ + protected String command; + protected JikesOutputParser jop; + protected Project project; + + /** + * Constructs a new Jikes obect. + * + * @param jop - Parser to send jike's output to + * @param command - name of jikes executeable + * @param project Description of Parameter + */ + protected Jikes( JikesOutputParser jop, String command, Project project ) + { + super(); + this.jop = jop; + this.command = command; + this.project = project; + } + + /** + * Do the compile with the specified arguments. + * + * @param args - arguments to pass to process on command line + */ + protected void compile( String[] args ) + { + String[] commandArray = null; + File tmpFile = null; + + try + { + String myos = System.getProperty( "os.name" ); + + // Windows has a 32k limit on total arg size, so + // create a temporary file to store all the arguments + + // There have been reports that 300 files could be compiled + // so 250 is a conservative approach + if( myos.toLowerCase().indexOf( "windows" ) >= 0 + && args.length > 250 ) + { + PrintWriter out = null; + try + { + tmpFile = new File( "jikes" + ( new Random( System.currentTimeMillis() ) ).nextLong() ); + out = new PrintWriter( new FileWriter( tmpFile ) ); + for( int i = 0; i < args.length; i++ ) + { + out.println( args[i] ); + } + out.flush(); + commandArray = new String[]{command, + "@" + tmpFile.getAbsolutePath()}; + } + catch( IOException e ) + { + throw new BuildException( "Error creating temporary file", e ); + } + finally + { + if( out != null ) + { + try + { + out.close(); + } + catch( Throwable t ) + {} + } + } + } + else + { + commandArray = new String[args.length + 1]; + commandArray[0] = command; + System.arraycopy( args, 0, commandArray, 1, args.length ); + } + + // We assume, that everything jikes writes goes to + // standard output, not to standard error. The option + // -Xstdout that is given to Jikes in Javac.doJikesCompile() + // should guarantee this. At least I hope so. :) + try + { + Execute exe = new Execute( jop ); + exe.setAntRun( project ); + exe.setWorkingDirectory( project.getBaseDir() ); + exe.setCommandline( commandArray ); + exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( "Error running Jikes compiler", e ); + } + } + finally + { + if( tmpFile != null ) + { + tmpFile.delete(); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JikesOutputParser.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JikesOutputParser.java new file mode 100644 index 000000000..3376ff97f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/JikesOutputParser.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Parses output from jikes and passes errors and warnings into the right + * logging channels of Project. TODO: Parsing could be much better + * + * @author skanthak@muehlheim.de + * @deprecated use Jikes' exit value to detect compilation failure. + */ +public class JikesOutputParser implements ExecuteStreamHandler +{ + protected boolean errorFlag = false; + protected boolean error = false; + + protected BufferedReader br; + protected boolean emacsMode;// no errors so far + protected int errors, warnings; + protected Task task; + + /** + * Construct a new Parser object + * + * @param task - task in whichs context we are called + * @param emacsMode Description of Parameter + */ + protected JikesOutputParser( Task task, boolean emacsMode ) + { + super(); + this.task = task; + this.emacsMode = emacsMode; + } + + /** + * Ignore. + * + * @param is The new ProcessErrorStream value + */ + public void setProcessErrorStream( InputStream is ) { } + + /** + * Ignore. + * + * @param os The new ProcessInputStream value + */ + public void setProcessInputStream( OutputStream os ) { } + + /** + * Set the inputstream + * + * @param is The new ProcessOutputStream value + * @exception IOException Description of Exception + */ + public void setProcessOutputStream( InputStream is ) + throws IOException + { + br = new BufferedReader( new InputStreamReader( is ) ); + } + + /** + * Invokes parseOutput. + * + * @exception IOException Description of Exception + */ + public void start() + throws IOException + { + parseOutput( br ); + } + + /** + * Ignore. + */ + public void stop() { } + + /** + * Indicate if there were errors during the compile + * + * @return if errors ocured + */ + protected boolean getErrorFlag() + { + return errorFlag; + } + + /** + * Parse the output of a jikes compiler + * + * @param reader - Reader used to read jikes's output + * @exception IOException Description of Exception + */ + protected void parseOutput( BufferedReader reader ) + throws IOException + { + if( emacsMode ) + parseEmacsOutput( reader ); + else + parseStandardOutput( reader ); + } + + private void setError( boolean err ) + { + error = err; + if( error ) + errorFlag = true; + } + + private void log( String line ) + { + if( !emacsMode ) + { + task.log( "", ( error ? Project.MSG_ERR : Project.MSG_WARN ) ); + } + task.log( line, ( error ? Project.MSG_ERR : Project.MSG_WARN ) ); + } + + private void parseEmacsOutput( BufferedReader reader ) + throws IOException + { + // This may change, if we add advanced parsing capabilities. + parseStandardOutput( reader ); + } + + private void parseStandardOutput( BufferedReader reader ) + throws IOException + { + String line; + String lower; + // We assume, that every output, jike does, stands for an error/warning + // XXX + // Is this correct? + + // TODO: + // A warning line, that shows code, which contains a variable + // error will cause some trouble. The parser should definitely + // be much better. + + while( ( line = reader.readLine() ) != null ) + { + lower = line.toLowerCase(); + if( line.trim().equals( "" ) ) + continue; + if( lower.indexOf( "error" ) != -1 ) + setError( true ); + else if( lower.indexOf( "warning" ) != -1 ) + setError( false ); + else + { + // If we don't know the type of the line + // and we are in emacs mode, it will be + // an error, because in this mode, jikes won't + // always print "error", but sometimes other + // keywords like "Syntax". We should look for + // all those keywords. + if( emacsMode ) + setError( true ); + } + log( line ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/KeySubst.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/KeySubst.java new file mode 100644 index 000000000..22fbf44f3 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/KeySubst.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Hashtable; +import java.util.StringTokenizer; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Keyword substitution. Input file is written to output file. Do not make input + * file same as output file. Keywords in input files look like this: + * + * @author Jon S. Stevens jon@clearink.com + * @foo@. See the docs for the setKeys method to understand how to do the + * substitutions. + * @deprecated KeySubst is deprecated. Use Filter + CopyDir instead. + */ +public class KeySubst extends Task +{ + private File source = null; + private File dest = null; + private String sep = "*"; + private Hashtable replacements = new Hashtable(); + + + public static void main( String[] args ) + { + try + { + Hashtable hash = new Hashtable(); + hash.put( "VERSION", "1.0.3" ); + hash.put( "b", "ffff" ); + System.out.println( KeySubst.replace( "$f ${VERSION} f ${b} jj $", hash ) ); + } + catch( Exception e ) + { + e.printStackTrace(); + } + } + + /** + * Does replacement on text using the hashtable of keys. + * + * @param origString Description of Parameter + * @param keys Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @returns the string with the replacements in it. + */ + public static String replace( String origString, Hashtable keys ) + throws BuildException + { + StringBuffer finalString = new StringBuffer(); + int index = 0; + int i = 0; + String key = null; + while( ( index = origString.indexOf( "${", i ) ) > -1 ) + { + key = origString.substring( index + 2, origString.indexOf( "}", index + 3 ) ); + finalString.append( origString.substring( i, index ) ); + if( keys.containsKey( key ) ) + { + finalString.append( keys.get( key ) ); + } + else + { + finalString.append( "${" ); + finalString.append( key ); + finalString.append( "}" ); + } + i = index + 3 + key.length(); + } + finalString.append( origString.substring( i ) ); + return finalString.toString(); + } + + /** + * Set the destination file. + * + * @param dest The new Dest value + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Format string is like this:

      + * + * name=value*name2=value

      + * + * Names are case sensitive.

      + * + * Use the setSep() method to change the * to something else if you need to + * use * as a name or value. + * + * @param keys The new Keys value + */ + public void setKeys( String keys ) + { + if( keys != null && keys.length() > 0 ) + { + StringTokenizer tok = + new StringTokenizer( keys, this.sep, false ); + while( tok.hasMoreTokens() ) + { + String token = tok.nextToken().trim(); + StringTokenizer itok = + new StringTokenizer( token, "=", false ); + + String name = itok.nextToken(); + String value = itok.nextToken(); +// log ( "Name: " + name ); +// log ( "Value: " + value ); + replacements.put( name, value ); + } + } + } + + /** + * Sets the seperator between name=value arguments in setKeys(). By default + * it is "*". + * + * @param sep The new Sep value + */ + public void setSep( String sep ) + { + this.sep = sep; + } + + /** + * Set the source file. + * + * @param s The new Src value + */ + public void setSrc( File s ) + { + this.source = s; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + log( "!! KeySubst is deprecated. Use Filter + CopyDir instead. !!" ); + log( "Performing Substitions" ); + if( source == null || dest == null ) + { + log( "Source and destinations must not be null" ); + return; + } + BufferedReader br = null; + BufferedWriter bw = null; + try + { + br = new BufferedReader( new FileReader( source ) ); + dest.delete(); + bw = new BufferedWriter( new FileWriter( dest ) ); + + String line = null; + String newline = null; + int length; + line = br.readLine(); + while( line != null ) + { + if( line.length() == 0 ) + { + bw.newLine(); + } + else + { + newline = KeySubst.replace( line, replacements ); + bw.write( newline ); + bw.newLine(); + } + line = br.readLine(); + } + bw.flush(); + bw.close(); + br.close(); + } + catch( IOException ioe ) + { + ioe.printStackTrace(); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogOutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogOutputStream.java new file mode 100644 index 000000000..f715beaaa --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogOutputStream.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + + +/** + * Logs each line written to this stream to the log system of ant. Tries to be + * smart about line separators.
      + * TODO: This class can be split to implement other line based processing of + * data written to the stream. + * + * @author thomas.haas@softwired-inc.com + */ +public class LogOutputStream extends OutputStream +{ + + private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + private boolean skip = false; + private int level = Project.MSG_INFO; + + private Task task; + + /** + * Creates a new instance of this class. + * + * @param task the task for whom to log + * @param level loglevel used to log data written to this stream. + */ + public LogOutputStream( Task task, int level ) + { + this.task = task; + this.level = level; + } + + public int getMessageLevel() + { + return level; + } + + + /** + * Writes all remaining + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + if( buffer.size() > 0 ) + processBuffer(); + super.close(); + } + + + /** + * Write the data to the buffer and flush the buffer, if a line separator is + * detected. + * + * @param cc data to log (byte). + * @exception IOException Description of Exception + */ + public void write( int cc ) + throws IOException + { + final byte c = ( byte )cc; + if( ( c == '\n' ) || ( c == '\r' ) ) + { + if( !skip ) + processBuffer(); + } + else + buffer.write( cc ); + skip = ( c == '\r' ); + } + + + /** + * Converts the buffer to a string and sends it to processLine + */ + protected void processBuffer() + { + processLine( buffer.toString() ); + buffer.reset(); + } + + /** + * Logs a line to the log system of ant. + * + * @param line the line to log. + */ + protected void processLine( String line ) + { + processLine( line, level ); + } + + /** + * Logs a line to the log system of ant. + * + * @param line the line to log. + * @param level Description of Parameter + */ + protected void processLine( String line, int level ) + { + task.log( line, level ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogStreamHandler.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogStreamHandler.java new file mode 100644 index 000000000..b7e6c849b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/LogStreamHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Logs standard output and error of a subprocess to the log system of ant. + * + * @author thomas.haas@softwired-inc.com + */ +public class LogStreamHandler extends PumpStreamHandler +{ + + /** + * Creates a new instance of this class. + * + * @param task the task for whom to log + * @param outlevel the loglevel used to log standard output + * @param errlevel the loglevel used to log standard error + */ + public LogStreamHandler( Task task, int outlevel, int errlevel ) + { + super( new LogOutputStream( task, outlevel ), + new LogOutputStream( task, errlevel ) ); + } + + public void stop() + { + super.stop(); + try + { + getErr().close(); + getOut().close(); + } + catch( IOException e ) + { + // plain impossible + throw new BuildException( e ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Manifest.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Manifest.java new file mode 100644 index 000000000..242b0fa5e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Manifest.java @@ -0,0 +1,950 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Class to manage Manifest information + * + * @author Conor MacNeill + * @author Stefan Bodewig + */ +public class Manifest extends Task +{ + /** + * The standard manifest version header + */ + public final static String ATTRIBUTE_MANIFEST_VERSION = "Manifest-Version"; + + /** + * The standard Signature Version header + */ + public final static String ATTRIBUTE_SIGNATURE_VERSION = "Signature-Version"; + + /** + * The Name Attribute is the first in a named section + */ + public final static String ATTRIBUTE_NAME = "Name"; + + /** + * The From Header is disallowed in a Manifest + */ + public final static String ATTRIBUTE_FROM = "From"; + + /** + * The Class-Path Header is special - it can be duplicated + */ + public final static String ATTRIBUTE_CLASSPATH = "class-path"; + + /** + * Default Manifest version if one is not specified + */ + public final static String DEFAULT_MANIFEST_VERSION = "1.0"; + + /** + * The max length of a line in a Manifest + */ + public final static int MAX_LINE_LENGTH = 70; + + /** + * The version of this manifest + */ + private String manifestVersion = DEFAULT_MANIFEST_VERSION; + + /** + * The main section of this manifest + */ + private Section mainSection = new Section(); + + /** + * The named sections of this manifest + */ + private Hashtable sections = new Hashtable(); + + private File manifestFile; + + private Mode mode; + + /** + * Construct an empty manifest + */ + public Manifest() + { + mode = new Mode(); + mode.setValue( "replace" ); + manifestVersion = null; + } + + /** + * Read a manifest file from the given reader + * + * @param r Description of Parameter + * @exception ManifestException Description of Exception + * @exception IOException Description of Exception + * @throws ManifestException if the manifest is not valid according to the + * JAR spec + * @throws IOException if the manifest cannot be read from the reader. + */ + public Manifest( Reader r ) + throws ManifestException, IOException + { + BufferedReader reader = new BufferedReader( r ); + // This should be the manifest version + String nextSectionName = mainSection.read( reader ); + String readManifestVersion = mainSection.getAttributeValue( ATTRIBUTE_MANIFEST_VERSION ); + if( readManifestVersion != null ) + { + manifestVersion = readManifestVersion; + mainSection.removeAttribute( ATTRIBUTE_MANIFEST_VERSION ); + } + + String line = null; + while( ( line = reader.readLine() ) != null ) + { + if( line.length() == 0 ) + { + continue; + } + + Section section = new Section(); + if( nextSectionName == null ) + { + Attribute sectionName = new Attribute( line ); + if( !sectionName.getName().equalsIgnoreCase( ATTRIBUTE_NAME ) ) + { + throw new ManifestException( "Manifest sections should start with a \"" + ATTRIBUTE_NAME + + "\" attribute and not \"" + sectionName.getName() + "\"" ); + } + nextSectionName = sectionName.getValue(); + } + else + { + // we have already started reading this section + // this line is the first attribute. set it and then let the normal + // read handle the rest + Attribute firstAttribute = new Attribute( line ); + section.addAttributeAndCheck( firstAttribute ); + } + + section.setName( nextSectionName ); + nextSectionName = section.read( reader ); + addConfiguredSection( section ); + } + } + + /** + * Construct a manifest from Ant's default manifest file. + * + * @return The DefaultManifest value + * @exception BuildException Description of Exception + */ + public static Manifest getDefaultManifest() + throws BuildException + { + try + { + String s = "/org/apache/tools/ant/defaultManifest.mf"; + InputStream in = Manifest.class.getResourceAsStream( s ); + if( in == null ) + { + throw new BuildException( "Could not find default manifest: " + s ); + } + try + { + return new Manifest( new InputStreamReader( in, "ASCII" ) ); + } + catch( UnsupportedEncodingException e ) + { + return new Manifest( new InputStreamReader( in ) ); + } + } + catch( ManifestException e ) + { + throw new BuildException( "Default manifest is invalid !!" ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read default manifest", e ); + } + } + + /** + * The name of the manifest file to write (if used as a task). + * + * @param f The new File value + */ + public void setFile( File f ) + { + manifestFile = f; + } + + /** + * Shall we update or replace an existing manifest? + * + * @param m The new Mode value + */ + public void setMode( Mode m ) + { + mode = m; + } + + /** + * Get the warnings for this manifest. + * + * @return an enumeration of warning strings + */ + public Enumeration getWarnings() + { + Vector warnings = new Vector(); + + for( Enumeration e2 = mainSection.getWarnings(); e2.hasMoreElements(); ) + { + warnings.addElement( e2.nextElement() ); + } + + // create a vector and add in the warnings for all the sections + for( Enumeration e = sections.elements(); e.hasMoreElements(); ) + { + Section section = ( Section )e.nextElement(); + for( Enumeration e2 = section.getWarnings(); e2.hasMoreElements(); ) + { + warnings.addElement( e2.nextElement() ); + } + } + + return warnings.elements(); + } + + public void addConfiguredAttribute( Attribute attribute ) + throws ManifestException + { + mainSection.addConfiguredAttribute( attribute ); + } + + public void addConfiguredSection( Section section ) + throws ManifestException + { + if( section.getName() == null ) + { + throw new BuildException( "Sections must have a name" ); + } + sections.put( section.getName().toLowerCase(), section ); + } + + public boolean equals( Object rhs ) + { + if( !( rhs instanceof Manifest ) ) + { + return false; + } + + Manifest rhsManifest = ( Manifest )rhs; + if( manifestVersion == null ) + { + if( rhsManifest.manifestVersion != null ) + { + return false; + } + } + else if( !manifestVersion.equals( rhsManifest.manifestVersion ) ) + { + return false; + } + if( sections.size() != rhsManifest.sections.size() ) + { + return false; + } + + if( !mainSection.equals( rhsManifest.mainSection ) ) + { + return false; + } + + for( Enumeration e = sections.elements(); e.hasMoreElements(); ) + { + Section section = ( Section )e.nextElement(); + Section rhsSection = ( Section )rhsManifest.sections.get( section.getName().toLowerCase() ); + if( !section.equals( rhsSection ) ) + { + return false; + } + } + + return true; + } + + /** + * Create or update the Manifest when used as a task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( manifestFile == null ) + { + throw new BuildException( "the file attribute is required" ); + } + + Manifest toWrite = getDefaultManifest(); + + if( mode.getValue().equals( "update" ) && manifestFile.exists() ) + { + FileReader f = null; + try + { + f = new FileReader( manifestFile ); + toWrite.merge( new Manifest( f ) ); + } + catch( ManifestException m ) + { + throw new BuildException( "Existing manifest " + manifestFile + + " is invalid", m, location ); + } + catch( IOException e ) + { + throw new BuildException( "Failed to read " + manifestFile, + e, location ); + } + finally + { + if( f != null ) + { + try + { + f.close(); + } + catch( IOException e ) + {} + } + } + } + + try + { + toWrite.merge( this ); + } + catch( ManifestException m ) + { + throw new BuildException( "Manifest is invalid", m, location ); + } + + PrintWriter w = null; + try + { + w = new PrintWriter( new FileWriter( manifestFile ) ); + toWrite.write( w ); + } + catch( IOException e ) + { + throw new BuildException( "Failed to write " + manifestFile, + e, location ); + } + finally + { + if( w != null ) + { + w.close(); + } + } + } + + /** + * Merge the contents of the given manifest into this manifest + * + * @param other the Manifest to be merged with this one. + * @throws ManifestException if there is a problem merging the manfest + * according to the Manifest spec. + */ + public void merge( Manifest other ) + throws ManifestException + { + if( other.manifestVersion != null ) + { + manifestVersion = other.manifestVersion; + } + mainSection.merge( other.mainSection ); + for( Enumeration e = other.sections.keys(); e.hasMoreElements(); ) + { + String sectionName = ( String )e.nextElement(); + Section ourSection = ( Section )sections.get( sectionName ); + Section otherSection = ( Section )other.sections.get( sectionName ); + if( ourSection == null ) + { + sections.put( sectionName.toLowerCase(), otherSection ); + } + else + { + ourSection.merge( otherSection ); + } + } + + } + + /** + * Convert the manifest to its string representation + * + * @return a multiline string with the Manifest as it appears in a Manifest + * file. + */ + public String toString() + { + StringWriter sw = new StringWriter(); + try + { + write( new PrintWriter( sw ) ); + } + catch( IOException e ) + { + return null; + } + return sw.toString(); + } + + /** + * Write the manifest out to a print writer. + * + * @param writer the Writer to which the manifest is written + * @throws IOException if the manifest cannot be written + */ + public void write( PrintWriter writer ) + throws IOException + { + writer.println( ATTRIBUTE_MANIFEST_VERSION + ": " + manifestVersion ); + String signatureVersion = mainSection.getAttributeValue( ATTRIBUTE_SIGNATURE_VERSION ); + if( signatureVersion != null ) + { + writer.println( ATTRIBUTE_SIGNATURE_VERSION + ": " + signatureVersion ); + mainSection.removeAttribute( ATTRIBUTE_SIGNATURE_VERSION ); + } + mainSection.write( writer ); + if( signatureVersion != null ) + { + try + { + mainSection.addConfiguredAttribute( new Attribute( ATTRIBUTE_SIGNATURE_VERSION, signatureVersion ) ); + } + catch( ManifestException e ) + { + // shouldn't happen - ignore + } + } + + for( Enumeration e = sections.elements(); e.hasMoreElements(); ) + { + Section section = ( Section )e.nextElement(); + section.write( writer ); + } + } + + /** + * Class to hold manifest attributes + * + * @author RT + */ + public static class Attribute + { + /** + * The attribute's name + */ + private String name = null; + + /** + * The attribute's value + */ + private String value = null; + + /** + * Construct an empty attribute + */ + public Attribute() { } + + /** + * Construct an attribute by parsing a line from the Manifest + * + * @param line the line containing the attribute name and value + * @exception ManifestException Description of Exception + * @throws ManifestException if the line is not valid + */ + public Attribute( String line ) + throws ManifestException + { + parse( line ); + } + + /** + * Construct a manifest by specifying its name and value + * + * @param name the attribute's name + * @param value the Attribute's value + */ + public Attribute( String name, String value ) + { + this.name = name; + this.value = value; + } + + /** + * Set the Attribute's name + * + * @param name the attribute's name + */ + public void setName( String name ) + { + this.name = name; + } + + /** + * Set the Attribute's value + * + * @param value the attribute's value + */ + public void setValue( String value ) + { + this.value = value; + } + + /** + * Get the Attribute's name + * + * @return the attribute's name. + */ + public String getName() + { + return name; + } + + /** + * Get the Attribute's value + * + * @return the attribute's value. + */ + public String getValue() + { + return value; + } + + /** + * Add a continuation line from the Manifest file When lines are too + * long in a manifest, they are continued on the next line by starting + * with a space. This method adds the continuation data to the attribute + * value by skipping the first character. + * + * @param line The feature to be added to the Continuation attribute + */ + public void addContinuation( String line ) + { + value += line.substring( 1 ); + } + + public boolean equals( Object rhs ) + { + if( !( rhs instanceof Attribute ) ) + { + return false; + } + + Attribute rhsAttribute = ( Attribute )rhs; + return ( name != null && rhsAttribute.name != null && + name.toLowerCase().equals( rhsAttribute.name.toLowerCase() ) && + value != null && value.equals( rhsAttribute.value ) ); + } + + /** + * Parse a line into name and value pairs + * + * @param line the line to be parsed + * @throws ManifestException if the line does not contain a colon + * separating the name and value + */ + public void parse( String line ) + throws ManifestException + { + int index = line.indexOf( ": " ); + if( index == -1 ) + { + throw new ManifestException( "Manifest line \"" + line + "\" is not valid as it does not " + + "contain a name and a value separated by ': ' " ); + } + name = line.substring( 0, index ); + value = line.substring( index + 2 ); + } + + /** + * Write the attribute out to a print writer. + * + * @param writer the Writer to which the attribute is written + * @throws IOException if the attribte value cannot be written + */ + public void write( PrintWriter writer ) + throws IOException + { + String line = name + ": " + value; + while( line.getBytes().length > MAX_LINE_LENGTH ) + { + // try to find a MAX_LINE_LENGTH byte section + int breakIndex = MAX_LINE_LENGTH; + String section = line.substring( 0, breakIndex ); + while( section.getBytes().length > MAX_LINE_LENGTH && breakIndex > 0 ) + { + breakIndex--; + section = line.substring( 0, breakIndex ); + } + if( breakIndex == 0 ) + { + throw new IOException( "Unable to write manifest line " + name + ": " + value ); + } + writer.println( section ); + line = " " + line.substring( breakIndex ); + } + writer.println( line ); + } + } + + /** + * Helper class for Manifest's mode attribute. + * + * @author RT + */ + public static class Mode extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"update", "replace"}; + } + } + + /** + * Class to represent an individual section in the Manifest. A section + * consists of a set of attribute values, separated from other sections by a + * blank line. + * + * @author RT + */ + public static class Section + { + private Vector warnings = new Vector(); + + /** + * The section's name if any. The main section in a manifest is unnamed. + */ + private String name = null; + + /** + * The section's attributes. + */ + private Hashtable attributes = new Hashtable(); + + /** + * Set the Section's name + * + * @param name the section's name + */ + public void setName( String name ) + { + this.name = name; + } + + /** + * Get the value of the attribute with the name given. + * + * @param attributeName the name of the attribute to be returned. + * @return the attribute's value or null if the attribute does not exist + * in the section + */ + public String getAttributeValue( String attributeName ) + { + Object attribute = attributes.get( attributeName.toLowerCase() ); + if( attribute == null ) + { + return null; + } + if( attribute instanceof Attribute ) + { + return ( ( Attribute )attribute ).getValue(); + } + else + { + String value = ""; + for( Enumeration e = ( ( Vector )attribute ).elements(); e.hasMoreElements(); ) + { + Attribute classpathAttribute = ( Attribute )e.nextElement(); + value += classpathAttribute.getValue() + " "; + } + return value.trim(); + } + } + + /** + * Get the Section's name + * + * @return the section's name. + */ + public String getName() + { + return name; + } + + public Enumeration getWarnings() + { + return warnings.elements(); + } + + /** + * Add an attribute to the section + * + * @param attribute the attribute to be added. + * @return the value of the attribute if it is a name attribute - null + * other wise + * @throws ManifestException if the attribute already exists in this + * section. + */ + public String addAttributeAndCheck( Attribute attribute ) + throws ManifestException + { + if( attribute.getName() == null || attribute.getValue() == null ) + { + throw new BuildException( "Attributes must have name and value" ); + } + if( attribute.getName().equalsIgnoreCase( ATTRIBUTE_NAME ) ) + { + warnings.addElement( "\"" + ATTRIBUTE_NAME + "\" attributes should not occur in the " + + "main section and must be the first element in all " + + "other sections: \"" + attribute.getName() + ": " + attribute.getValue() + "\"" ); + return attribute.getValue(); + } + + if( attribute.getName().toLowerCase().startsWith( ATTRIBUTE_FROM.toLowerCase() ) ) + { + warnings.addElement( "Manifest attributes should not start with \"" + + ATTRIBUTE_FROM + "\" in \"" + attribute.getName() + ": " + attribute.getValue() + "\"" ); + } + else + { + // classpath attributes go into a vector + String attributeName = attribute.getName().toLowerCase(); + if( attributeName.equals( ATTRIBUTE_CLASSPATH ) ) + { + Vector classpathAttrs = ( Vector )attributes.get( attributeName ); + if( classpathAttrs == null ) + { + classpathAttrs = new Vector(); + attributes.put( attributeName, classpathAttrs ); + } + classpathAttrs.addElement( attribute ); + } + else if( attributes.containsKey( attributeName ) ) + { + throw new ManifestException( "The attribute \"" + attribute.getName() + "\" may not " + + "occur more than once in the same section" ); + } + else + { + attributes.put( attributeName, attribute ); + } + } + return null; + } + + public void addConfiguredAttribute( Attribute attribute ) + throws ManifestException + { + String check = addAttributeAndCheck( attribute ); + if( check != null ) + { + throw new BuildException( "Specify the section name using the \"name\" attribute of the

      element rather " + + "than using a \"Name\" manifest attribute" ); + } + } + + public boolean equals( Object rhs ) + { + if( !( rhs instanceof Section ) ) + { + return false; + } + + Section rhsSection = ( Section )rhs; + if( attributes.size() != rhsSection.attributes.size() ) + { + return false; + } + + for( Enumeration e = attributes.elements(); e.hasMoreElements(); ) + { + Attribute attribute = ( Attribute )e.nextElement(); + Attribute rshAttribute = ( Attribute )rhsSection.attributes.get( attribute.getName().toLowerCase() ); + if( !attribute.equals( rshAttribute ) ) + { + return false; + } + } + + return true; + } + + /** + * Merge in another section + * + * @param section the section to be merged with this one. + * @throws ManifestException if the sections cannot be merged. + */ + public void merge( Section section ) + throws ManifestException + { + if( name == null && section.getName() != null || + name != null && !( name.equalsIgnoreCase( section.getName() ) ) ) + { + throw new ManifestException( "Unable to merge sections with different names" ); + } + + for( Enumeration e = section.attributes.keys(); e.hasMoreElements(); ) + { + String attributeName = ( String )e.nextElement(); + if( attributeName.equals( ATTRIBUTE_CLASSPATH ) && + attributes.containsKey( attributeName ) ) + { + // classpath entries are vetors which are merged + Vector classpathAttrs = ( Vector )section.attributes.get( attributeName ); + Vector ourClasspathAttrs = ( Vector )attributes.get( attributeName ); + for( Enumeration e2 = classpathAttrs.elements(); e2.hasMoreElements(); ) + { + ourClasspathAttrs.addElement( e2.nextElement() ); + } + } + else + { + // the merge file always wins + attributes.put( attributeName, section.attributes.get( attributeName ) ); + } + } + + // add in the warnings + for( Enumeration e = section.warnings.elements(); e.hasMoreElements(); ) + { + warnings.addElement( e.nextElement() ); + } + } + + /** + * Read a section through a reader + * + * @param reader the reader from which the section is read + * @return the name of the next section if it has been read as part of + * this section - This only happens if the Manifest is malformed. + * @throws ManifestException if the section is not valid according to + * the JAR spec + * @throws IOException if the section cannot be read from the reader. + */ + public String read( BufferedReader reader ) + throws ManifestException, IOException + { + Attribute attribute = null; + while( true ) + { + String line = reader.readLine(); + if( line == null || line.length() == 0 ) + { + return null; + } + if( line.charAt( 0 ) == ' ' ) + { + // continuation line + if( attribute == null ) + { + if( name != null ) + { + // a continuation on the first line is a continuation of the name - concatenate + // this line and the name + name += line.substring( 1 ); + } + else + { + throw new ManifestException( "Can't start an attribute with a continuation line " + line ); + } + } + else + { + attribute.addContinuation( line ); + } + } + else + { + attribute = new Attribute( line ); + String nameReadAhead = addAttributeAndCheck( attribute ); + if( nameReadAhead != null ) + { + return nameReadAhead; + } + } + } + } + + /** + * Remove tge given attribute from the section + * + * @param attributeName the name of the attribute to be removed. + */ + public void removeAttribute( String attributeName ) + { + attributes.remove( attributeName.toLowerCase() ); + } + + /** + * Write the section out to a print writer. + * + * @param writer the Writer to which the section is written + * @throws IOException if the section cannot be written + */ + public void write( PrintWriter writer ) + throws IOException + { + if( name != null ) + { + Attribute nameAttr = new Attribute( ATTRIBUTE_NAME, name ); + nameAttr.write( writer ); + } + for( Enumeration e = attributes.elements(); e.hasMoreElements(); ) + { + Object object = e.nextElement(); + if( object instanceof Attribute ) + { + Attribute attribute = ( Attribute )object; + attribute.write( writer ); + } + else + { + Vector attrList = ( Vector )object; + for( Enumeration e2 = attrList.elements(); e2.hasMoreElements(); ) + { + Attribute attribute = ( Attribute )e2.nextElement(); + attribute.write( writer ); + } + } + } + writer.println(); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ManifestException.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ManifestException.java new file mode 100644 index 000000000..813da555e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ManifestException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + + + +/** + * Exception thrown indicating problems in a JAR Manifest + * + * @author Conor MacNeill + */ +public class ManifestException extends Exception +{ + + /** + * Constructs an exception with the given descriptive message. + * + * @param msg Description of or information about the exception. + */ + public ManifestException( String msg ) + { + super( msg ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/MatchingTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/MatchingTask.java new file mode 100644 index 000000000..a03cb9be7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/MatchingTask.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; + +/** + * This is an abstract task that should be used by all those tasks that require + * to include or exclude files based on pattern matching. + * + * @author Arnout J. Kuiper ajkuiper@wxs.nl + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Sam Ruby rubys@us.ibm.com + * @author Jon S. Stevens jon@clearink.com + * @author Stefan Bodewig + */ + +public abstract class MatchingTask extends Task +{ + + protected boolean useDefaultExcludes = true; + protected FileSet fileset = new FileSet(); + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + fileset.setExcludes( excludes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param excludesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setExcludesfile( File excludesfile ) + { + fileset.setExcludesfile( excludesfile ); + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space. + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + fileset.setIncludes( includes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param includesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setIncludesfile( File includesfile ) + { + fileset.setIncludesfile( includesfile ); + } + + /** + * List of filenames and directory names to not include. They should be + * either , or " " (space) separated. The ignored files will be logged. + * + * @param ignoreString the string containing the files to ignore. + */ + public void XsetIgnore( String ignoreString ) + { + log( "The ignore attribute is deprecated." + + "Please use the excludes attribute.", + Project.MSG_WARN ); + if( ignoreString != null && ignoreString.length() > 0 ) + { + Vector tmpExcludes = new Vector(); + StringTokenizer tok = new StringTokenizer( ignoreString, ", ", false ); + while( tok.hasMoreTokens() ) + { + createExclude().setName( "**/" + tok.nextToken().trim() + "/**" ); + } + } + } + + /** + * Set this to be the items in the base directory that you want to be + * included. You can also specify "*" for the items (ie: items="*") and it + * will include all the items in the base directory. + * + * @param itemString the string containing the files to include. + */ + public void XsetItems( String itemString ) + { + log( "The items attribute is deprecated. " + + "Please use the includes attribute.", + Project.MSG_WARN ); + if( itemString == null || itemString.equals( "*" ) + || itemString.equals( "." ) ) + { + createInclude().setName( "**" ); + } + else + { + StringTokenizer tok = new StringTokenizer( itemString, ", " ); + while( tok.hasMoreTokens() ) + { + String pattern = tok.nextToken().trim(); + if( pattern.length() > 0 ) + { + createInclude().setName( pattern + "/**" ); + } + } + } + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + return fileset.createExclude(); + } + + /** + * add a name entry on the include files list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExcludesFile() + { + return fileset.createExcludesFile(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + return fileset.createInclude(); + } + + /** + * add a name entry on the include files list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createIncludesFile() + { + return fileset.createIncludesFile(); + } + + /** + * add a set of patterns + * + * @return Description of the Returned Value + */ + public PatternSet createPatternSet() + { + return fileset.createPatternSet(); + } + + /** + * Returns the directory scanner needed to access the files to process. + * + * @param baseDir Description of Parameter + * @return The DirectoryScanner value + */ + protected DirectoryScanner getDirectoryScanner( File baseDir ) + { + fileset.setDir( baseDir ); + fileset.setDefaultexcludes( useDefaultExcludes ); + return fileset.getDirectoryScanner( project ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Mkdir.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Mkdir.java new file mode 100644 index 000000000..c323430a4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Mkdir.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +/** + * Creates a given directory. + * + * @author duncan@x180.com + */ + +public class Mkdir extends Task +{ + + private File dir; + + public void setDir( File dir ) + { + this.dir = dir; + } + + public void execute() + throws BuildException + { + if( dir == null ) + { + throw new BuildException( "dir attribute is required", location ); + } + + if( dir.isFile() ) + { + throw new BuildException( "Unable to create directory as a file already exists with that name: " + dir.getAbsolutePath() ); + } + + if( !dir.exists() ) + { + boolean result = dir.mkdirs(); + if( result == false ) + { + String msg = "Directory " + dir.getAbsolutePath() + " creation was not " + + "successful for an unknown reason"; + throw new BuildException( msg, location ); + } + log( "Created dir: " + dir.getAbsolutePath() ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Move.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Move.java new file mode 100644 index 000000000..75811008d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Move.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; + +/** + * Moves a file or directory to a new file or directory. By default, the + * destination is overwriten when existing. When overwrite is turned off, then + * files are only moved if the source file is newer than the destination file, + * or when the destination file does not exist.

      + * + * Source files and directories are only deleted when the file or directory has + * been copied to the destination successfully. Filtering also works.

      + * + * This implementation is based on Arnout Kuiper's initial design document, the + * following mailing list discussions, and the copyfile/copydir tasks.

      + * + * @author Glenn McAllister glennm@ca.ibm.com + * + * @author Magesh Umasankar + */ +public class Move extends Copy +{ + + public Move() + { + super(); + forceOverwrite = true; + } + + /** + * Go and delete the directory tree. + * + * @param d Description of Parameter + */ + protected void deleteDir( File d ) + { + String[] list = d.list(); + if( list == null ) + return;// on an io error list() can return null + + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( d, s ); + if( f.isDirectory() ) + { + deleteDir( f ); + } + else + { + throw new BuildException( "UNEXPECTED ERROR - The file " + f.getAbsolutePath() + " should not exist!" ); + } + } + log( "Deleting directory " + d.getAbsolutePath(), verbosity ); + if( !d.delete() ) + { + throw new BuildException( "Unable to delete directory " + d.getAbsolutePath() ); + } + } + +//************************************************************************ +// protected and private methods +//************************************************************************ + + protected void doFileOperations() + { + //Attempt complete directory renames, if any, first. + if( completeDirMap.size() > 0 ) + { + Enumeration e = completeDirMap.keys(); + while( e.hasMoreElements() ) + { + File fromDir = ( File )e.nextElement(); + File toDir = ( File )completeDirMap.get( fromDir ); + try + { + log( "Attempting to rename dir: " + fromDir + + " to " + toDir, verbosity ); + renameFile( fromDir, toDir, filtering, forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Failed to rename dir " + fromDir + + " to " + toDir + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + if( fileCopyMap.size() > 0 ) + {// files to move + log( "Moving " + fileCopyMap.size() + " files to " + + destDir.getAbsolutePath() ); + + Enumeration e = fileCopyMap.keys(); + while( e.hasMoreElements() ) + { + String fromFile = ( String )e.nextElement(); + String toFile = ( String )fileCopyMap.get( fromFile ); + + if( fromFile.equals( toFile ) ) + { + log( "Skipping self-move of " + fromFile, verbosity ); + continue; + } + + boolean moved = false; + File f = new File( fromFile ); + + if( f.exists() ) + {//Is this file still available to be moved? + File d = new File( toFile ); + + try + { + log( "Attempting to rename: " + fromFile + + " to " + toFile, verbosity ); + moved = renameFile( f, d, filtering, forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Failed to rename " + fromFile + + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + + if( !moved ) + { + try + { + log( "Moving " + fromFile + " to " + toFile, verbosity ); + + FilterSetCollection executionFilters = new FilterSetCollection(); + if( filtering ) + { + executionFilters.addFilterSet( project.getGlobalFilterSet() ); + } + for( Enumeration filterEnum = getFilterSets().elements(); filterEnum.hasMoreElements(); ) + { + executionFilters.addFilterSet( ( FilterSet )filterEnum.nextElement() ); + } + getFileUtils().copyFile( f, d, executionFilters, + forceOverwrite ); + + f = new File( fromFile ); + if( !f.delete() ) + { + throw new BuildException( "Unable to delete file " + + f.getAbsolutePath() ); + } + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + } + } + + if( includeEmpty ) + { + Enumeration e = dirCopyMap.elements(); + int count = 0; + while( e.hasMoreElements() ) + { + File d = new File( ( String )e.nextElement() ); + if( !d.exists() ) + { + if( !d.mkdirs() ) + { + log( "Unable to create directory " + d.getAbsolutePath(), Project.MSG_ERR ); + } + else + { + count++; + } + } + } + + if( count > 0 ) + { + log( "Moved " + count + " empty directories to " + destDir.getAbsolutePath() ); + } + } + + if( filesets.size() > 0 ) + { + Enumeration e = filesets.elements(); + while( e.hasMoreElements() ) + { + FileSet fs = ( FileSet )e.nextElement(); + File dir = fs.getDir( project ); + + if( okToDelete( dir ) ) + { + deleteDir( dir ); + } + } + } + } + + /** + * Its only ok to delete a directory tree if there are no files in it. + * + * @param d Description of Parameter + * @return Description of the Returned Value + */ + protected boolean okToDelete( File d ) + { + String[] list = d.list(); + if( list == null ) + return false;// maybe io error? + + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( d, s ); + if( f.isDirectory() ) + { + if( !okToDelete( f ) ) + return false; + } + else + { + return false;// found a file + } + } + + return true; + } + + /** + * Attempts to rename a file from a source to a destination. If overwrite is + * set to true, this method overwrites existing file even if the destination + * file is newer. Otherwise, the source file is renamed only if the + * destination file is older than it. Method then checks if token filtering + * is used. If it is, this method returns false assuming it is the + * responsibility to the copyFile method. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @throws IOException + */ + protected boolean renameFile( File sourceFile, File destFile, + boolean filtering, boolean overwrite ) + throws IOException, BuildException + { + + boolean renamed = true; + if( !filtering ) + { + // ensure that parent dir of dest file exists! + // not using getParentFile method to stay 1.1 compat + String parentPath = destFile.getParent(); + if( parentPath != null ) + { + File parent = new File( parentPath ); + if( !parent.exists() ) + { + parent.mkdirs(); + } + } + + if( destFile.exists() ) + { + if( !destFile.delete() ) + { + throw new BuildException( "Unable to remove existing file " + + destFile ); + } + } + renamed = sourceFile.renameTo( destFile ); + } + else + { + renamed = false; + } + return renamed; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Pack.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Pack.java new file mode 100644 index 000000000..3a9d0701d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Pack.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Abstract Base class for pack tasks. + * + * @author Magesh Umasankar + */ + +public abstract class Pack extends Task +{ + protected File source; + + protected File zipFile; + + public void setSrc( File src ) + { + source = src; + } + + public void setZipfile( File zipFile ) + { + this.zipFile = zipFile; + } + + public void execute() + throws BuildException + { + validate(); + log( "Building: " + zipFile.getAbsolutePath() ); + pack(); + } + + protected abstract void pack(); + + protected void zipFile( File file, OutputStream zOut ) + throws IOException + { + FileInputStream fIn = new FileInputStream( file ); + try + { + zipFile( fIn, zOut ); + } + finally + { + fIn.close(); + } + } + + private void validate() + { + if( zipFile == null ) + { + throw new BuildException( "zipfile attribute is required", location ); + } + + if( source == null ) + { + throw new BuildException( "src attribute is required", location ); + } + + if( source.isDirectory() ) + { + throw new BuildException( "Src attribute must not " + + "represent a directory!", location ); + } + } + + private void zipFile( InputStream in, OutputStream zOut ) + throws IOException + { + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + zOut.write( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Parallel.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Parallel.java new file mode 100644 index 000000000..1203369ac --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Parallel.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.TaskContainer; + + +/** + * Implements a multi threaded task execution.

      + * + * + * + * @author Thomas Christen chr@active.ch + * @author Conor MacNeill + */ +public class Parallel extends Task + implements TaskContainer +{ + + /** + * Collection holding the nested tasks + */ + private Vector nestedTasks = new Vector(); + + + /** + * Add a nested task to execute parallel (asynchron).

      + * + * + * + * @param nestedTask Nested task to be executed in parallel + * @exception BuildException Description of Exception + */ + public void addTask( Task nestedTask ) + throws BuildException + { + nestedTasks.addElement( nestedTask ); + } + + /** + * Block execution until the specified time or for a specified amount of + * milliseconds and if defined, execute the wait status. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + TaskThread[] threads = new TaskThread[nestedTasks.size()]; + int threadNumber = 0; + for( Enumeration e = nestedTasks.elements(); e.hasMoreElements(); threadNumber++ ) + { + Task nestedTask = ( Task )e.nextElement(); + threads[threadNumber] = new TaskThread( threadNumber, nestedTask ); + } + + // now start all threads + for( int i = 0; i < threads.length; ++i ) + { + threads[i].start(); + } + + // now join to all the threads + for( int i = 0; i < threads.length; ++i ) + { + try + { + threads[i].join(); + } + catch( InterruptedException ie ) + { + // who would interrupt me at a time like this? + } + } + + // now did any of the threads throw an exception + StringBuffer exceptionMessage = new StringBuffer(); + String lSep = System.getProperty( "line.separator" ); + int numExceptions = 0; + Throwable firstException = null; + Location firstLocation = Location.UNKNOWN_LOCATION; + ; + for( int i = 0; i < threads.length; ++i ) + { + Throwable t = threads[i].getException(); + if( t != null ) + { + numExceptions++; + if( firstException == null ) + { + firstException = t; + } + if( t instanceof BuildException && + firstLocation == Location.UNKNOWN_LOCATION ) + { + firstLocation = ( ( BuildException )t ).getLocation(); + } + exceptionMessage.append( lSep ); + exceptionMessage.append( t.getMessage() ); + } + } + + if( numExceptions == 1 ) + { + if( firstException instanceof BuildException ) + { + throw ( BuildException )firstException; + } + else + { + throw new BuildException( firstException ); + } + } + else if( numExceptions > 1 ) + { + throw new BuildException( exceptionMessage.toString(), firstLocation ); + } + } + + class TaskThread extends Thread + { + private Throwable exception; + private Task task; + private int taskNumber; + + /** + * Construct a new TaskThread

      + * + * + * + * @param task the Task to be executed in a seperate thread + * @param taskNumber Description of Parameter + */ + TaskThread( int taskNumber, Task task ) + { + this.task = task; + this.taskNumber = taskNumber; + } + + public Throwable getException() + { + return exception; + } + + /** + * Executes the task within a thread and takes care about Exceptions + * raised within the task. + */ + public void run() + { + try + { + task.perform(); + } + catch( Throwable t ) + { + exception = t; + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Patch.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Patch.java new file mode 100644 index 000000000..8c7657b12 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Patch.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; + +/** + * Task as a layer on top of patch. Patch applies a diff file to an original. + * + * @author Stefan Bodewig + */ +public class Patch extends Task +{ + private boolean havePatchfile = false; + private Commandline cmd = new Commandline(); + + private File originalFile; + + /** + * Shall patch write backups. + * + * @param backups The new Backups value + */ + public void setBackups( boolean backups ) + { + if( backups ) + { + cmd.createArgument().setValue( "-b" ); + } + } + + /** + * Ignore whitespace differences. + * + * @param ignore The new Ignorewhitespace value + */ + public void setIgnorewhitespace( boolean ignore ) + { + if( ignore ) + { + cmd.createArgument().setValue( "-l" ); + } + } + + /** + * The file to patch. + * + * @param file The new Originalfile value + */ + public void setOriginalfile( File file ) + { + originalFile = file; + } + + /** + * The file containing the diff output. + * + * @param file The new Patchfile value + */ + public void setPatchfile( File file ) + { + if( !file.exists() ) + { + throw new BuildException( "patchfile " + file + " doesn\'t exist", + location ); + } + cmd.createArgument().setValue( "-i" ); + cmd.createArgument().setFile( file ); + havePatchfile = true; + } + + /** + * Work silently unless an error occurs. + * + * @param q The new Quiet value + */ + public void setQuiet( boolean q ) + { + if( q ) + { + cmd.createArgument().setValue( "-s" ); + } + } + + /** + * Assume patch was created with old and new files swapped. + * + * @param r The new Reverse value + */ + public void setReverse( boolean r ) + { + if( r ) + { + cmd.createArgument().setValue( "-R" ); + } + } + + /** + * Strip the smallest prefix containing num leading slashes from + * filenames.

      + * + * patch's -p option. + * + * @param num The new Strip value + * @exception BuildException Description of Exception + */ + public void setStrip( int num ) + throws BuildException + { + if( num < 0 ) + { + throw new BuildException( "strip has to be >= 0", location ); + } + cmd.createArgument().setValue( "-p" + num ); + } + + public void execute() + throws BuildException + { + if( !havePatchfile ) + { + throw new BuildException( "patchfile argument is required", + location ); + } + + Commandline toExecute = ( Commandline )cmd.clone(); + toExecute.setExecutable( "patch" ); + + if( originalFile != null ) + { + toExecute.createArgument().setFile( originalFile ); + } + + Execute exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ), + null ); + exe.setCommandline( toExecute.getCommandline() ); + try + { + exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + +}// Patch diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PathConvert.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PathConvert.java new file mode 100644 index 000000000..e508b580a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PathConvert.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * This task converts path and classpath information to a specific target OS + * format. The resulting formatted path is placed into a specified property.

      + * + * LIMITATION: Currently this implementation groups all machines into one of two + * types: Unix or Windows. Unix is defined as NOT windows. + * + * @author Larry Streepy + * streepy@healthlanguage.com + */ +public class PathConvert extends Task +{ + + // Members + private Path path = null;// Path to be converted + private Reference refid = null;// Reference to path/fileset to convert + private String targetOS = null;// The target OS type + private boolean targetWindows = false;// Set when targetOS is set + private boolean onWindows = false;// Set if we're running on windows + private String property = null;// The property to receive the results + private Vector prefixMap = new Vector();// Path prefix map + private String pathSep = null;// User override on path sep char + private String dirSep = null; + + /** + * Override the default directory separator string for the target os + * + * @param sep The new DirSep value + */ + public void setDirSep( String sep ) + { + dirSep = sep; + } + + /** + * Override the default path separator string for the target os + * + * @param sep The new PathSep value + */ + public void setPathSep( String sep ) + { + pathSep = sep; + } + + /** + * Set the value of the proprty attribute - this is the property into which + * our converted path will be placed. + * + * @param p The new Property value + */ + public void setProperty( String p ) + { + property = p; + } + + /** + * Adds a reference to a PATH or FILESET defined elsewhere. + * + * @param r The new Refid value + */ + public void setRefid( Reference r ) + { + if( path != null ) + throw noChildrenAllowed(); + + refid = r; + } + + /** + * Set the value of the targetos attribute + * + * @param target The new Targetos value + */ + public void setTargetos( String target ) + { + + targetOS = target.toLowerCase(); + + if( !targetOS.equals( "windows" ) && !target.equals( "unix" ) && + !targetOS.equals( "netware" ) ) + { + throw new BuildException( "targetos must be one of 'unix', 'netware', or 'windows'" ); + } + + // Currently, we deal with only two path formats: Unix and Windows + // And Unix is everything that is not Windows + + // for NetWare, piggy-back on Windows, since in the validateSetup code, + // the same assumptions can be made as with windows - + // that ; is the path separator + + targetWindows = ( targetOS.equals( "windows" ) || targetOS.equals( "netware" ) ); + } + + /** + * Has the refid attribute of this element been set? + * + * @return The Reference value + */ + public boolean isReference() + { + return refid != null; + } + + /** + * Create a nested MAP element + * + * @return Description of the Returned Value + */ + public MapEntry createMap() + { + + MapEntry entry = new MapEntry(); + prefixMap.addElement( entry ); + return entry; + } + + /** + * Create a nested PATH element + * + * @return Description of the Returned Value + */ + public Path createPath() + { + + if( isReference() ) + throw noChildrenAllowed(); + + if( path == null ) + { + path = new Path( getProject() ); + } + return path.createPath(); + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + + // If we are a reference, the create a Path from the reference + if( isReference() ) + { + path = new Path( getProject() ).createPath(); + + Object obj = refid.getReferencedObject( getProject() ); + + if( obj instanceof Path ) + { + path.setRefid( refid ); + } + else if( obj instanceof FileSet ) + { + FileSet fs = ( FileSet )obj; + path.addFileset( fs ); + } + else + { + throw new BuildException( "'refid' does not refer to a path or fileset" ); + } + } + + validateSetup();// validate our setup + + // Currently, we deal with only two path formats: Unix and Windows + // And Unix is everything that is not Windows + // (with the exception for NetWare below) + + String osname = System.getProperty( "os.name" ).toLowerCase(); + + // for NetWare, piggy-back on Windows, since here and in the + // apply code, the same assumptions can be made as with windows - + // that \\ is an OK separator, and do comparisons case-insensitive. + onWindows = ( ( osname.indexOf( "windows" ) >= 0 ) || + ( osname.indexOf( "netware" ) >= 0 ) ); + + // Determine the from/to char mappings for dir sep + char fromDirSep = onWindows ? '\\' : '/'; + char toDirSep = dirSep.charAt( 0 ); + + StringBuffer rslt = new StringBuffer( 100 ); + + // Get the list of path components in canonical form + String[] elems = path.list(); + + for( int i = 0; i < elems.length; i++ ) + { + String elem = elems[i]; + + elem = mapElement( elem );// Apply the path prefix map + + // Now convert the path and file separator characters from the + // current os to the target os. + + elem = elem.replace( fromDirSep, toDirSep ); + + if( i != 0 ) + rslt.append( pathSep ); + rslt.append( elem ); + } + + // Place the result into the specified property + String value = rslt.toString(); + + log( "Set property " + property + " = " + value, Project.MSG_VERBOSE ); + + getProject().setNewProperty( property, value ); + } + + /** + * Apply the configured map to a path element. The map is used to convert + * between Windows drive letters and Unix paths. If no map is configured, + * then the input string is returned unchanged. + * + * @param elem The path element to apply the map to + * @return String Updated element + */ + private String mapElement( String elem ) + { + + int size = prefixMap.size(); + + if( size != 0 ) + { + + // Iterate over the map entries and apply each one. Stop when one of the + // entries actually changes the element + + for( int i = 0; i < size; i++ ) + { + MapEntry entry = ( MapEntry )prefixMap.elementAt( i ); + String newElem = entry.apply( elem ); + + // Note I'm using "!=" to see if we got a new object back from + // the apply method. + + if( newElem != elem ) + { + elem = newElem; + break;// We applied one, so we're done + } + } + } + + return elem; + } + + /** + * Creates an exception that indicates that this XML element must not have + * child elements if the refid attribute is set. + * + * @return Description of the Returned Value + */ + private BuildException noChildrenAllowed() + { + return new BuildException( "You must not specify nested PATH elements when using refid" ); + } + + /** + * Validate that all our parameters have been properly initialized. + * + * @throws BuildException if something is not setup properly + */ + private void validateSetup() + throws BuildException + { + + if( path == null ) + throw new BuildException( "You must specify a path to convert" ); + + if( property == null ) + throw new BuildException( "You must specify a property" ); + + // Must either have a target OS or both a dirSep and pathSep + + if( targetOS == null && pathSep == null && dirSep == null ) + throw new BuildException( "You must specify at least one of targetOS, dirSep, or pathSep" ); + + // Determine the separator strings. The dirsep and pathsep attributes + // override the targetOS settings. + String dsep = File.separator; + String psep = File.pathSeparator; + + if( targetOS != null ) + { + psep = targetWindows ? ";" : ":"; + dsep = targetWindows ? "\\" : "/"; + } + + if( pathSep != null ) + {// override with pathsep= + psep = pathSep; + } + + if( dirSep != null ) + {// override with dirsep= + dsep = dirSep; + } + + pathSep = psep; + dirSep = dsep; + } + + /** + * Helper class, holds the nested values. Elements will look like + * this: <map from="d:" to="/foo"/>

      + * + * When running on windows, the prefix comparison will be case insensitive. + * + * @author RT + */ + public class MapEntry + { + + // Members + private String from = null; + private String to = null; + + /** + * Set the "from" attribute of the map entry + * + * @param from The new From value + */ + public void setFrom( String from ) + { + this.from = from; + } + + /** + * Set the "to" attribute of the map entry + * + * @param to The new To value + */ + public void setTo( String to ) + { + this.to = to; + } + + /** + * Apply this map entry to a given path element + * + * @param elem Path element to process + * @return String Updated path element after mapping + */ + public String apply( String elem ) + { + if( from == null || to == null ) + { + throw new BuildException( "Both 'from' and 'to' must be set in a map entry" ); + } + + // If we're on windows, then do the comparison ignoring case + String cmpElem = onWindows ? elem.toLowerCase() : elem; + String cmpFrom = onWindows ? from.toLowerCase() : from; + + // If the element starts with the configured prefix, then convert the prefix + // to the configured 'to' value. + + if( cmpElem.startsWith( cmpFrom ) ) + { + int len = from.length(); + + if( len >= elem.length() ) + { + elem = to; + } + else + { + elem = to + elem.substring( len ); + } + } + + return elem; + } + }// User override on directory sep char +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ProcessDestroyer.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ProcessDestroyer.java new file mode 100644 index 000000000..590e86fd1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/ProcessDestroyer.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Vector; + +/** + * Destroys all registered Processes when the VM exits. + * + * @author Michael Newcomb + */ +class ProcessDestroyer + extends Thread +{ + + private Vector processes = new Vector(); + + /** + * Constructs a ProcessDestroyer and registers it as a shutdown + * hook. + */ + public ProcessDestroyer() + { + try + { + // check to see if the method exists (support pre-JDK 1.3 VMs) + // + Class[] paramTypes = {Thread.class}; + Method addShutdownHook = + Runtime.class.getMethod( "addShutdownHook", paramTypes ); + + // add the hook + // + Object[] args = {this}; + addShutdownHook.invoke( Runtime.getRuntime(), args ); + } + catch( Exception e ) + { + // it just won't be added as a shutdown hook... :( + } + } + + /** + * Returns true if the specified Process was + * successfully added to the list of processes to destroy upon VM exit. + * + * @param process the process to add + * @return true if the specified Process was + * successfully added + */ + public boolean add( Process process ) + { + processes.addElement( process ); + return processes.contains( process ); + } + + /** + * Returns true if the specified Process was + * successfully removed from the list of processes to destroy upon VM exit. + * + * @param process the process to remove + * @return true if the specified Process was + * successfully removed + */ + public boolean remove( Process process ) + { + return processes.removeElement( process ); + } + + /** + * Invoked by the VM when it is exiting. + */ + public void run() + { + synchronized( processes ) + { + Enumeration e = processes.elements(); + while( e.hasMoreElements() ) + { + ( ( Process )e.nextElement() ).destroy(); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Property.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Property.java new file mode 100644 index 000000000..15b182ecf --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Property.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Will set a Project property. Used to be a hack in ProjectHelper Will not + * override values set by the command line or parent projects. + * + * @author costin@dnt.ro + * @author Sam Ruby + * @author Glenn McAllister + */ +public class Property extends Task +{ + protected Path classpath; + protected String env; + protected File file; + + protected String name; + protected Reference ref; + protected String resource; + + protected boolean userProperty; + protected String value;// set read-only properties + + public Property() + { + super(); + } + + protected Property( boolean userProperty ) + { + this.userProperty = userProperty; + } + + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setEnvironment( String env ) + { + this.env = env; + } + + public void setFile( File file ) + { + this.file = file; + } + + public void setLocation( File location ) + { + setValue( location.getAbsolutePath() ); + } + + public void setName( String name ) + { + this.name = name; + } + + public void setRefid( Reference ref ) + { + this.ref = ref; + } + + public void setResource( String resource ) + { + this.resource = resource; + } + + /** + * @param userProperty The new UserProperty value + * @deprecated This was never a supported feature and has been deprecated + * without replacement + */ + public void setUserProperty( boolean userProperty ) + { + log( "DEPRECATED: Ignoring request to set user property in Property task.", + Project.MSG_WARN ); + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getEnvironment() + { + return env; + } + + public File getFile() + { + return file; + } + + public String getName() + { + return name; + } + + public Reference getRefid() + { + return ref; + } + + public String getResource() + { + return resource; + } + + public String getValue() + { + return value; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + public void execute() + throws BuildException + { + if( name != null ) + { + if( value == null && ref == null ) + { + throw new BuildException( "You must specify value, location or refid with the name attribute", + location ); + } + } + else + { + if( file == null && resource == null && env == null ) + { + throw new BuildException( "You must specify file, resource or environment when not using the name attribute", + location ); + } + } + + if( ( name != null ) && ( value != null ) ) + { + addProperty( name, value ); + } + + if( file != null ) + loadFile( file ); + + if( resource != null ) + loadResource( resource ); + + if( env != null ) + loadEnvironment( env ); + + if( ( name != null ) && ( ref != null ) ) + { + Object obj = ref.getReferencedObject( getProject() ); + if( obj != null ) + { + addProperty( name, obj.toString() ); + } + } + } + + public String toString() + { + return value == null ? "" : value; + } + + protected void addProperties( Properties props ) + { + resolveAllProperties( props ); + Enumeration e = props.keys(); + while( e.hasMoreElements() ) + { + String name = ( String )e.nextElement(); + String value = ( String )props.getProperty( name ); + + String v = project.replaceProperties( value ); + addProperty( name, v ); + } + } + + protected void addProperty( String n, String v ) + { + if( userProperty ) + { + if( project.getUserProperty( n ) == null ) + { + project.setUserProperty( n, v ); + } + else + { + log( "Override ignored for " + n, Project.MSG_VERBOSE ); + } + } + else + { + project.setNewProperty( n, v ); + } + } + + protected void loadEnvironment( String prefix ) + { + Properties props = new Properties(); + if( !prefix.endsWith( "." ) ) + prefix += "."; + log( "Loading Environment " + prefix, Project.MSG_VERBOSE ); + Vector osEnv = Execute.getProcEnvironment(); + for( Enumeration e = osEnv.elements(); e.hasMoreElements(); ) + { + String entry = ( String )e.nextElement(); + int pos = entry.indexOf( '=' ); + if( pos == -1 ) + { + log( "Ignoring: " + entry, Project.MSG_WARN ); + } + else + { + props.put( prefix + entry.substring( 0, pos ), + entry.substring( pos + 1 ) ); + } + } + addProperties( props ); + } + + protected void loadFile( File file ) + throws BuildException + { + Properties props = new Properties(); + log( "Loading " + file.getAbsolutePath(), Project.MSG_VERBOSE ); + try + { + if( file.exists() ) + { + FileInputStream fis = new FileInputStream( file ); + try + { + props.load( fis ); + } + finally + { + if( fis != null ) + { + fis.close(); + } + } + addProperties( props ); + } + else + { + log( "Unable to find property file: " + file.getAbsolutePath(), + Project.MSG_VERBOSE ); + } + } + catch( IOException ex ) + { + throw new BuildException( ex ); + } + } + + protected void loadResource( String name ) + { + Properties props = new Properties(); + log( "Resource Loading " + name, Project.MSG_VERBOSE ); + try + { + ClassLoader cL = null; + InputStream is = null; + + if( classpath != null ) + { + cL = new AntClassLoader( project, classpath ); + } + else + { + cL = this.getClass().getClassLoader(); + } + + if( cL == null ) + { + is = ClassLoader.getSystemResourceAsStream( name ); + } + else + { + is = cL.getResourceAsStream( name ); + } + + if( is != null ) + { + props.load( is ); + addProperties( props ); + } + else + { + log( "Unable to find resource " + name, Project.MSG_WARN ); + } + } + catch( IOException ex ) + { + throw new BuildException( ex ); + } + } + + private void resolveAllProperties( Properties props ) + throws BuildException + { + for( Enumeration e = props.keys(); e.hasMoreElements(); ) + { + String name = ( String )e.nextElement(); + String value = props.getProperty( name ); + + boolean resolved = false; + while( !resolved ) + { + Vector fragments = new Vector(); + Vector propertyRefs = new Vector(); + ProjectHelper.parsePropertyString( value, fragments, propertyRefs ); + + resolved = true; + if( propertyRefs.size() != 0 ) + { + StringBuffer sb = new StringBuffer(); + Enumeration i = fragments.elements(); + Enumeration j = propertyRefs.elements(); + while( i.hasMoreElements() ) + { + String fragment = ( String )i.nextElement(); + if( fragment == null ) + { + String propertyName = ( String )j.nextElement(); + if( propertyName.equals( name ) ) + { + throw new BuildException( "Property " + name + " was circularly defined." ); + } + fragment = getProject().getProperty( propertyName ); + if( fragment == null ) + { + if( props.containsKey( propertyName ) ) + { + fragment = props.getProperty( propertyName ); + resolved = false; + } + else + { + fragment = "${" + propertyName + "}"; + } + } + } + sb.append( fragment ); + } + value = sb.toString(); + props.put( name, value ); + } + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java new file mode 100644 index 000000000..ab5109052 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/PumpStreamHandler.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Copies standard output and error of subprocesses to standard output and error + * of the parent process. TODO: standard input of the subprocess is not + * implemented. + * + * @author thomas.haas@softwired-inc.com + */ +public class PumpStreamHandler implements ExecuteStreamHandler +{ + private Thread errorThread; + + private Thread inputThread; + + private OutputStream out, err; + + public PumpStreamHandler( OutputStream out, OutputStream err ) + { + this.out = out; + this.err = err; + } + + public PumpStreamHandler( OutputStream outAndErr ) + { + this( outAndErr, outAndErr ); + } + + public PumpStreamHandler() + { + this( System.out, System.err ); + } + + + public void setProcessErrorStream( InputStream is ) + { + createProcessErrorPump( is, err ); + } + + + public void setProcessInputStream( OutputStream os ) { } + + public void setProcessOutputStream( InputStream is ) + { + createProcessOutputPump( is, out ); + } + + + public void start() + { + inputThread.start(); + errorThread.start(); + } + + + public void stop() + { + try + { + inputThread.join(); + } + catch( InterruptedException e ) + {} + try + { + errorThread.join(); + } + catch( InterruptedException e ) + {} + try + { + err.flush(); + } + catch( IOException e ) + {} + try + { + out.flush(); + } + catch( IOException e ) + {} + } + + protected OutputStream getErr() + { + return err; + } + + protected OutputStream getOut() + { + return out; + } + + protected void createProcessErrorPump( InputStream is, OutputStream os ) + { + errorThread = createPump( is, os ); + } + + protected void createProcessOutputPump( InputStream is, OutputStream os ) + { + inputThread = createPump( is, os ); + } + + + /** + * Creates a stream pumper to copy the given input stream to the given + * output stream. + * + * @param is Description of Parameter + * @param os Description of Parameter + * @return Description of the Returned Value + */ + protected Thread createPump( InputStream is, OutputStream os ) + { + final Thread result = new Thread( new StreamPumper( is, os ) ); + result.setDaemon( true ); + return result; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Recorder.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Recorder.java new file mode 100644 index 000000000..ebe3fcc92 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Recorder.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * This task is the manager for RecorderEntry's. It is this class that holds all + * entries, modifies them every time the <recorder> task is called, and + * addes them to the build listener process. + * + * @author J D Glanville + * @version 0.5 + * @see RecorderEntry + */ +public class Recorder extends Task +{ + /** + * The list of recorder entries. + */ + private static Hashtable recorderEntries = new Hashtable(); + + ////////////////////////////////////////////////////////////////////// + // ATTRIBUTES + + /** + * The name of the file to record to. + */ + private String filename = null; + /** + * Whether or not to append. Need Boolean to record an unset state (null). + */ + private Boolean append = null; + /** + * Whether to start or stop recording. Need Boolean to record an unset state + * (null). + */ + private Boolean start = null; + /** + * What level to log? -1 means not initialized yet. + */ + private int loglevel = -1; + + /** + * Sets the action for the associated recorder entry. + * + * @param action The action for the entry to take: start or stop. + */ + public void setAction( ActionChoices action ) + { + if( action.getValue().equalsIgnoreCase( "start" ) ) + { + start = Boolean.TRUE; + } + else + { + start = Boolean.FALSE; + } + } + + /** + * Whether or not the logger should append to a previous file. + * + * @param append The new Append value + */ + public void setAppend( boolean append ) + { + this.append = new Boolean( append ); + } + + /** + * Sets the level to which this recorder entry should log to. + * + * @param level The new Loglevel value + * @see VerbosityLevelChoices + */ + public void setLoglevel( VerbosityLevelChoices level ) + { + //I hate cascading if/elseif clauses !!! + String lev = level.getValue(); + if( lev.equalsIgnoreCase( "error" ) ) + { + loglevel = Project.MSG_ERR; + } + else if( lev.equalsIgnoreCase( "warn" ) ) + { + loglevel = Project.MSG_WARN; + } + else if( lev.equalsIgnoreCase( "info" ) ) + { + loglevel = Project.MSG_INFO; + } + else if( lev.equalsIgnoreCase( "verbose" ) ) + { + loglevel = Project.MSG_VERBOSE; + } + else if( lev.equalsIgnoreCase( "debug" ) ) + { + loglevel = Project.MSG_DEBUG; + } + } + + ////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS / INITIALIZERS + + ////////////////////////////////////////////////////////////////////// + // ACCESSOR METHODS + + /** + * Sets the name of the file to log to, and the name of the recorder entry. + * + * @param fname File name of logfile. + */ + public void setName( String fname ) + { + filename = fname; + } + + ////////////////////////////////////////////////////////////////////// + // CORE / MAIN BODY + + /** + * The main execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( filename == null ) + throw new BuildException( "No filename specified" ); + + getProject().log( "setting a recorder for name " + filename, + Project.MSG_DEBUG ); + + // get the recorder entry + RecorderEntry recorder = getRecorder( filename, getProject() ); + // set the values on the recorder + recorder.setMessageOutputLevel( loglevel ); + recorder.setRecordState( start ); + } + + /** + * Gets the recorder that's associated with the passed in name. If the + * recorder doesn't exist, then a new one is created. + * + * @param name Description of Parameter + * @param proj Description of Parameter + * @return The Recorder value + * @exception BuildException Description of Exception + */ + protected RecorderEntry getRecorder( String name, Project proj ) + throws BuildException + { + Object o = recorderEntries.get( name ); + RecorderEntry entry; + if( o == null ) + { + // create a recorder entry + try + { + entry = new RecorderEntry( name ); + PrintStream out = null; + if( append == null ) + { + out = new PrintStream( + new FileOutputStream( name ) ); + } + else + { + out = new PrintStream( + new FileOutputStream( name, append.booleanValue() ) ); + } + entry.setErrorPrintStream( out ); + entry.setOutputPrintStream( out ); + } + catch( IOException ioe ) + { + throw new BuildException( "Problems creating a recorder entry", + ioe ); + } + proj.addBuildListener( entry ); + recorderEntries.put( name, entry ); + } + else + { + entry = ( RecorderEntry )o; + } + return entry; + } + + ////////////////////////////////////////////////////////////////////// + // INNER CLASSES + + /** + * A list of possible values for the setAction() method. + * Possible values include: start and stop. + * + * @author RT + */ + public static class ActionChoices extends EnumeratedAttribute + { + private final static String[] values = {"start", "stop"}; + + public String[] getValues() + { + return values; + } + } + + /** + * A list of possible values for the setLoglevel() method. + * Possible values include: error, warn, info, verbose, debug. + * + * @author RT + */ + public static class VerbosityLevelChoices extends EnumeratedAttribute + { + private final static String[] values = {"error", "warn", "info", + "verbose", "debug"}; + + public String[] getValues() + { + return values; + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/RecorderEntry.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/RecorderEntry.java new file mode 100644 index 000000000..4dad6229e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/RecorderEntry.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.PrintStream; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildLogger; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.StringUtils; + + +/** + * This is a class that represents a recorder. This is the listener to the build + * process. + * + * @author J D Glanville + * @version 0.5 + */ +public class RecorderEntry implements BuildLogger +{ + + ////////////////////////////////////////////////////////////////////// + // ATTRIBUTES + + /** + * The name of the file associated with this recorder entry. + */ + private String filename = null; + /** + * The state of the recorder (recorder on or off). + */ + private boolean record = true; + /** + * The current verbosity level to record at. + */ + private int loglevel = Project.MSG_INFO; + /** + * The output PrintStream to record to. + */ + private PrintStream out = null; + /** + * The start time of the last know target. + */ + private long targetStartTime = 0l; + + ////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS / INITIALIZERS + + /** + * @param name The name of this recorder (used as the filename). + */ + protected RecorderEntry( String name ) + { + filename = name; + } + + private static String formatTime( long millis ) + { + long seconds = millis / 1000; + long minutes = seconds / 60; + + if( minutes > 0 ) + { + return Long.toString( minutes ) + " minute" + + ( minutes == 1 ? " " : "s " ) + + Long.toString( seconds % 60 ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + else + { + return Long.toString( seconds ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + + } + + public void setEmacsMode( boolean emacsMode ) + { + throw new java.lang.RuntimeException( "Method setEmacsMode() not yet implemented." ); + } + + public void setErrorPrintStream( PrintStream err ) + { + out = err; + } + + public void setMessageOutputLevel( int level ) + { + if( level >= Project.MSG_ERR && level <= Project.MSG_DEBUG ) + loglevel = level; + } + + public void setOutputPrintStream( PrintStream output ) + { + out = output; + } + + /** + * Turns off or on this recorder. + * + * @param state true for on, false for off, null for no change. + */ + public void setRecordState( Boolean state ) + { + if( state != null ) + record = state.booleanValue(); + } + + ////////////////////////////////////////////////////////////////////// + // ACCESSOR METHODS + + /** + * @return the name of the file the output is sent to. + */ + public String getFilename() + { + return filename; + } + + public void buildFinished( BuildEvent event ) + { + log( "< BUILD FINISHED", Project.MSG_DEBUG ); + + Throwable error = event.getException(); + if( error == null ) + { + out.println( StringUtils.LINE_SEP + "BUILD SUCCESSFUL" ); + } + else + { + out.println( StringUtils.LINE_SEP + "BUILD FAILED" + StringUtils.LINE_SEP ); + error.printStackTrace( out ); + } + out.flush(); + out.close(); + } + + public void buildStarted( BuildEvent event ) + { + log( "> BUILD STARTED", Project.MSG_DEBUG ); + } + + public void messageLogged( BuildEvent event ) + { + log( "--- MESSAGE LOGGED", Project.MSG_DEBUG ); + + StringBuffer buf = new StringBuffer(); + if( event.getTask() != null ) + { + String name = "[" + event.getTask().getTaskName() + "]"; + /** + * @todo replace 12 with DefaultLogger.LEFT_COLUMN_SIZE + */ + for( int i = 0; i < ( 12 - name.length() ); i++ ) + { + buf.append( " " ); + }// for + buf.append( name ); + }// if + buf.append( event.getMessage() ); + + log( buf.toString(), event.getPriority() ); + } + + public void targetFinished( BuildEvent event ) + { + log( "<< TARGET FINISHED -- " + event.getTarget(), Project.MSG_DEBUG ); + String time = formatTime( System.currentTimeMillis() - targetStartTime ); + log( event.getTarget() + ": duration " + time, Project.MSG_VERBOSE ); + out.flush(); + } + + public void targetStarted( BuildEvent event ) + { + log( ">> TARGET STARTED -- " + event.getTarget(), Project.MSG_DEBUG ); + log( StringUtils.LINE_SEP + event.getTarget().getName() + ":", Project.MSG_INFO ); + targetStartTime = System.currentTimeMillis(); + } + + public void taskFinished( BuildEvent event ) + { + log( "<<< TASK FINISHED -- " + event.getTask(), Project.MSG_DEBUG ); + out.flush(); + } + + public void taskStarted( BuildEvent event ) + { + log( ">>> TASK STARTED -- " + event.getTask(), Project.MSG_DEBUG ); + } + + /** + * The thing that actually sends the information to the output. + * + * @param mesg The message to log. + * @param level The verbosity level of the message. + */ + private void log( String mesg, int level ) + { + if( record && ( level <= loglevel ) ) + { + out.println( mesg ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rename.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rename.java new file mode 100644 index 000000000..7e1090049 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rename.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Renames a file. + * + * @author haas@softwired.ch + * @deprecated The rename task is deprecated. Use move instead. + */ +public class Rename extends Task +{ + private boolean replace = true; + private File dest; + + private File src; + + /** + * Sets the new name of the file. + * + * @param dest the new name of the file. + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Sets wheter an existing file should be replaced. + * + * @param replace on, if an existing file should be replaced. + */ + public void setReplace( String replace ) + { + this.replace = project.toBoolean( replace ); + } + + + /** + * Sets the file to be renamed. + * + * @param src the file to rename + */ + public void setSrc( File src ) + { + this.src = src; + } + + + /** + * Renames the file src to dest + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + log( "DEPRECATED - The rename task is deprecated. Use move instead." ); + + if( dest == null ) + { + throw new BuildException( "dest attribute is required", location ); + } + + if( src == null ) + { + throw new BuildException( "src attribute is required", location ); + } + + if( replace && dest.exists() ) + { + if( !dest.delete() ) + { + throw new BuildException( "Unable to remove existing file " + + dest ); + } + } + if( !src.renameTo( dest ) ) + { + throw new BuildException( "Unable to rename " + src + " to " + + dest ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Replace.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Replace.java new file mode 100644 index 000000000..626ac8216 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Replace.java @@ -0,0 +1,591 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.FileUtils; + +/** + * Replaces all occurrences of one or more string tokens with given values in + * the indicated files. Each value can be either a string or the value of a + * property available in a designated property file. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Erik Langenbach + */ +public class Replace extends MatchingTask +{ + + private File src = null; + private NestedString token = null; + private NestedString value = new NestedString(); + + private File propertyFile = null; + private Properties properties = null; + private Vector replacefilters = new Vector(); + + private File dir = null; + private boolean summary = false; + + /** + * The encoding used to read and write files - if null, uses default + */ + private String encoding = null; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + + private int fileCount; + private int replaceCount; + + + /** + * Set the source files path when using matching tasks. + * + * @param dir The new Dir value + */ + public void setDir( File dir ) + { + this.dir = dir; + } + + /** + * Set the file encoding to use on the files read and written by replace + * + * @param encoding the encoding to use on the files + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + + /** + * Set the source file. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.src = file; + } + + /** + * Sets a file to be searched for property values. + * + * @param filename The new PropertyFile value + */ + public void setPropertyFile( File filename ) + { + propertyFile = filename; + } + + /** + * Request a summary + * + * @param summary true if you would like a summary logged of the replace + * operation + */ + public void setSummary( boolean summary ) + { + this.summary = summary; + } + + /** + * Set the string token to replace. + * + * @param token The new Token value + */ + public void setToken( String token ) + { + createReplaceToken().addText( token ); + } + + /** + * Set the string value to use as token replacement. + * + * @param value The new Value value + */ + public void setValue( String value ) + { + createReplaceValue().addText( value ); + } + + public Properties getProperties( File propertyFile ) + throws BuildException + { + Properties properties = new Properties(); + + try + { + properties.load( new FileInputStream( propertyFile ) ); + } + catch( FileNotFoundException e ) + { + String message = "Property file (" + propertyFile.getPath() + ") not found."; + throw new BuildException( message ); + } + catch( IOException e ) + { + String message = "Property file (" + propertyFile.getPath() + ") cannot be loaded."; + throw new BuildException( message ); + } + + return properties; + } + + /** + * Nested <replacetoken> element. + * + * @return Description of the Returned Value + */ + public NestedString createReplaceToken() + { + if( token == null ) + { + token = new NestedString(); + } + return token; + } + + /** + * Nested <replacevalue> element. + * + * @return Description of the Returned Value + */ + public NestedString createReplaceValue() + { + return value; + } + + /** + * Add nested <replacefilter> element. + * + * @return Description of the Returned Value + */ + public Replacefilter createReplacefilter() + { + Replacefilter filter = new Replacefilter(); + replacefilters.addElement( filter ); + return filter; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + validateAttributes(); + + if( propertyFile != null ) + { + properties = getProperties( propertyFile ); + } + + validateReplacefilters(); + fileCount = 0; + replaceCount = 0; + + if( src != null ) + { + processFile( src ); + } + + if( dir != null ) + { + DirectoryScanner ds = super.getDirectoryScanner( dir ); + String[] srcs = ds.getIncludedFiles(); + + for( int i = 0; i < srcs.length; i++ ) + { + File file = new File( dir, srcs[i] ); + processFile( file ); + } + } + + if( summary ) + { + log( "Replaced " + replaceCount + " occurrences in " + fileCount + " files.", Project.MSG_INFO ); + } + } + + /** + * Validate attributes provided for this task in .xml build file. + * + * @exception BuildException if any supplied attribute is invalid or any + * mandatory attribute is missing + */ + public void validateAttributes() + throws BuildException + { + if( src == null && dir == null ) + { + String message = "Either the file or the dir attribute " + "must be specified"; + throw new BuildException( message, location ); + } + if( propertyFile != null && !propertyFile.exists() ) + { + String message = "Property file " + propertyFile.getPath() + " does not exist."; + throw new BuildException( message, location ); + } + if( token == null && replacefilters.size() == 0 ) + { + String message = "Either token or a nested replacefilter " + + "must be specified"; + throw new BuildException( message, location ); + } + if( token != null && "".equals( token.getText() ) ) + { + String message = "The token attribute must not be an empty string."; + throw new BuildException( message, location ); + } + } + + /** + * Validate nested elements. + * + * @exception BuildException if any supplied attribute is invalid or any + * mandatory attribute is missing + */ + public void validateReplacefilters() + throws BuildException + { + for( int i = 0; i < replacefilters.size(); i++ ) + { + Replacefilter element = ( Replacefilter )replacefilters.elementAt( i ); + element.validate(); + } + } + + /** + * Perform the replacement on the given file. The replacement is performed + * on a temporary file which then replaces the original file. + * + * @param src the source file + * @exception BuildException Description of Exception + */ + private void processFile( File src ) + throws BuildException + { + if( !src.exists() ) + { + throw new BuildException( "Replace: source file " + src.getPath() + " doesn't exist", location ); + } + + File temp = fileUtils.createTempFile( "rep", ".tmp", + fileUtils.getParentFile( src ) ); + + Reader reader = null; + Writer writer = null; + try + { + reader = encoding == null ? new FileReader( src ) + : new InputStreamReader( new FileInputStream( src ), encoding ); + writer = encoding == null ? new FileWriter( temp ) + : new OutputStreamWriter( new FileOutputStream( temp ), encoding ); + + BufferedReader br = new BufferedReader( reader ); + BufferedWriter bw = new BufferedWriter( writer ); + + // read the entire file into a StringBuffer + // size of work buffer may be bigger than needed + // when multibyte characters exist in the source file + // but then again, it might be smaller than needed on + // platforms like Windows where length can't be trusted + int fileLengthInBytes = ( int )( src.length() ); + StringBuffer tmpBuf = new StringBuffer( fileLengthInBytes ); + int readChar = 0; + int totread = 0; + while( true ) + { + readChar = br.read(); + if( readChar < 0 ) + { + break; + } + tmpBuf.append( ( char )readChar ); + totread++; + } + + // create a String so we can use indexOf + String buf = tmpBuf.toString(); + + //Preserve original string (buf) so we can compare the result + String newString = new String( buf ); + + if( token != null ) + { + // line separators in values and tokens are "\n" + // in order to compare with the file contents, replace them + // as needed + String linesep = System.getProperty( "line.separator" ); + String val = stringReplace( value.getText(), "\n", linesep ); + String tok = stringReplace( token.getText(), "\n", linesep ); + + // for each found token, replace with value + log( "Replacing in " + src.getPath() + ": " + token.getText() + " --> " + value.getText(), Project.MSG_VERBOSE ); + newString = stringReplace( newString, tok, val ); + } + + if( replacefilters.size() > 0 ) + { + newString = processReplacefilters( newString, src.getPath() ); + } + + boolean changes = !newString.equals( buf ); + if( changes ) + { + bw.write( newString, 0, newString.length() ); + bw.flush(); + } + + // cleanup + bw.close(); + writer = null; + br.close(); + reader = null; + + // If there were changes, move the new one to the old one; + // otherwise, delete the new one + if( changes ) + { + ++fileCount; + src.delete(); + temp.renameTo( src ); + temp = null; + } + } + catch( IOException ioe ) + { + throw new BuildException( "IOException in " + src + " - " + + ioe.getClass().getName() + ":" + ioe.getMessage(), ioe, location ); + } + finally + { + if( reader != null ) + { + try + { + reader.close(); + } + catch( IOException e ) + {} + } + if( writer != null ) + { + try + { + writer.close(); + } + catch( IOException e ) + {} + } + if( temp != null ) + { + temp.delete(); + } + } + + } + + private String processReplacefilters( String buffer, String filename ) + { + String newString = new String( buffer ); + + for( int i = 0; i < replacefilters.size(); i++ ) + { + Replacefilter filter = ( Replacefilter )replacefilters.elementAt( i ); + + //for each found token, replace with value + log( "Replacing in " + filename + ": " + filter.getToken() + " --> " + filter.getReplaceValue(), Project.MSG_VERBOSE ); + newString = stringReplace( newString, filter.getToken(), filter.getReplaceValue() ); + } + + return newString; + } + + /** + * Replace occurrences of str1 in string str with str2 + * + * @param str Description of Parameter + * @param str1 Description of Parameter + * @param str2 Description of Parameter + * @return Description of the Returned Value + */ + private String stringReplace( String str, String str1, String str2 ) + { + StringBuffer ret = new StringBuffer(); + int start = 0; + int found = str.indexOf( str1 ); + while( found >= 0 ) + { + // write everything up to the found str1 + if( found > start ) + { + ret.append( str.substring( start, found ) ); + } + + // write the replacement str2 + if( str2 != null ) + { + ret.append( str2 ); + } + + // search again + start = found + str1.length(); + found = str.indexOf( str1, start ); + ++replaceCount; + } + + // write the remaining characters + if( str.length() > start ) + { + ret.append( str.substring( start, str.length() ) ); + } + + return ret.toString(); + } + + //Inner class + public class NestedString + { + + private StringBuffer buf = new StringBuffer(); + + public String getText() + { + return buf.toString(); + } + + public void addText( String val ) + { + buf.append( val ); + } + } + + //Inner class + public class Replacefilter + { + private String property; + private String token; + private String value; + + public void setProperty( String property ) + { + this.property = property; + } + + public void setToken( String token ) + { + this.token = token; + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getProperty() + { + return property; + } + + public String getReplaceValue() + { + if( property != null ) + { + return ( String )properties.getProperty( property ); + } + else if( value != null ) + { + return value; + } + else if( Replace.this.value != null ) + { + return Replace.this.value.getText(); + } + else + { + //Default is empty string + return new String( "" ); + } + } + + public String getToken() + { + return token; + } + + public String getValue() + { + return value; + } + + public void validate() + throws BuildException + { + //Validate mandatory attributes + if( token == null ) + { + String message = "token is a mandatory attribute " + "of replacefilter."; + throw new BuildException( message ); + } + + if( "".equals( token ) ) + { + String message = "The token attribute must not be an empty string."; + throw new BuildException( message ); + } + + //value and property are mutually exclusive attributes + if( ( value != null ) && ( property != null ) ) + { + String message = "Either value or property " + "can be specified, but a replacefilter " + "element cannot have both."; + throw new BuildException( message ); + } + + if( ( property != null ) ) + { + //the property attribute must have access to a property file + if( propertyFile == null ) + { + String message = "The replacefilter's property attribute " + "can only be used with the replacetask's " + "propertyFile attribute."; + throw new BuildException( message ); + } + + //Make sure property exists in property file + if( properties == null || + properties.getProperty( property ) == null ) + { + String message = "property \"" + property + "\" was not found in " + propertyFile.getPath(); + throw new BuildException( message ); + } + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rmic.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rmic.java new file mode 100644 index 000000000..e37fac08e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Rmic.java @@ -0,0 +1,688 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.rmi.Remote; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.rmic.RmicAdapter; +import org.apache.tools.ant.taskdefs.rmic.RmicAdapterFactory; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Task to compile RMI stubs and skeletons. This task can take the following + * arguments: + *

        + *
      • base: The base directory for the compiled stubs and skeletons + *
      • class: The name of the class to generate the stubs from + *
      • stubVersion: The version of the stub prototol to use (1.1, 1.2, + * compat) + *
      • sourceBase: The base directory for the generated stubs and skeletons + * + *
      • classpath: Additional classpath, appended before the system classpath + * + *
      • iiop: Generate IIOP compatable output + *
      • iiopopts: Include IIOP options + *
      • idl: Generate IDL output + *
      • idlopts: Include IDL options + *
      • includeantruntime + *
      • includejavaruntime + *
      • extdirs + *
      + * Of these arguments, base is required.

      + * + * If classname is specified then only that classname will be compiled. If it is + * absent, then base is traversed for classes according to patterns.

      + * + * + * + * @author duncan@x180.com + * @author ludovic.claude@websitewatchers.co.uk + * @author David Maclean david@cm.co.za + * @author Stefan Bodewig + * @author Takashi Okamoto tokamoto@rd.nttdata.co.jp + */ + +public class Rmic extends MatchingTask +{ + + private final static String FAIL_MSG + = "Rmic failed, messages should have been provided."; + private boolean verify = false; + private boolean filtering = false; + + private boolean iiop = false; + private boolean idl = false; + private boolean debug = false; + private boolean includeAntRuntime = true; + private boolean includeJavaRuntime = false; + + private Vector compileList = new Vector(); + + private ClassLoader loader = null; + + private File baseDir; + private String classname; + private Path compileClasspath; + private Path extdirs; + private String idlopts; + private String iiopopts; + private File sourceBase; + private String stubVersion; + + /** + * Sets the base directory to output generated class. + * + * @param base The new Base value + */ + public void setBase( File base ) + { + this.baseDir = base; + } + + /** + * Sets the class name to compile. + * + * @param classname The new Classname value + */ + public void setClassname( String classname ) + { + this.classname = classname; + } + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( compileClasspath == null ) + { + compileClasspath = classpath; + } + else + { + compileClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Sets the debug flag. + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Sets the extension directories that will be used during the compilation. + * + * @param extdirs The new Extdirs value + */ + public void setExtdirs( Path extdirs ) + { + if( this.extdirs == null ) + { + this.extdirs = extdirs; + } + else + { + this.extdirs.append( extdirs ); + } + } + + public void setFiltering( boolean filter ) + { + filtering = filter; + } + + /** + * Indicates that IDL output should be generated. This defaults to false if + * not set. + * + * @param idl The new Idl value + */ + public void setIdl( boolean idl ) + { + this.idl = idl; + } + + /** + * pass additional arguments for idl compile + * + * @param idlopts The new Idlopts value + */ + public void setIdlopts( String idlopts ) + { + this.idlopts = idlopts; + } + + /** + * Indicates that IIOP compatible stubs should be generated. This defaults + * to false if not set. + * + * @param iiop The new Iiop value + */ + public void setIiop( boolean iiop ) + { + this.iiop = iiop; + } + + /** + * pass additional arguments for iiop + * + * @param iiopopts The new Iiopopts value + */ + public void setIiopopts( String iiopopts ) + { + this.iiopopts = iiopopts; + } + + /** + * Include ant's own classpath in this task's classpath? + * + * @param include The new Includeantruntime value + */ + public void setIncludeantruntime( boolean include ) + { + includeAntRuntime = include; + } + + /** + * Sets whether or not to include the java runtime libraries to this task's + * classpath. + * + * @param include The new Includejavaruntime value + */ + public void setIncludejavaruntime( boolean include ) + { + includeJavaRuntime = include; + } + + /** + * Sets the source dirs to find the source java files. + * + * @param sourceBase The new SourceBase value + */ + public void setSourceBase( File sourceBase ) + { + this.sourceBase = sourceBase; + } + + /** + * Sets the stub version. + * + * @param stubVersion The new StubVersion value + */ + public void setStubVersion( String stubVersion ) + { + this.stubVersion = stubVersion; + } + + /** + * Indicates that the classes found by the directory match should be checked + * to see if they implement java.rmi.Remote. This defaults to false if not + * set. + * + * @param verify The new Verify value + */ + public void setVerify( boolean verify ) + { + this.verify = verify; + } + + /** + * Gets the base directory to output generated class. + * + * @return The Base value + */ + public File getBase() + { + return this.baseDir; + } + + /** + * Gets the class name to compile. + * + * @return The Classname value + */ + public String getClassname() + { + return classname; + } + + /** + * Gets the classpath. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return compileClasspath; + } + + public Vector getCompileList() + { + return compileList; + } + + /** + * Gets the debug flag. + * + * @return The Debug value + */ + public boolean getDebug() + { + return debug; + } + + /** + * Gets the extension directories that will be used during the compilation. + * + * @return The Extdirs value + */ + public Path getExtdirs() + { + return extdirs; + } + + /** + * Gets file list to compile. + * + * @return The FileList value + */ + public Vector getFileList() + { + return compileList; + } + + public boolean getFiltering() + { + return filtering; + } + + /* + * Gets IDL flags. + */ + public boolean getIdl() + { + return idl; + } + + /** + * Gets additional arguments for idl compile. + * + * @return The Idlopts value + */ + public String getIdlopts() + { + return idlopts; + } + + /** + * Gets iiop flags. + * + * @return The Iiop value + */ + public boolean getIiop() + { + return iiop; + } + + /** + * Gets additional arguments for iiop. + * + * @return The Iiopopts value + */ + public String getIiopopts() + { + return iiopopts; + } + + /** + * Gets whether or not the ant classpath is to be included in the task's + * classpath. + * + * @return The Includeantruntime value + */ + public boolean getIncludeantruntime() + { + return includeAntRuntime; + } + + /** + * Gets whether or not the java runtime should be included in this task's + * classpath. + * + * @return The Includejavaruntime value + */ + public boolean getIncludejavaruntime() + { + return includeJavaRuntime; + } + + /** + * Classloader for the user-specified classpath. + * + * @return The Loader value + */ + public ClassLoader getLoader() + { + return loader; + } + + /** + * Returns the topmost interface that extends Remote for a given class - if + * one exists. + * + * @param testClass Description of Parameter + * @return The RemoteInterface value + */ + public Class getRemoteInterface( Class testClass ) + { + if( Remote.class.isAssignableFrom( testClass ) ) + { + Class[] interfaces = testClass.getInterfaces(); + if( interfaces != null ) + { + for( int i = 0; i < interfaces.length; i++ ) + { + if( Remote.class.isAssignableFrom( interfaces[i] ) ) + { + return interfaces[i]; + } + } + } + } + return null; + } + + /** + * Gets the source dirs to find the source java files. + * + * @return The SourceBase value + */ + public File getSourceBase() + { + return sourceBase; + } + + public String getStubVersion() + { + return stubVersion; + } + + /** + * Get verify flag. + * + * @return The Verify value + */ + public boolean getVerify() + { + return verify; + } + + /** + * Load named class and test whether it can be rmic'ed + * + * @param classname Description of Parameter + * @return The ValidRmiRemote value + */ + public boolean isValidRmiRemote( String classname ) + { + try + { + Class testClass = loader.loadClass( classname ); + // One cannot RMIC an interface for "classic" RMI (JRMP) + if( testClass.isInterface() && !iiop && !idl ) + { + return false; + } + return isValidRmiRemote( testClass ); + } + catch( ClassNotFoundException e ) + { + log( "Unable to verify class " + classname + + ". It could not be found.", Project.MSG_WARN ); + } + catch( NoClassDefFoundError e ) + { + log( "Unable to verify class " + classname + + ". It is not defined.", Project.MSG_WARN ); + } + catch( Throwable t ) + { + log( "Unable to verify class " + classname + + ". Loading caused Exception: " + + t.getMessage(), Project.MSG_WARN ); + } + // we only get here if an exception has been thrown + return false; + } + + /** + * Creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + return compileClasspath.createPath(); + } + + /** + * Maybe creates a nested extdirs element. + * + * @return Description of the Returned Value + */ + public Path createExtdirs() + { + if( extdirs == null ) + { + extdirs = new Path( project ); + } + return extdirs.createPath(); + } + + public void execute() + throws BuildException + { + if( baseDir == null ) + { + throw new BuildException( "base attribute must be set!", location ); + } + if( !baseDir.exists() ) + { + throw new BuildException( "base does not exist!", location ); + } + + if( verify ) + { + log( "Verify has been turned on.", Project.MSG_INFO ); + } + + String compiler = project.getProperty( "build.rmic" ); + RmicAdapter adapter = RmicAdapterFactory.getRmic( compiler, this ); + + // now we need to populate the compiler adapter + adapter.setRmic( this ); + + Path classpath = adapter.getClasspath(); + loader = new AntClassLoader( project, classpath ); + + // scan base dirs to build up compile lists only if a + // specific classname is not given + if( classname == null ) + { + DirectoryScanner ds = this.getDirectoryScanner( baseDir ); + String[] files = ds.getIncludedFiles(); + scanDir( baseDir, files, adapter.getMapper() ); + } + else + { + // otherwise perform a timestamp comparison - at least + scanDir( baseDir, + new String[]{classname.replace( '.', File.separatorChar ) + ".class"}, + adapter.getMapper() ); + } + + int fileCount = compileList.size(); + if( fileCount > 0 ) + { + log( "RMI Compiling " + fileCount + + " class" + ( fileCount > 1 ? "es" : "" ) + " to " + baseDir, + Project.MSG_INFO ); + + // finally, lets execute the compiler!! + if( !adapter.execute() ) + { + throw new BuildException( FAIL_MSG, location ); + } + } + + /* + * Move the generated source file to the base directory. If + * base directory and sourcebase are the same, the generated + * sources are already in place. + */ + if( null != sourceBase && !baseDir.equals( sourceBase ) ) + { + if( idl ) + { + log( "Cannot determine sourcefiles in idl mode, ", + Project.MSG_WARN ); + log( "sourcebase attribute will be ignored.", Project.MSG_WARN ); + } + else + { + for( int j = 0; j < fileCount; j++ ) + { + moveGeneratedFile( baseDir, sourceBase, + ( String )compileList.elementAt( j ), + adapter ); + } + } + } + compileList.removeAllElements(); + } + + /** + * Scans the directory looking for class files to be compiled. The result is + * returned in the class variable compileList. + * + * @param baseDir Description of Parameter + * @param files Description of Parameter + * @param mapper Description of Parameter + */ + protected void scanDir( File baseDir, String files[], + FileNameMapper mapper ) + { + + String[] newFiles = files; + if( idl ) + { + log( "will leave uptodate test to rmic implementation in idl mode.", + Project.MSG_VERBOSE ); + } + else if( iiop + && iiopopts != null && iiopopts.indexOf( "-always" ) > -1 ) + { + log( "no uptodate test as -always option has been specified", + Project.MSG_VERBOSE ); + } + else + { + SourceFileScanner sfs = new SourceFileScanner( this ); + newFiles = sfs.restrict( files, baseDir, baseDir, mapper ); + } + + for( int i = 0; i < newFiles.length; i++ ) + { + String classname = newFiles[i].replace( File.separatorChar, '.' ); + classname = classname.substring( 0, classname.lastIndexOf( ".class" ) ); + compileList.addElement( classname ); + } + } + + /** + * Check to see if the class or (super)interfaces implement java.rmi.Remote. + * + * @param testClass Description of Parameter + * @return The ValidRmiRemote value + */ + private boolean isValidRmiRemote( Class testClass ) + { + return getRemoteInterface( testClass ) != null; + } + + /** + * Move the generated source file(s) to the base directory + * + * @param baseDir Description of Parameter + * @param sourceBaseFile Description of Parameter + * @param classname Description of Parameter + * @param adapter Description of Parameter + * @exception BuildException Description of Exception + */ + private void moveGeneratedFile( File baseDir, File sourceBaseFile, + String classname, + RmicAdapter adapter ) + throws BuildException + { + + String classFileName = + classname.replace( '.', File.separatorChar ) + ".class"; + String[] generatedFiles = + adapter.getMapper().mapFileName( classFileName ); + + for( int i = 0; i < generatedFiles.length; i++ ) + { + String sourceFileName = + classFileName.substring( 0, classFileName.length() - 6 ) + ".java"; + File oldFile = new File( baseDir, sourceFileName ); + File newFile = new File( sourceBaseFile, sourceFileName ); + try + { + project.copyFile( oldFile, newFile, filtering ); + oldFile.delete(); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + oldFile + " to " + + newFile + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SQLExec.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SQLExec.java new file mode 100644 index 000000000..27acf92a0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SQLExec.java @@ -0,0 +1,867 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.Reader; +import java.io.StringReader; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Driver; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.Enumeration; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Reads in a text file containing SQL statements seperated with semicolons and + * executes it in a given db. Comments may be created with REM -- or //. + * + * @author Jeff Martin + * @author Michael McCallum + * @author Tim Stephenson + */ +public class SQLExec extends Task +{ + + private int goodSql = 0, totalSql = 0; + + private Vector filesets = new Vector(); + + /** + * Database connection + */ + private Connection conn = null; + + /** + * Autocommit flag. Default value is false + */ + private boolean autocommit = false; + + /** + * SQL statement + */ + private Statement statement = null; + + /** + * DB driver. + */ + private String driver = null; + + /** + * DB url. + */ + private String url = null; + + /** + * User name. + */ + private String userId = null; + + /** + * Password + */ + private String password = null; + + /** + * SQL input file + */ + private File srcFile = null; + + /** + * SQL input command + */ + private String sqlCommand = ""; + + /** + * SQL transactions to perform + */ + private Vector transactions = new Vector(); + + /** + * SQL Statement delimiter + */ + private String delimiter = ";"; + + /** + * The delimiter type indicating whether the delimiter will only be + * recognized on a line by itself + */ + private String delimiterType = DelimiterType.NORMAL; + + /** + * Print SQL results. + */ + private boolean print = false; + + /** + * Print header columns. + */ + private boolean showheaders = true; + + /** + * Results Output file. + */ + private File output = null; + + /** + * RDBMS Product needed for this SQL. + */ + private String rdbms = null; + + /** + * RDBMS Version needed for this SQL. + */ + private String version = null; + + /** + * Action to perform if an error is found + */ + private String onError = "abort"; + + /** + * Encoding to use when reading SQL statements from a file + */ + private String encoding = null; + + private Path classpath; + + private AntClassLoader loader; + + /** + * Set the autocommit flag for the DB connection. + * + * @param autocommit The new Autocommit value + */ + public void setAutocommit( boolean autocommit ) + { + this.autocommit = autocommit; + } + + /** + * Set the classpath for loading the driver. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + /** + * Set the classpath for loading the driver using the classpath reference. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Set the statement delimiter.

      + * + * For example, set this to "go" and delimitertype to "ROW" for Sybase ASE + * or MS SQL Server.

      + * + * @param delimiter The new Delimiter value + */ + public void setDelimiter( String delimiter ) + { + this.delimiter = delimiter; + } + + /** + * Set the Delimiter type for this sql task. The delimiter type takes two + * values - normal and row. Normal means that any occurence of the delimiter + * terminate the SQL command whereas with row, only a line containing just + * the delimiter is recognized as the end of the command. + * + * @param delimiterType The new DelimiterType value + */ + public void setDelimiterType( DelimiterType delimiterType ) + { + this.delimiterType = delimiterType.getValue(); + } + + /** + * Set the JDBC driver to be used. + * + * @param driver The new Driver value + */ + public void setDriver( String driver ) + { + this.driver = driver; + } + + /** + * Set the file encoding to use on the sql files read in + * + * @param encoding the encoding to use on the files + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Set the action to perform onerror + * + * @param action The new Onerror value + */ + public void setOnerror( OnError action ) + { + this.onError = action.getValue(); + } + + /** + * Set the output file. + * + * @param output The new Output value + */ + public void setOutput( File output ) + { + this.output = output; + } + + + /** + * Set the password for the DB connection. + * + * @param password The new Password value + */ + public void setPassword( String password ) + { + this.password = password; + } + + /** + * Set the print flag. + * + * @param print The new Print value + */ + public void setPrint( boolean print ) + { + this.print = print; + } + + /** + * Set the rdbms required + * + * @param vendor The new Rdbms value + */ + public void setRdbms( String vendor ) + { + this.rdbms = vendor.toLowerCase(); + } + + /** + * Set the showheaders flag. + * + * @param showheaders The new Showheaders value + */ + public void setShowheaders( boolean showheaders ) + { + this.showheaders = showheaders; + } + + /** + * Set the name of the sql file to be run. + * + * @param srcFile The new Src value + */ + public void setSrc( File srcFile ) + { + this.srcFile = srcFile; + } + + /** + * Set the DB connection url. + * + * @param url The new Url value + */ + public void setUrl( String url ) + { + this.url = url; + } + + /** + * Set the user name for the DB connection. + * + * @param userId The new Userid value + */ + public void setUserid( String userId ) + { + this.userId = userId; + } + + /** + * Set the version required + * + * @param version The new Version value + */ + public void setVersion( String version ) + { + this.version = version.toLowerCase(); + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Set the sql command to execute + * + * @param sql The feature to be added to the Text attribute + */ + public void addText( String sql ) + { + this.sqlCommand += sql; + } + + /** + * Create the classpath for loading the driver. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + + /** + * Set the sql command to execute + * + * @return Description of the Returned Value + */ + public Transaction createTransaction() + { + Transaction t = new Transaction(); + transactions.addElement( t ); + return t; + } + + /** + * Load the sql file and then execute it + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + sqlCommand = sqlCommand.trim(); + + if( srcFile == null && sqlCommand.length() == 0 && filesets.isEmpty() ) + { + if( transactions.size() == 0 ) + { + throw new BuildException( "Source file or fileset, transactions or sql statement must be set!", location ); + } + } + else + { + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File srcDir = fs.getDir( project ); + + String[] srcFiles = ds.getIncludedFiles(); + + // Make a transaction for each file + for( int j = 0; j < srcFiles.length; j++ ) + { + Transaction t = createTransaction(); + t.setSrc( new File( srcDir, srcFiles[j] ) ); + } + } + + // Make a transaction group for the outer command + Transaction t = createTransaction(); + t.setSrc( srcFile ); + t.addText( sqlCommand ); + } + + if( driver == null ) + { + throw new BuildException( "Driver attribute must be set!", location ); + } + if( userId == null ) + { + throw new BuildException( "User Id attribute must be set!", location ); + } + if( password == null ) + { + throw new BuildException( "Password attribute must be set!", location ); + } + if( url == null ) + { + throw new BuildException( "Url attribute must be set!", location ); + } + if( srcFile != null && !srcFile.exists() ) + { + throw new BuildException( "Source file does not exist!", location ); + } + Driver driverInstance = null; + // Load the driver using the + try + { + Class dc; + if( classpath != null ) + { + log( "Loading " + driver + " using AntClassLoader with classpath " + classpath, + Project.MSG_VERBOSE ); + + loader = new AntClassLoader( project, classpath ); + dc = loader.loadClass( driver ); + } + else + { + log( "Loading " + driver + " using system loader.", Project.MSG_VERBOSE ); + dc = Class.forName( driver ); + } + driverInstance = ( Driver )dc.newInstance(); + } + catch( ClassNotFoundException e ) + { + throw new BuildException( "Class Not Found: JDBC driver " + driver + " could not be loaded", location ); + } + catch( IllegalAccessException e ) + { + throw new BuildException( "Illegal Access: JDBC driver " + driver + " could not be loaded", location ); + } + catch( InstantiationException e ) + { + throw new BuildException( "Instantiation Exception: JDBC driver " + driver + " could not be loaded", location ); + } + + try + { + log( "connecting to " + url, Project.MSG_VERBOSE ); + Properties info = new Properties(); + info.put( "user", userId ); + info.put( "password", password ); + conn = driverInstance.connect( url, info ); + + if( conn == null ) + { + // Driver doesn't understand the URL + throw new SQLException( "No suitable Driver for " + url ); + } + + if( !isValidRdbms( conn ) ) + return; + + conn.setAutoCommit( autocommit ); + + statement = conn.createStatement(); + + PrintStream out = System.out; + try + { + if( output != null ) + { + log( "Opening PrintStream to output file " + output, Project.MSG_VERBOSE ); + out = new PrintStream( new BufferedOutputStream( new FileOutputStream( output ) ) ); + } + + // Process all transactions + for( Enumeration e = transactions.elements(); + e.hasMoreElements(); ) + { + + ( ( Transaction )e.nextElement() ).runTransaction( out ); + if( !autocommit ) + { + log( "Commiting transaction", Project.MSG_VERBOSE ); + conn.commit(); + } + } + } + finally + { + if( out != null && out != System.out ) + { + out.close(); + } + } + } + catch( IOException e ) + { + if( !autocommit && conn != null && onError.equals( "abort" ) ) + { + try + { + conn.rollback(); + } + catch( SQLException ex ) + {} + } + throw new BuildException( e ); + } + catch( SQLException e ) + { + if( !autocommit && conn != null && onError.equals( "abort" ) ) + { + try + { + conn.rollback(); + } + catch( SQLException ex ) + {} + } + throw new BuildException( e ); + } + finally + { + try + { + if( statement != null ) + { + statement.close(); + } + if( conn != null ) + { + conn.close(); + } + } + catch( SQLException e ) + {} + } + + log( goodSql + " of " + totalSql + + " SQL statements executed successfully" ); + } + + /** + * Verify if connected to the correct RDBMS + * + * @param conn Description of Parameter + * @return The ValidRdbms value + */ + protected boolean isValidRdbms( Connection conn ) + { + if( rdbms == null && version == null ) + return true; + + try + { + DatabaseMetaData dmd = conn.getMetaData(); + + if( rdbms != null ) + { + String theVendor = dmd.getDatabaseProductName().toLowerCase(); + + log( "RDBMS = " + theVendor, Project.MSG_VERBOSE ); + if( theVendor == null || theVendor.indexOf( rdbms ) < 0 ) + { + log( "Not the required RDBMS: " + rdbms, Project.MSG_VERBOSE ); + return false; + } + } + + if( version != null ) + { + String theVersion = dmd.getDatabaseProductVersion().toLowerCase(); + + log( "Version = " + theVersion, Project.MSG_VERBOSE ); + if( theVersion == null || + !( theVersion.startsWith( version ) || + theVersion.indexOf( " " + version ) >= 0 ) ) + { + log( "Not the required version: \"" + version + "\"", Project.MSG_VERBOSE ); + return false; + } + } + } + catch( SQLException e ) + { + // Could not get the required information + log( "Failed to obtain required RDBMS information", Project.MSG_ERR ); + return false; + } + + return true; + } + + /** + * Exec the sql statement. + * + * @param sql Description of Parameter + * @param out Description of Parameter + * @exception SQLException Description of Exception + */ + protected void execSQL( String sql, PrintStream out ) + throws SQLException + { + // Check and ignore empty statements + if( "".equals( sql.trim() ) ) + return; + + try + { + totalSql++; + if( !statement.execute( sql ) ) + { + log( statement.getUpdateCount() + " rows affected", + Project.MSG_VERBOSE ); + } + else + { + if( print ) + { + printResults( out ); + } + } + + SQLWarning warning = conn.getWarnings(); + while( warning != null ) + { + log( warning + " sql warning", Project.MSG_VERBOSE ); + warning = warning.getNextWarning(); + } + conn.clearWarnings(); + goodSql++; + } + catch( SQLException e ) + { + log( "Failed to execute: " + sql, Project.MSG_ERR ); + if( !onError.equals( "continue" ) ) + throw e; + log( e.toString(), Project.MSG_ERR ); + } + } + + /** + * print any results in the statement. + * + * @param out Description of Parameter + * @exception java.sql.SQLException Description of Exception + */ + protected void printResults( PrintStream out ) + throws java.sql.SQLException + { + ResultSet rs = null; + do + { + rs = statement.getResultSet(); + if( rs != null ) + { + log( "Processing new result set.", Project.MSG_VERBOSE ); + ResultSetMetaData md = rs.getMetaData(); + int columnCount = md.getColumnCount(); + StringBuffer line = new StringBuffer(); + if( showheaders ) + { + for( int col = 1; col < columnCount; col++ ) + { + line.append( md.getColumnName( col ) ); + line.append( "," ); + } + line.append( md.getColumnName( columnCount ) ); + out.println( line ); + line.setLength( 0 ); + } + while( rs.next() ) + { + boolean first = true; + for( int col = 1; col <= columnCount; col++ ) + { + String columnValue = rs.getString( col ); + if( columnValue != null ) + { + columnValue = columnValue.trim(); + } + + if( first ) + { + first = false; + } + else + { + line.append( "," ); + } + line.append( columnValue ); + } + out.println( line ); + line.setLength( 0 ); + } + } + }while ( statement.getMoreResults() ); + out.println(); + } + + protected void runStatements( Reader reader, PrintStream out ) + throws SQLException, IOException + { + String sql = ""; + String line = ""; + + BufferedReader in = new BufferedReader( reader ); + + try + { + while( ( line = in.readLine() ) != null ) + { + line = line.trim(); + line = project.replaceProperties( line ); + if( line.startsWith( "//" ) ) + continue; + if( line.startsWith( "--" ) ) + continue; + StringTokenizer st = new StringTokenizer( line ); + if( st.hasMoreTokens() ) + { + String token = st.nextToken(); + if( "REM".equalsIgnoreCase( token ) ) + { + continue; + } + } + + sql += " " + line; + sql = sql.trim(); + + // SQL defines "--" as a comment to EOL + // and in Oracle it may contain a hint + // so we cannot just remove it, instead we must end it + if( line.indexOf( "--" ) >= 0 ) + sql += "\n"; + + if( delimiterType.equals( DelimiterType.NORMAL ) && sql.endsWith( delimiter ) || + delimiterType.equals( DelimiterType.ROW ) && line.equals( delimiter ) ) + { + log( "SQL: " + sql, Project.MSG_VERBOSE ); + execSQL( sql.substring( 0, sql.length() - delimiter.length() ), out ); + sql = ""; + } + } + + // Catch any statements not followed by ; + if( !sql.equals( "" ) ) + { + execSQL( sql, out ); + } + } + catch( SQLException e ) + { + throw e; + } + + } + + public static class DelimiterType extends EnumeratedAttribute + { + public final static String NORMAL = "normal"; + public final static String ROW = "row"; + + public String[] getValues() + { + return new String[]{NORMAL, ROW}; + } + } + + /** + * Enumerated attribute with the values "continue", "stop" and "abort" for + * the onerror attribute. + * + * @author RT + */ + public static class OnError extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"continue", "stop", "abort"}; + } + } + + /** + * Contains the definition of a new transaction element. Transactions allow + * several files or blocks of statements to be executed using the same JDBC + * connection and commit operation in between. + * + * @author RT + */ + public class Transaction + { + private File tSrcFile = null; + private String tSqlCommand = ""; + + public void setSrc( File src ) + { + this.tSrcFile = src; + } + + public void addText( String sql ) + { + this.tSqlCommand += sql; + } + + private void runTransaction( PrintStream out ) + throws IOException, SQLException + { + if( tSqlCommand.length() != 0 ) + { + log( "Executing commands", Project.MSG_INFO ); + runStatements( new StringReader( tSqlCommand ), out ); + } + + if( tSrcFile != null ) + { + log( "Executing file: " + tSrcFile.getAbsolutePath(), + Project.MSG_INFO ); + Reader reader = ( encoding == null ) ? new FileReader( tSrcFile ) + : new InputStreamReader( new FileInputStream( tSrcFile ), encoding ); + runStatements( reader, out ); + reader.close(); + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SendEmail.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SendEmail.java new file mode 100644 index 000000000..052ed4940 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SendEmail.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.mail.MailMessage; + +/** + * A task to send SMTP email.

      + * + * + * + * + * + * + * + * Attribute + * + * + * + * Description + * + * + * + * Required + * + * + * + * + * + * + * + * from + * + * + * + * Email address of sender. + * + * + * + * Yes + * + * + * + * + * + * + * + * mailhost + * + * + * + * Host name of the mail server. + * + * + * + * No, default to "localhost" + * + * + * + * + * + * + * + * toList + * + * + * + * Comma-separated list of recipients. + * + * + * + * Yes + * + * + * + * + * + * + * + * subject + * + * + * + * Email subject line. + * + * + * + * No + * + * + * + * + * + * + * + * files + * + * + * + * Filename(s) of text to send in the body of the email. Multiple files + * are comma-separated. + * + * + * + * One of these two attributes + * + * + * + * + * + * + * + * message + * + * + * + * Message to send inthe body of the email. + * + * + * + * + * + * + * + * + * + * includefilenames + * + * + * + * Includes filenames before file contents when set to true. + * + * + * + * No, default is false + * + * + * + *

      + * + * + * + * @author glenn_twiggs@bmc.com + * @author Magesh Umasankar + */ +public class SendEmail extends Task +{ + private String mailhost = "localhost"; + private int mailport = MailMessage.DEFAULT_PORT; + private Vector files = new Vector(); + /** + * failure flag + */ + private boolean failOnError = true; + private String from; + private boolean includefilenames; + private String message; + private String subject; + private String toList; + + + /** + * Creates new SendEmail + */ + public SendEmail() { } + + /** + * Sets the FailOnError attribute of the MimeMail object + * + * @param failOnError The new FailOnError value + * @since 1.5 + */ + public void setFailOnError( boolean failOnError ) + { + this.failOnError = failOnError; + } + + /** + * Sets the file parameter of this build task. + * + * @param filenames Filenames to include as the message body of this email. + */ + public void setFiles( String filenames ) + { + StringTokenizer t = new StringTokenizer( filenames, ", " ); + + while( t.hasMoreTokens() ) + { + files.addElement( project.resolveFile( t.nextToken() ) ); + } + } + + /** + * Sets the from parameter of this build task. + * + * @param from Email address of sender. + */ + public void setFrom( String from ) + { + this.from = from; + } + + /** + * Sets Includefilenames attribute + * + * @param includefilenames Set to true if file names are to be included. + * @since 1.5 + */ + public void setIncludefilenames( boolean includefilenames ) + { + this.includefilenames = includefilenames; + } + + /** + * Sets the mailhost parameter of this build task. + * + * @param mailhost Mail host name. + */ + public void setMailhost( String mailhost ) + { + this.mailhost = mailhost; + } + + /** + * Sets the mailport parameter of this build task. + * + * @param value mail port name. + */ + public void setMailport( Integer value ) + { + this.mailport = value.intValue(); + } + + /** + * Sets the message parameter of this build task. + * + * @param message Message body of this email. + */ + public void setMessage( String message ) + { + this.message = message; + } + + /** + * Sets the subject parameter of this build task. + * + * @param subject Subject of this email. + */ + public void setSubject( String subject ) + { + this.subject = subject; + } + + /** + * Sets the toList parameter of this build task. + * + * @param toList Comma-separated list of email recipient addreses. + */ + public void setToList( String toList ) + { + this.toList = toList; + } + + /** + * Executes this build task. + * + * @throws BuildException if there is an error during task execution. + */ + public void execute() + throws BuildException + { + try + { + MailMessage mailMessage = new MailMessage( mailhost ); + mailMessage.setPort( mailport ); + + if( from != null ) + { + mailMessage.from( from ); + } + else + { + throw new BuildException( "Attribute \"from\" is required." ); + } + + if( toList != null ) + { + StringTokenizer t = new StringTokenizer( toList, ", ", false ); + + while( t.hasMoreTokens() ) + { + mailMessage.to( t.nextToken() ); + } + } + else + { + throw new BuildException( "Attribute \"toList\" is required." ); + } + + if( subject != null ) + { + mailMessage.setSubject( subject ); + } + + if( !files.isEmpty() ) + { + PrintStream out = mailMessage.getPrintStream(); + + for( Enumeration e = files.elements(); e.hasMoreElements(); ) + { + File file = ( File )e.nextElement(); + + if( file.exists() && file.canRead() ) + { + int bufsize = 1024; + int length; + byte[] buf = new byte[bufsize]; + if( includefilenames ) + { + String filename = file.getName(); + int filenamelength = filename.length(); + out.println( filename ); + for( int star = 0; star < filenamelength; star++ ) + { + out.print( '=' ); + } + out.println(); + } + BufferedInputStream in = null; + try + { + in = new BufferedInputStream( + new FileInputStream( file ), bufsize ); + while( ( length = in.read( buf, 0, bufsize ) ) != -1 ) + { + out.write( buf, 0, length ); + } + if( includefilenames ) + { + out.println(); + } + } + finally + { + if( in != null ) + { + try + { + in.close(); + } + catch( IOException ioe ) + {} + } + } + + } + else + { + throw new BuildException( "File \"" + file.getName() + + "\" does not exist or is not readable." ); + } + } + } + else if( message != null ) + { + PrintStream out = mailMessage.getPrintStream(); + out.print( message ); + } + else + { + throw new BuildException( "Attribute \"file\" or \"message\" is required." ); + } + + log( "Sending email" ); + mailMessage.sendAndClose(); + } + catch( IOException ioe ) + { + String err = "IO error sending mail " + ioe.toString(); + if( failOnError ) + { + throw new BuildException( err, ioe, location ); + } + else + { + log( err, Project.MSG_ERR ); + } + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sequential.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sequential.java new file mode 100644 index 000000000..59fa575be --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sequential.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.TaskContainer; + + +/** + * Implements a single threaded task execution.

      + * + * + * + * @author Thomas Christen chr@active.ch + */ +public class Sequential extends Task + implements TaskContainer +{ + + /** + * Optional Vector holding the nested tasks + */ + private Vector nestedTasks = new Vector(); + + /** + * Add a nested task to Sequential.

      + * + * + * + * @param nestedTask Nested task to execute Sequential

      + * + * + */ + public void addTask( Task nestedTask ) + { + nestedTasks.addElement( nestedTask ); + } + + /** + * Execute all nestedTasks. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + for( Enumeration e = nestedTasks.elements(); e.hasMoreElements(); ) + { + Task nestedTask = ( Task )e.nextElement(); + nestedTask.perform(); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SignJar.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SignJar.java new file mode 100644 index 000000000..29ffddd1a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/SignJar.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; + +/** + * Sign a archive. + * + * @author Peter Donald donaldp@apache.org + * + * @author Nick Fortescue + * nick@ox.compsoc.net + */ +public class SignJar extends Task +{ + + /** + * the filesets of the jars to sign + */ + protected Vector filesets = new Vector(); + + /** + * The alias of signer. + */ + protected String alias; + protected boolean internalsf; + + /** + * The name of the jar file. + */ + protected File jar; + protected String keypass; + + /** + * The name of keystore file. + */ + protected File keystore; + /** + * Whether to assume a jar which has an appropriate .SF file in is already + * signed. + */ + protected boolean lazy; + protected boolean sectionsonly; + protected File sigfile; + protected File signedjar; + + protected String storepass; + protected String storetype; + protected boolean verbose; + + public void setAlias( final String alias ) + { + this.alias = alias; + } + + public void setInternalsf( final boolean internalsf ) + { + this.internalsf = internalsf; + } + + public void setJar( final File jar ) + { + this.jar = jar; + } + + public void setKeypass( final String keypass ) + { + this.keypass = keypass; + } + + public void setKeystore( final File keystore ) + { + this.keystore = keystore; + } + + public void setLazy( final boolean lazy ) + { + this.lazy = lazy; + } + + public void setSectionsonly( final boolean sectionsonly ) + { + this.sectionsonly = sectionsonly; + } + + public void setSigfile( final File sigfile ) + { + this.sigfile = sigfile; + } + + public void setSignedjar( final File signedjar ) + { + this.signedjar = signedjar; + } + + public void setStorepass( final String storepass ) + { + this.storepass = storepass; + } + + public void setStoretype( final String storetype ) + { + this.storetype = storetype; + } + + public void setVerbose( final boolean verbose ) + { + this.verbose = verbose; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( final FileSet set ) + { + filesets.addElement( set ); + } + + + public void execute() + throws BuildException + { + if( null == jar && null == filesets ) + { + throw new BuildException( "jar must be set through jar attribute or nested filesets" ); + } + if( null != jar ) + { + doOneJar( jar, signedjar ); + return; + } + else + { + //Assume null != filesets + + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] jarFiles = ds.getIncludedFiles(); + for( int j = 0; j < jarFiles.length; j++ ) + { + doOneJar( new File( fs.getDir( project ), jarFiles[j] ), null ); + } + } + } + } + + protected boolean isSigned( File file ) + { + final String SIG_START = "META-INF/"; + final String SIG_END = ".SF"; + + if( !file.exists() ) + { + return false; + } + ZipFile jarFile = null; + try + { + jarFile = new ZipFile( file ); + if( null == alias ) + { + Enumeration entries = jarFile.entries(); + while( entries.hasMoreElements() ) + { + String name = ( ( ZipEntry )entries.nextElement() ).getName(); + if( name.startsWith( SIG_START ) && name.endsWith( SIG_END ) ) + { + return true; + } + } + return false; + } + else + { + return jarFile.getEntry( SIG_START + alias.toUpperCase() + + SIG_END ) != null; + } + } + catch( IOException e ) + { + return false; + } + finally + { + if( jarFile != null ) + { + try + { + jarFile.close(); + } + catch( IOException e ) + {} + } + } + } + + protected boolean isUpToDate( File jarFile, File signedjarFile ) + { + if( null == jarFile ) + { + return false; + } + + if( null != signedjarFile ) + { + + if( !jarFile.exists() ) + return false; + if( !signedjarFile.exists() ) + return false; + if( jarFile.equals( signedjarFile ) ) + return false; + if( signedjarFile.lastModified() > jarFile.lastModified() ) + return true; + } + else + { + if( lazy ) + { + return isSigned( jarFile ); + } + } + + return false; + } + + private void doOneJar( File jarSource, File jarTarget ) + throws BuildException + { + if( project.getJavaVersion().equals( Project.JAVA_1_1 ) ) + { + throw new BuildException( "The signjar task is only available on JDK versions 1.2 or greater" ); + } + + if( null == alias ) + { + throw new BuildException( "alias attribute must be set" ); + } + + if( null == storepass ) + { + throw new BuildException( "storepass attribute must be set" ); + } + + if( isUpToDate( jarSource, jarTarget ) ) + return; + + final StringBuffer sb = new StringBuffer(); + + final ExecTask cmd = ( ExecTask )project.createTask( "exec" ); + cmd.setExecutable( "jarsigner" ); + + if( null != keystore ) + { + cmd.createArg().setValue( "-keystore" ); + cmd.createArg().setValue( keystore.toString() ); + } + + if( null != storepass ) + { + cmd.createArg().setValue( "-storepass" ); + cmd.createArg().setValue( storepass ); + } + + if( null != storetype ) + { + cmd.createArg().setValue( "-storetype" ); + cmd.createArg().setValue( storetype ); + } + + if( null != keypass ) + { + cmd.createArg().setValue( "-keypass" ); + cmd.createArg().setValue( keypass ); + } + + if( null != sigfile ) + { + cmd.createArg().setValue( "-sigfile" ); + cmd.createArg().setValue( sigfile.toString() ); + } + + if( null != jarTarget ) + { + cmd.createArg().setValue( "-signedjar" ); + cmd.createArg().setValue( jarTarget.toString() ); + } + + if( verbose ) + { + cmd.createArg().setValue( "-verbose" ); + } + + if( internalsf ) + { + cmd.createArg().setValue( "-internalsf" ); + } + + if( sectionsonly ) + { + cmd.createArg().setValue( "-sectionsonly" ); + } + + cmd.createArg().setValue( jarSource.toString() ); + + cmd.createArg().setValue( alias ); + + log( "Signing Jar : " + jarSource.getAbsolutePath() ); + cmd.setFailonerror( true ); + cmd.setTaskName( getTaskName() ); + cmd.execute(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sleep.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sleep.java new file mode 100644 index 000000000..2c8d81c86 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Sleep.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * A task to sleep for a period of time + * + * @author steve_l@iseran.com steve loughran + * @created 01 May 2001 + */ + +public class Sleep extends Task +{ + /** + * failure flag + */ + private boolean failOnError = true; + + /** + * Description of the Field + */ + private int seconds = 0; + /** + * Description of the Field + */ + private int hours = 0; + /** + * Description of the Field + */ + private int minutes = 0; + /** + * Description of the Field + */ + private int milliseconds = 0; + + + /** + * Creates new instance + */ + public Sleep() { } + + + /** + * Sets the FailOnError attribute of the MimeMail object + * + * @param failOnError The new FailOnError value + */ + public void setFailOnError( boolean failOnError ) + { + this.failOnError = failOnError; + } + + + /** + * Sets the Hours attribute of the Sleep object + * + * @param hours The new Hours value + */ + public void setHours( int hours ) + { + this.hours = hours; + } + + + /** + * Sets the Milliseconds attribute of the Sleep object + * + * @param milliseconds The new Milliseconds value + */ + public void setMilliseconds( int milliseconds ) + { + this.milliseconds = milliseconds; + } + + + /** + * Sets the Minutes attribute of the Sleep object + * + * @param minutes The new Minutes value + */ + public void setMinutes( int minutes ) + { + this.minutes = minutes; + } + + + /** + * Sets the Seconds attribute of the Sleep object + * + * @param seconds The new Seconds value + */ + public void setSeconds( int seconds ) + { + this.seconds = seconds; + } + + + /** + * sleep for a period of time + * + * @param millis time to sleep + */ + public void doSleep( long millis ) + { + try + { + Thread.currentThread().sleep( millis ); + } + catch( InterruptedException ie ) + { + } + } + + + /** + * Executes this build task. throws org.apache.tools.ant.BuildException if + * there is an error during task execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + try + { + validate(); + long sleepTime = getSleepTime(); + log( "sleeping for " + sleepTime + " milliseconds", + Project.MSG_VERBOSE ); + doSleep( sleepTime ); + } + catch( Exception e ) + { + if( failOnError ) + { + throw new BuildException( e ); + } + else + { + String text = e.toString(); + log( text, Project.MSG_ERR ); + } + } + } + + + /** + * verify parameters + * + * @throws BuildException if something is invalid + */ + public void validate() + throws BuildException + { + long sleepTime = getSleepTime(); + if( getSleepTime() < 0 ) + { + throw new BuildException( "Negative sleep periods are not supported" ); + } + } + + + /** + * return time to sleep + * + * @return sleep time. if below 0 then there is an error + */ + + private long getSleepTime() + { + return ( ( ( ( long )hours * 60 ) + minutes ) * 60 + seconds ) * 1000 + milliseconds; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/StreamPumper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/StreamPumper.java new file mode 100644 index 000000000..8e8a8e9ef --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/StreamPumper.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Copies all data from an input stream to an output stream. + * + * @author thomas.haas@softwired-inc.com + */ +public class StreamPumper implements Runnable +{ + + // TODO: make SIZE and SLEEP instance variables. + // TODO: add a status flag to note if an error occured in run. + + private final static int SLEEP = 5; + private final static int SIZE = 128; + private InputStream is; + private OutputStream os; + + + /** + * Create a new stream pumper. + * + * @param is input stream to read data from + * @param os output stream to write data to. + */ + public StreamPumper( InputStream is, OutputStream os ) + { + this.is = is; + this.os = os; + } + + + /** + * Copies data from the input stream to the output stream. Terminates as + * soon as the input stream is closed or an error occurs. + */ + public void run() + { + final byte[] buf = new byte[SIZE]; + + int length; + try + { + while( ( length = is.read( buf ) ) > 0 ) + { + os.write( buf, 0, length ); + try + { + Thread.sleep( SLEEP ); + } + catch( InterruptedException e ) + {} + } + } + catch( IOException e ) + {} + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tar.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tar.java new file mode 100644 index 000000000..2f3fe3cdc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tar.java @@ -0,0 +1,481 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.SourceFileScanner; +import org.apache.tools.tar.TarConstants; +import org.apache.tools.tar.TarEntry; +import org.apache.tools.tar.TarOutputStream; + +/** + * Creates a TAR archive. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Stefan Bodewig + * @author Magesh Umasankar + */ + +public class Tar extends MatchingTask +{ + + /** + * @deprecated Tar.WARN is deprecated and is replaced with + * Tar.TarLongFileMode.WARN + */ + public final static String WARN = "warn"; + /** + * @deprecated Tar.FAIL is deprecated and is replaced with + * Tar.TarLongFileMode.FAIL + */ + public final static String FAIL = "fail"; + /** + * @deprecated Tar.TRUNCATE is deprecated and is replaced with + * Tar.TarLongFileMode.TRUNCATE + */ + public final static String TRUNCATE = "truncate"; + /** + * @deprecated Tar.GNU is deprecated and is replaced with + * Tar.TarLongFileMode.GNU + */ + public final static String GNU = "gnu"; + /** + * @deprecated Tar.OMIT is deprecated and is replaced with + * Tar.TarLongFileMode.OMIT + */ + public final static String OMIT = "omit"; + + private TarLongFileMode longFileMode = new TarLongFileMode(); + + Vector filesets = new Vector(); + Vector fileSetFiles = new Vector(); + + /** + * Indicates whether the user has been warned about long files already. + */ + private boolean longWarningGiven = false; + File baseDir; + + File tarFile; + + /** + * This is the base directory to look in for things to tar. + * + * @param baseDir The new Basedir value + */ + public void setBasedir( File baseDir ) + { + this.baseDir = baseDir; + } + + /** + * Set how to handle long files. Allowable values are truncate - paths are + * truncated to the maximum length fail - paths greater than the maximim + * cause a build exception warn - paths greater than the maximum cause a + * warning and GNU is used gnu - GNU extensions are used for any paths + * greater than the maximum. omit - paths greater than the maximum are + * omitted from the archive + * + * @param mode The new Longfile value + * @deprecated setLongFile(String) is deprecated and is replaced with + * setLongFile(Tar.TarLongFileMode) to make Ant's Introspection + * mechanism do the work and also to encapsulate operations on the mode + * in its own class. + */ + public void setLongfile( String mode ) + { + log( "DEPRECATED - The setLongfile(String) method has been deprecated." + + " Use setLongfile(Tar.TarLongFileMode) instead." ); + this.longFileMode = new TarLongFileMode(); + longFileMode.setValue( mode ); + } + + /** + * Set how to handle long files. Allowable values are truncate - paths are + * truncated to the maximum length fail - paths greater than the maximim + * cause a build exception warn - paths greater than the maximum cause a + * warning and GNU is used gnu - GNU extensions are used for any paths + * greater than the maximum. omit - paths greater than the maximum are + * omitted from the archive + * + * @param mode The new Longfile value + */ + public void setLongfile( TarLongFileMode mode ) + { + this.longFileMode = mode; + } + + + /** + * This is the name/location of where to create the tar file. + * + * @param tarFile The new Tarfile value + */ + public void setTarfile( File tarFile ) + { + this.tarFile = tarFile; + } + + public TarFileSet createTarFileSet() + { + TarFileSet fileset = new TarFileSet(); + filesets.addElement( fileset ); + return fileset; + } + + public void execute() + throws BuildException + { + if( tarFile == null ) + { + throw new BuildException( "tarfile attribute must be set!", + location ); + } + + if( tarFile.exists() && tarFile.isDirectory() ) + { + throw new BuildException( "tarfile is a directory!", + location ); + } + + if( tarFile.exists() && !tarFile.canWrite() ) + { + throw new BuildException( "Can not write to the specified tarfile!", + location ); + } + + if( baseDir != null ) + { + if( !baseDir.exists() ) + { + throw new BuildException( "basedir does not exist!", location ); + } + + // add the main fileset to the list of filesets to process. + TarFileSet mainFileSet = new TarFileSet( fileset ); + mainFileSet.setDir( baseDir ); + filesets.addElement( mainFileSet ); + } + + if( filesets.size() == 0 ) + { + throw new BuildException( "You must supply either a basdir attribute or some nested filesets.", + location ); + } + + // check if tr is out of date with respect to each + // fileset + boolean upToDate = true; + for( Enumeration e = filesets.elements(); e.hasMoreElements(); ) + { + TarFileSet fs = ( TarFileSet )e.nextElement(); + String[] files = fs.getFiles( project ); + + if( !archiveIsUpToDate( files ) ) + { + upToDate = false; + } + + for( int i = 0; i < files.length; ++i ) + { + if( tarFile.equals( new File( fs.getDir( project ), files[i] ) ) ) + { + throw new BuildException( "A tar file cannot include itself", location ); + } + } + } + + if( upToDate ) + { + log( "Nothing to do: " + tarFile.getAbsolutePath() + " is up to date.", + Project.MSG_INFO ); + return; + } + + log( "Building tar: " + tarFile.getAbsolutePath(), Project.MSG_INFO ); + + TarOutputStream tOut = null; + try + { + tOut = new TarOutputStream( new FileOutputStream( tarFile ) ); + tOut.setDebug( true ); + if( longFileMode.isTruncateMode() ) + { + tOut.setLongFileMode( TarOutputStream.LONGFILE_TRUNCATE ); + } + else if( longFileMode.isFailMode() || + longFileMode.isOmitMode() ) + { + tOut.setLongFileMode( TarOutputStream.LONGFILE_ERROR ); + } + else + { + // warn or GNU + tOut.setLongFileMode( TarOutputStream.LONGFILE_GNU ); + } + + longWarningGiven = false; + for( Enumeration e = filesets.elements(); e.hasMoreElements(); ) + { + TarFileSet fs = ( TarFileSet )e.nextElement(); + String[] files = fs.getFiles( project ); + for( int i = 0; i < files.length; i++ ) + { + File f = new File( fs.getDir( project ), files[i] ); + String name = files[i].replace( File.separatorChar, '/' ); + tarFile( f, tOut, name, fs ); + } + } + } + catch( IOException ioe ) + { + String msg = "Problem creating TAR: " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( tOut != null ) + { + try + { + // close up + tOut.close(); + } + catch( IOException e ) + {} + } + } + } + + protected boolean archiveIsUpToDate( String[] files ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + MergingMapper mm = new MergingMapper(); + mm.setTo( tarFile.getAbsolutePath() ); + return sfs.restrict( files, baseDir, null, mm ).length == 0; + } + + protected void tarFile( File file, TarOutputStream tOut, String vPath, + TarFileSet tarFileSet ) + throws IOException + { + FileInputStream fIn = null; + + // don't add "" to the archive + if( vPath.length() <= 0 ) + { + return; + } + + if( file.isDirectory() && !vPath.endsWith( "/" ) ) + { + vPath += "/"; + } + + try + { + if( vPath.length() >= TarConstants.NAMELEN ) + { + if( longFileMode.isOmitMode() ) + { + log( "Omitting: " + vPath, Project.MSG_INFO ); + return; + } + else if( longFileMode.isWarnMode() ) + { + log( "Entry: " + vPath + " longer than " + + TarConstants.NAMELEN + " characters.", Project.MSG_WARN ); + if( !longWarningGiven ) + { + log( "Resulting tar file can only be processed successfully" + + " by GNU compatible tar commands", Project.MSG_WARN ); + longWarningGiven = true; + } + } + else if( longFileMode.isFailMode() ) + { + throw new BuildException( + "Entry: " + vPath + " longer than " + + TarConstants.NAMELEN + "characters.", location ); + } + } + + TarEntry te = new TarEntry( vPath ); + te.setModTime( file.lastModified() ); + if( !file.isDirectory() ) + { + te.setSize( file.length() ); + te.setMode( tarFileSet.getMode() ); + } + te.setUserName( tarFileSet.getUserName() ); + te.setGroupName( tarFileSet.getGroup() ); + + tOut.putNextEntry( te ); + + if( !file.isDirectory() ) + { + fIn = new FileInputStream( file ); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + tOut.write( buffer, 0, count ); + count = fIn.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } + + tOut.closeEntry(); + } + finally + { + if( fIn != null ) + fIn.close(); + } + } + + public static class TarFileSet extends FileSet + { + private String[] files = null; + + private int mode = 0100644; + + private String userName = ""; + private String groupName = ""; + + + public TarFileSet( FileSet fileset ) + { + super( fileset ); + } + + public TarFileSet() + { + super(); + } + + public void setGroup( String groupName ) + { + this.groupName = groupName; + } + + public void setMode( String octalString ) + { + this.mode = 0100000 | Integer.parseInt( octalString, 8 ); + } + + public void setUserName( String userName ) + { + this.userName = userName; + } + + /** + * Get a list of files and directories specified in the fileset. + * + * @param p Description of Parameter + * @return a list of file and directory names, relative to the baseDir + * for the project. + */ + public String[] getFiles( Project p ) + { + if( files == null ) + { + DirectoryScanner ds = getDirectoryScanner( p ); + String[] directories = ds.getIncludedDirectories(); + String[] filesPerSe = ds.getIncludedFiles(); + files = new String[directories.length + filesPerSe.length]; + System.arraycopy( directories, 0, files, 0, directories.length ); + System.arraycopy( filesPerSe, 0, files, directories.length, + filesPerSe.length ); + } + + return files; + } + + public String getGroup() + { + return groupName; + } + + public int getMode() + { + return mode; + } + + public String getUserName() + { + return userName; + } + + } + + /** + * Valid Modes for LongFile attribute to Tar Task + * + * @author Magesh Umasankar + */ + public static class TarLongFileMode extends EnumeratedAttribute + { + + // permissable values for longfile attribute + public final static String WARN = "warn"; + public final static String FAIL = "fail"; + public final static String TRUNCATE = "truncate"; + public final static String GNU = "gnu"; + public final static String OMIT = "omit"; + + private final String[] validModes = {WARN, FAIL, TRUNCATE, GNU, OMIT}; + + public TarLongFileMode() + { + super(); + setValue( WARN ); + } + + public String[] getValues() + { + return validModes; + } + + public boolean isFailMode() + { + return FAIL.equalsIgnoreCase( getValue() ); + } + + public boolean isGnuMode() + { + return GNU.equalsIgnoreCase( getValue() ); + } + + public boolean isOmitMode() + { + return OMIT.equalsIgnoreCase( getValue() ); + } + + public boolean isTruncateMode() + { + return TRUNCATE.equalsIgnoreCase( getValue() ); + } + + public boolean isWarnMode() + { + return WARN.equalsIgnoreCase( getValue() ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/TaskOutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/TaskOutputStream.java new file mode 100644 index 000000000..5af5c9934 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/TaskOutputStream.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.tools.ant.Task; + +/** + * Redirects text written to a stream thru the standard ant logging mechanism. + * This class is useful for integrating with tools that write to System.out and + * System.err. For example, the following will cause all text written to + * System.out to be logged with "info" priority:

      System.setOut(new PrintStream(new TaskOutputStream(project, Project.MSG_INFO)));
      + * + * @author James Duncan Davidson (duncan@x180.com) + * @deprecated use LogOutputStream instead. + */ + +public class TaskOutputStream extends OutputStream +{ + private StringBuffer line; + private int msgOutputLevel; + + private Task task; + + /** + * Constructs a new JavacOutputStream with the given project as the output + * source for messages. + * + * @param task Description of Parameter + * @param msgOutputLevel Description of Parameter + */ + + TaskOutputStream( Task task, int msgOutputLevel ) + { + this.task = task; + this.msgOutputLevel = msgOutputLevel; + + line = new StringBuffer(); + } + + /** + * Write a character to the output stream. This method looks to make sure + * that there isn't an error being reported and will flush each line of + * input out to the project's log stream. + * + * @param c Description of Parameter + * @exception IOException Description of Exception + */ + + public void write( int c ) + throws IOException + { + char cc = ( char )c; + if( cc == '\r' || cc == '\n' ) + { + // line feed + if( line.length() > 0 ) + { + processLine(); + } + } + else + { + line.append( cc ); + } + } + + /** + * Processes a line of input and determines if an error occured. + */ + + private void processLine() + { + String s = line.toString(); + task.log( s, msgOutputLevel ); + line = new StringBuffer(); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Taskdef.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Taskdef.java new file mode 100644 index 000000000..609a2771d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Taskdef.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; + +/** + * Define a new task. + * + * @author Stefan Bodewig + */ +public class Taskdef extends Definer +{ + protected void addDefinition( String name, Class c ) + throws BuildException + { + project.addTaskDefinition( name, c ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Touch.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Touch.java new file mode 100644 index 000000000..358597118 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Touch.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Locale; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.FileUtils; + +/** + * Touch a file and/or fileset(s) -- corresponds to the Unix touch command.

      + * + * If the file to touch doesn't exist, an empty one is created.

      + * + * Note: Setting the modification time of files is not supported in JDK 1.1.

      + * + * @author Stefan Bodewig + * @author Michael J. Sikorsky + * @author Robert Shaw + */ +public class Touch extends Task +{// required + private long millis = -1; + private Vector filesets = new Vector(); + private String dateTime; + + private File file; + private FileUtils fileUtils; + + public Touch() + { + fileUtils = FileUtils.newFileUtils(); + } + + /** + * Date in the format MM/DD/YYYY HH:MM AM_PM. + * + * @param dateTime The new Datetime value + */ + public void setDatetime( String dateTime ) + { + this.dateTime = dateTime; + } + + /** + * Sets a single source file to touch. If the file does not exist an empty + * file will be created. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Milliseconds since 01/01/1970 00:00 am. + * + * @param millis The new Millis value + */ + public void setMillis( long millis ) + { + this.millis = millis; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Execute the touch operation. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( file == null && filesets.size() == 0 ) + { + throw + new BuildException( "Specify at least one source - a file or a fileset." ); + } + + if( file != null && file.exists() && file.isDirectory() ) + { + throw new BuildException( "Use a fileset to touch directories." ); + } + + if( dateTime != null ) + { + DateFormat df = DateFormat.getDateTimeInstance( DateFormat.SHORT, + DateFormat.SHORT, + Locale.US ); + try + { + setMillis( df.parse( dateTime ).getTime() ); + if( millis < 0 ) + { + throw new BuildException( "Date of " + dateTime + + " results in negative milliseconds value relative to epoch (January 1, 1970, 00:00:00 GMT)." ); + } + } + catch( ParseException pe ) + { + throw new BuildException( pe.getMessage(), pe, location ); + } + } + + touch(); + } + + /** + * Does the actual work. Entry point for Untar and Expand as well. + * + * @exception BuildException Description of Exception + */ + protected void touch() + throws BuildException + { + if( file != null ) + { + if( !file.exists() ) + { + log( "Creating " + file, Project.MSG_INFO ); + try + { + FileOutputStream fos = new FileOutputStream( file ); + fos.write( new byte[0] ); + fos.close(); + } + catch( IOException ioe ) + { + throw new BuildException( "Could not create " + file, ioe, + location ); + } + } + } + + if( millis >= 0 && project.getJavaVersion() == Project.JAVA_1_1 ) + { + log( "modification time of files cannot be set in JDK 1.1", + Project.MSG_WARN ); + return; + } + + boolean resetMillis = false; + if( millis < 0 ) + { + resetMillis = true; + millis = System.currentTimeMillis(); + } + + if( file != null ) + { + touch( file ); + } + + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File fromDir = fs.getDir( project ); + + String[] srcFiles = ds.getIncludedFiles(); + String[] srcDirs = ds.getIncludedDirectories(); + + for( int j = 0; j < srcFiles.length; j++ ) + { + touch( new File( fromDir, srcFiles[j] ) ); + } + + for( int j = 0; j < srcDirs.length; j++ ) + { + touch( new File( fromDir, srcDirs[j] ) ); + } + } + + if( resetMillis ) + { + millis = -1; + } + } + + protected void touch( File file ) + throws BuildException + { + if( !file.canWrite() ) + { + throw new BuildException( "Can not change modification date of read-only file " + file ); + } + + if( project.getJavaVersion() == Project.JAVA_1_1 ) + { + return; + } + + fileUtils.setFileLastModified( file, millis ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Transform.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Transform.java new file mode 100644 index 000000000..02ecfae96 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Transform.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +/** + * Has been merged into ExecuteOn, empty class for backwards compatibility. + * + * @author Stefan Bodewig + */ +public class Transform extends ExecuteOn +{ +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tstamp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tstamp.java new file mode 100644 index 000000000..276d964e0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Tstamp.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; +import java.util.TimeZone; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Sets TSTAMP, DSTAMP and TODAY + * + * @author costin@dnt.ro + * @author stefano@apache.org + * @author roxspring@yahoo.com + * @author conor@cognet.com.au + * @author Magesh Umasankar + */ +public class Tstamp extends Task +{ + + private Vector customFormats = new Vector(); + private String prefix = ""; + + public void setPrefix( String prefix ) + { + this.prefix = prefix; + if( !this.prefix.endsWith( "." ) ) + { + this.prefix += "."; + } + } + + public CustomFormat createFormat() + { + CustomFormat cts = new CustomFormat( prefix ); + customFormats.addElement( cts ); + return cts; + } + + public void execute() + throws BuildException + { + try + { + Date d = new Date(); + + SimpleDateFormat dstamp = new SimpleDateFormat( "yyyyMMdd" ); + project.setNewProperty( prefix + "DSTAMP", dstamp.format( d ) ); + + SimpleDateFormat tstamp = new SimpleDateFormat( "HHmm" ); + project.setNewProperty( prefix + "TSTAMP", tstamp.format( d ) ); + + SimpleDateFormat today = new SimpleDateFormat( "MMMM d yyyy", Locale.US ); + project.setNewProperty( prefix + "TODAY", today.format( d ) ); + + Enumeration i = customFormats.elements(); + while( i.hasMoreElements() ) + { + CustomFormat cts = ( CustomFormat )i.nextElement(); + cts.execute( project, d, location ); + } + + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + public static class Unit extends EnumeratedAttribute + { + + private final static String MILLISECOND = "millisecond"; + private final static String SECOND = "second"; + private final static String MINUTE = "minute"; + private final static String HOUR = "hour"; + private final static String DAY = "day"; + private final static String WEEK = "week"; + private final static String MONTH = "month"; + private final static String YEAR = "year"; + + private final static String[] units = { + MILLISECOND, + SECOND, + MINUTE, + HOUR, + DAY, + WEEK, + MONTH, + YEAR + }; + + private Hashtable calendarFields = new Hashtable(); + + public Unit() + { + calendarFields.put( MILLISECOND, + new Integer( Calendar.MILLISECOND ) ); + calendarFields.put( SECOND, new Integer( Calendar.SECOND ) ); + calendarFields.put( MINUTE, new Integer( Calendar.MINUTE ) ); + calendarFields.put( HOUR, new Integer( Calendar.HOUR_OF_DAY ) ); + calendarFields.put( DAY, new Integer( Calendar.DATE ) ); + calendarFields.put( WEEK, new Integer( Calendar.WEEK_OF_YEAR ) ); + calendarFields.put( MONTH, new Integer( Calendar.MONTH ) ); + calendarFields.put( YEAR, new Integer( Calendar.YEAR ) ); + } + + public int getCalendarField() + { + String key = getValue().toLowerCase(); + Integer i = ( Integer )calendarFields.get( key ); + return i.intValue(); + } + + public String[] getValues() + { + return units; + } + } + + public class CustomFormat + { + private int offset = 0; + private int field = Calendar.DATE; + private String prefix = ""; + private String country; + private String language; + private String pattern; + private String propertyName; + private TimeZone timeZone; + private String variant; + + public CustomFormat( String prefix ) + { + this.prefix = prefix; + } + + public void setLocale( String locale ) + { + StringTokenizer st = new StringTokenizer( locale, " \t\n\r\f," ); + try + { + language = st.nextToken(); + if( st.hasMoreElements() ) + { + country = st.nextToken(); + if( st.hasMoreElements() ) + { + country = st.nextToken(); + if( st.hasMoreElements() ) + { + throw new BuildException( "bad locale format", getLocation() ); + } + } + } + else + { + country = ""; + } + } + catch( NoSuchElementException e ) + { + throw new BuildException( "bad locale format", e, getLocation() ); + } + } + + public void setOffset( int offset ) + { + this.offset = offset; + } + + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + public void setProperty( String propertyName ) + { + this.propertyName = prefix + propertyName; + } + + public void setTimezone( String id ) + { + timeZone = TimeZone.getTimeZone( id ); + } + + /** + * @param unit The new Unit value + * @deprecated setUnit(String) is deprecated and is replaced with + * setUnit(Tstamp.Unit) to make Ant's Introspection mechanism do + * the work and also to encapsulate operations on the unit in its + * own class. + */ + public void setUnit( String unit ) + { + log( "DEPRECATED - The setUnit(String) method has been deprecated." + + " Use setUnit(Tstamp.Unit) instead." ); + Unit u = new Unit(); + u.setValue( unit ); + field = u.getCalendarField(); + } + + public void setUnit( Unit unit ) + { + field = unit.getCalendarField(); + } + + public void execute( Project project, Date date, Location location ) + { + if( propertyName == null ) + { + throw new BuildException( "property attribute must be provided", location ); + } + + if( pattern == null ) + { + throw new BuildException( "pattern attribute must be provided", location ); + } + + SimpleDateFormat sdf; + if( language == null ) + { + sdf = new SimpleDateFormat( pattern ); + } + else if( variant == null ) + { + sdf = new SimpleDateFormat( pattern, new Locale( language, country ) ); + } + else + { + sdf = new SimpleDateFormat( pattern, new Locale( language, country, variant ) ); + } + if( offset != 0 ) + { + Calendar calendar = Calendar.getInstance(); + calendar.setTime( date ); + calendar.add( field, offset ); + date = calendar.getTime(); + } + if( timeZone != null ) + { + sdf.setTimeZone( timeZone ); + } + project.setNewProperty( propertyName, sdf.format( date ) ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Typedef.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Typedef.java new file mode 100644 index 000000000..2bb6ac880 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Typedef.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; + +/** + * Define a new data type. + * + * @author Stefan Bodewig + */ +public class Typedef extends Definer +{ + protected void addDefinition( String name, Class c ) + throws BuildException + { + project.addDataTypeDefinition( name, c ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Unpack.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Unpack.java new file mode 100644 index 000000000..1e641b251 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Unpack.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Abstract Base class for unpack tasks. + * + * @author Magesh Umasankar + */ + +public abstract class Unpack extends Task +{ + protected File dest; + + protected File source; + + public void setDest( String dest ) + { + this.dest = project.resolveFile( dest ); + } + + public void setSrc( String src ) + { + source = project.resolveFile( src ); + } + + public void execute() + throws BuildException + { + validate(); + extract(); + } + + protected abstract String getDefaultExtension(); + + protected abstract void extract(); + + private void createDestFile( String defaultExtension ) + { + String sourceName = source.getName(); + int len = sourceName.length(); + if( defaultExtension != null + && len > defaultExtension.length() + && defaultExtension.equalsIgnoreCase( sourceName.substring( len - defaultExtension.length() ) ) ) + { + dest = new File( dest, sourceName.substring( 0, + len - defaultExtension.length() ) ); + } + else + { + dest = new File( dest, sourceName ); + } + } + + private void validate() + throws BuildException + { + if( source == null ) + { + throw new BuildException( "No Src for gunzip specified", location ); + } + + if( !source.exists() ) + { + throw new BuildException( "Src doesn't exist", location ); + } + + if( source.isDirectory() ) + { + throw new BuildException( "Cannot expand a directory", location ); + } + + if( dest == null ) + { + dest = new File( source.getParent() ); + } + + if( dest.isDirectory() ) + { + String defaultExtension = getDefaultExtension(); + createDestFile( defaultExtension ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Untar.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Untar.java new file mode 100644 index 000000000..99fba8759 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Untar.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Date; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.tar.TarEntry; +import org.apache.tools.tar.TarInputStream; + +/** + * Untar a file. Heavily based on the Expand task. + * + * @author Stefan Bodewig + * @author Magesh Umasankar + */ +public class Untar extends Expand +{ + + protected void expandFile( FileUtils fileUtils, File srcF, File dir ) + { + TarInputStream tis = null; + try + { + log( "Expanding: " + srcF + " into " + dir, Project.MSG_INFO ); + + tis = new TarInputStream( new FileInputStream( srcF ) ); + TarEntry te = null; + + while( ( te = tis.getNextEntry() ) != null ) + { + extractFile( fileUtils, srcF, dir, tis, + te.getName(), + te.getModTime(), te.isDirectory() ); + } + log( "expand complete", Project.MSG_VERBOSE ); + + } + catch( IOException ioe ) + { + throw new BuildException( "Error while expanding " + srcF.getPath(), + ioe, location ); + } + finally + { + if( tis != null ) + { + try + { + tis.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/UpToDate.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/UpToDate.java new file mode 100644 index 000000000..968131b0a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/UpToDate.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Will set the given property if the specified target has a timestamp greater + * than all of the source files. + * + * @author William Ferguson + * williamf@mincom.com + * @author Hiroaki Nakamura + * hnakamur@mc.neweb.ne.jp + * @author Stefan Bodewig + */ + +public class UpToDate extends MatchingTask implements Condition +{ + private Vector sourceFileSets = new Vector(); + + protected Mapper mapperElement = null; + + private String _property; + private File _targetFile; + private String _value; + + /** + * The property to set if the target file is more up to date than each of + * the source files. + * + * @param property the name of the property to set if Target is up to date. + */ + public void setProperty( String property ) + { + _property = property; + } + + /** + * The file which must be more up to date than each of the source files if + * the property is to be set. + * + * @param file the file which we are checking against. + */ + public void setTargetFile( File file ) + { + _targetFile = file; + } + + /** + * The value to set the named property to if the target file is more up to + * date than each of the source files. Defaults to 'true'. + * + * @param value the value to set the property to if Target is up to date + */ + public void setValue( String value ) + { + _value = value; + } + + /** + * Nested <srcfiles> element. + * + * @param fs The feature to be added to the Srcfiles attribute + */ + public void addSrcfiles( FileSet fs ) + { + sourceFileSets.addElement( fs ); + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapperElement != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapperElement = new Mapper( project ); + return mapperElement; + } + + /** + * Evaluate all target and source files, see if the targets are up-to-date. + * + * @return Description of the Returned Value + */ + public boolean eval() + { + if( sourceFileSets.size() == 0 ) + { + throw new BuildException( "At least one element must be set" ); + } + + if( _targetFile == null && mapperElement == null ) + { + throw new BuildException( "The targetfile attribute or a nested mapper element must be set" ); + } + + // if not there then it can't be up to date + if( _targetFile != null && !_targetFile.exists() ) + return false; + + Enumeration enum = sourceFileSets.elements(); + boolean upToDate = true; + while( upToDate && enum.hasMoreElements() ) + { + FileSet fs = ( FileSet )enum.nextElement(); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + upToDate = upToDate && scanDir( fs.getDir( project ), + ds.getIncludedFiles() ); + } + return upToDate; + } + + + /** + * Sets property to true if target files have a more recent timestamp than + * each of the corresponding source files. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + boolean upToDate = eval(); + if( upToDate ) + { + this.project.setProperty( _property, this.getValue() ); + if( mapperElement == null ) + { + log( "File \"" + _targetFile.getAbsolutePath() + "\" is up to date.", + Project.MSG_VERBOSE ); + } + else + { + log( "All target files have been up to date.", + Project.MSG_VERBOSE ); + } + } + } + + protected boolean scanDir( File srcDir, String files[] ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + FileNameMapper mapper = null; + File dir = srcDir; + if( mapperElement == null ) + { + MergingMapper mm = new MergingMapper(); + mm.setTo( _targetFile.getAbsolutePath() ); + mapper = mm; + dir = null; + } + else + { + mapper = mapperElement.getImplementation(); + } + return sfs.restrict( files, srcDir, dir, mapper ).length == 0; + } + + /** + * Returns the value, or "true" if a specific value wasn't provided. + * + * @return The Value value + */ + private String getValue() + { + return ( _value != null ) ? _value : "true"; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/WaitFor.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/WaitFor.java new file mode 100644 index 000000000..9f87abb1d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/WaitFor.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.taskdefs.condition.ConditionBase; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Wait for an external event to occur. Wait for an external process to start or + * to complete some task. This is useful with the parallel task to + * syncronize the execution of tests with server startup. The following + * attributes can be specified on a waitfor task: + *
        + *
      • maxwait - maximum length of time to wait before giving up
      • + *
      • maxwaitunit - The unit to be used to interpret maxwait attribute
      • + * + *
      • checkevery - amount of time to sleep between each check
      • + *
      • checkeveryunit - The unit to be used to interpret checkevery attribute + *
      • + *
      • timeoutproperty - name of a property to set if maxwait has been + * exceeded.
      • + *
      + * The maxwaitunit and checkeveryunit are allowed to have the following values: + * millesond, second, minute, hour, day and week. The default is millisecond. + * + * @author Denis Hennessy + * @author Magesh Umasankar + */ + +public class WaitFor extends ConditionBase +{ + private long maxWaitMillis = 1000l * 60l * 3l;// default max wait time + private long maxWaitMultiplier = 1l; + private long checkEveryMillis = 500l; + private long checkEveryMultiplier = 1l; + private String timeoutProperty; + + /** + * Set the time between each check + * + * @param time The new CheckEvery value + */ + public void setCheckEvery( long time ) + { + checkEveryMillis = time; + } + + /** + * Set the check every time unit + * + * @param unit The new CheckEveryUnit value + */ + public void setCheckEveryUnit( Unit unit ) + { + checkEveryMultiplier = unit.getMultiplier(); + } + + /** + * Set the maximum length of time to wait + * + * @param time The new MaxWait value + */ + public void setMaxWait( long time ) + { + maxWaitMillis = time; + } + + /** + * Set the max wait time unit + * + * @param unit The new MaxWaitUnit value + */ + public void setMaxWaitUnit( Unit unit ) + { + maxWaitMultiplier = unit.getMultiplier(); + } + + /** + * Set the timeout property. + * + * @param p The new TimeoutProperty value + */ + public void setTimeoutProperty( String p ) + { + timeoutProperty = p; + } + + /** + * Check repeatedly for the specified conditions until they become true or + * the timeout expires. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( countConditions() > 1 ) + { + throw new BuildException( "You must not nest more than one condition into " ); + } + if( countConditions() < 1 ) + { + throw new BuildException( "You must nest a condition into " ); + } + Condition c = ( Condition )getConditions().nextElement(); + + maxWaitMillis *= maxWaitMultiplier; + checkEveryMillis *= checkEveryMultiplier; + long start = System.currentTimeMillis(); + long end = start + maxWaitMillis; + + while( System.currentTimeMillis() < end ) + { + if( c.eval() ) + { + return; + } + try + { + Thread.sleep( checkEveryMillis ); + } + catch( InterruptedException e ) + { + } + } + + if( timeoutProperty != null ) + { + project.setNewProperty( timeoutProperty, "true" ); + } + } + + public static class Unit extends EnumeratedAttribute + { + + private final static String MILLISECOND = "millisecond"; + private final static String SECOND = "second"; + private final static String MINUTE = "minute"; + private final static String HOUR = "hour"; + private final static String DAY = "day"; + private final static String WEEK = "week"; + + private final static String[] units = { + MILLISECOND, SECOND, MINUTE, HOUR, DAY, WEEK + }; + + private Hashtable timeTable = new Hashtable(); + + public Unit() + { + timeTable.put( MILLISECOND, new Long( 1l ) ); + timeTable.put( SECOND, new Long( 1000l ) ); + timeTable.put( MINUTE, new Long( 1000l * 60l ) ); + timeTable.put( HOUR, new Long( 1000l * 60l * 60l ) ); + timeTable.put( DAY, new Long( 1000l * 60l * 60l * 24l ) ); + timeTable.put( WEEK, new Long( 1000l * 60l * 60l * 24l * 7l ) ); + } + + public long getMultiplier() + { + String key = getValue().toLowerCase(); + Long l = ( Long )timeTable.get( key ); + return l.longValue(); + } + + public String[] getValues() + { + return units; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/War.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/War.java new file mode 100644 index 000000000..ff2bf9c48 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/War.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.zip.ZipOutputStream; + + +/** + * Creates a WAR archive. + * + * @author Stefan Bodewig + */ +public class War extends Jar +{ + + private File deploymentDescriptor; + private boolean descriptorAdded; + + public War() + { + super(); + archiveType = "war"; + emptyBehavior = "create"; + } + + public void setWarfile( File warFile ) + { + log( "DEPRECATED - The warfile attribute is deprecated. Use file attribute instead." ); + setFile( warFile ); + } + + public void setWebxml( File descr ) + { + deploymentDescriptor = descr; + if( !deploymentDescriptor.exists() ) + throw new BuildException( "Deployment descriptor: " + deploymentDescriptor + " does not exist." ); + + // Create a ZipFileSet for this file, and pass it up. + ZipFileSet fs = new ZipFileSet(); + fs.setDir( new File( deploymentDescriptor.getParent() ) ); + fs.setIncludes( deploymentDescriptor.getName() ); + fs.setFullpath( "WEB-INF/web.xml" ); + super.addFileset( fs ); + } + + public void addClasses( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "WEB-INF/classes/" ); + super.addFileset( fs ); + } + + public void addLib( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "WEB-INF/lib/" ); + super.addFileset( fs ); + } + + public void addWebinf( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "WEB-INF/" ); + super.addFileset( fs ); + } + + /** + * Make sure we don't think we already have a web.xml next time this task + * gets executed. + */ + protected void cleanUp() + { + descriptorAdded = false; + super.cleanUp(); + } + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + // If no webxml file is specified, it's an error. + if( deploymentDescriptor == null && !isInUpdateMode() ) + { + throw new BuildException( "webxml attribute is required", location ); + } + + super.initZipOutputStream( zOut ); + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + // If the file being added is WEB-INF/web.xml, we warn if it's not the + // one specified in the "webxml" attribute - or if it's being added twice, + // meaning the same file is specified by the "webxml" attribute and in + // a element. + if( vPath.equalsIgnoreCase( "WEB-INF/web.xml" ) ) + { + if( deploymentDescriptor == null || !deploymentDescriptor.equals( file ) || descriptorAdded ) + { + log( "Warning: selected " + archiveType + " files include a WEB-INF/web.xml which will be ignored " + + "(please use webxml attribute to " + archiveType + " task)", Project.MSG_WARN ); + } + else + { + super.zipFile( file, zOut, vPath ); + descriptorAdded = true; + } + } + else + { + super.zipFile( file, zOut, vPath ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLiaison.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLiaison.java new file mode 100644 index 000000000..a4d01bd8e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLiaison.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; + +/** + * Proxy interface for XSLT processors. + * + * @author Sam Ruby + * @author Stephane Bailliez + * @see XSLTProcess + */ +public interface XSLTLiaison +{ + + /** + * the file protocol prefix for systemid. This file protocol must be + * appended to an absolute path. Typically: FILE_PROTOCOL_PREFIX + + * file.getAbsolutePath() This is not correct in specification terms + * since an absolute url in Unix is file:// + file.getAbsolutePath() while + * it is file:/// + file.getAbsolutePath() under Windows. Whatever, it + * should not be a problem to put file:/// in every case since most parsers + * for now incorrectly makes no difference between it.. and users also have + * problem with that :) + */ + String FILE_PROTOCOL_PREFIX = "file:///"; + + /** + * set the stylesheet to use for the transformation. + * + * @param stylesheet the stylesheet to be used for transformation. + * @exception Exception Description of Exception + */ + void setStylesheet( File stylesheet ) + throws Exception; + + /** + * Add a parameter to be set during the XSL transformation. + * + * @param name the parameter name. + * @param expression the parameter value as an expression string. + * @throws Exception thrown if any problems happens. + */ + void addParam( String name, String expression ) + throws Exception; + + /** + * set the output type to use for the transformation. Only "xml" (the + * default) is guaranteed to work for all parsers. Xalan2 also supports + * "html" and "text". + * + * @param type the output method to use + * @exception Exception Description of Exception + */ + void setOutputtype( String type ) + throws Exception; + + /** + * Perform the transformation of a file into another. + * + * @param infile the input file, probably an XML one. :-) + * @param outfile the output file resulting from the transformation + * @see #setStylesheet(File) + * @throws Exception thrown if any problems happens. + */ + void transform( File infile, File outfile ) + throws Exception; + +}//-- XSLTLiaison diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLogger.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLogger.java new file mode 100644 index 000000000..4579d4085 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLogger.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +public interface XSLTLogger +{ + /** + * Log a message. + * + * @param msg Description of Parameter + */ + void log( String msg ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java new file mode 100644 index 000000000..ae2051ae0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java @@ -0,0 +1,13 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +public interface XSLTLoggerAware +{ + void setLogger( XSLTLogger l ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java new file mode 100644 index 000000000..13a17d2b2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/XSLTProcess.java @@ -0,0 +1,581 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileUtils; + + +/** + * A Task to process via XSLT a set of XML documents. This is useful for + * building views of XML based documentation. arguments: + *
        + *
      • basedir + *
      • destdir + *
      • style + *
      • includes + *
      • excludes + *
      + * Of these arguments, the sourcedir and destdir are required.

      + * + * This task will recursively scan the sourcedir and destdir looking for XML + * documents to process via XSLT. Any other files, such as images, or html files + * in the source directory will be copied into the destination directory. + * + * @author Keith Visco + * @author Sam Ruby + * @author Russell Gold + * @author Stefan Bodewig + */ + +public class XSLTProcess extends MatchingTask implements XSLTLogger +{ + + private File destDir = null; + + private File baseDir = null; + + private String xslFile = null; + + private String targetExtension = ".html"; + private Vector params = new Vector(); + + private File inFile = null; + + private File outFile = null; + private Path classpath = null; + private boolean stylesheetLoaded = false; + + private boolean force = false; + + private String outputtype = null; + + private FileUtils fileUtils; + private XSLTLiaison liaison; + + private String processor; + + /** + * Creates a new XSLTProcess Task. + */ + public XSLTProcess() + { + fileUtils = FileUtils.newFileUtils(); + }//-- setForce + + /** + * Set the base directory. + * + * @param dir The new Basedir value + */ + public void setBasedir( File dir ) + { + baseDir = dir; + } + + /** + * Set the classpath to load the Processor through (attribute). + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + createClasspath().append( classpath ); + } + + /** + * Set the classpath to load the Processor through via reference + * (attribute). + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + }//-- setSourceDir + + /** + * Set the destination directory into which the XSL result files should be + * copied to + * + * @param dir The new Destdir value + */ + public void setDestdir( File dir ) + { + destDir = dir; + }//-- setDestDir + + /** + * Set the desired file extension to be used for the target + * + * @param name the extension to use + */ + public void setExtension( String name ) + { + targetExtension = name; + }//-- execute + + /** + * Set whether to check dependencies, or always generate. + * + * @param force The new Force value + */ + public void setForce( boolean force ) + { + this.force = force; + } + + /** + * Sets an input xml file to be styled + * + * @param inFile The new In value + */ + public void setIn( File inFile ) + { + this.inFile = inFile; + } + + /** + * Sets an out file + * + * @param outFile The new Out value + */ + public void setOut( File outFile ) + { + this.outFile = outFile; + } + + /** + * Set the output type to use for the transformation. Only "xml" (the + * default) is guaranteed to work for all parsers. Xalan2 also supports + * "html" and "text". + * + * @param type the output method to use + */ + public void setOutputtype( String type ) + { + this.outputtype = type; + } + + + public void setProcessor( String processor ) + { + this.processor = processor; + }//-- setDestDir + + /** + * Sets the file to use for styling relative to the base directory of this + * task. + * + * @param xslFile The new Style value + */ + public void setStyle( String xslFile ) + { + this.xslFile = xslFile; + } + + /** + * Set the classpath to load the Processor through (nested element). + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + public Param createParam() + { + Param p = new Param(); + params.addElement( p ); + return p; + }//-- XSLTProcess + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + + public void execute() + throws BuildException + { + DirectoryScanner scanner; + String[] list; + String[] dirs; + + if( xslFile == null ) + { + throw new BuildException( "no stylesheet specified", location ); + } + + if( baseDir == null ) + { + baseDir = project.resolveFile( "." ); + } + + liaison = getLiaison(); + + // check if liaison wants to log errors using us as logger + if( liaison instanceof XSLTLoggerAware ) + { + ( ( XSLTLoggerAware )liaison ).setLogger( this ); + } + + log( "Using " + liaison.getClass().toString(), Project.MSG_VERBOSE ); + + File stylesheet = project.resolveFile( xslFile ); + if( !stylesheet.exists() ) + { + stylesheet = fileUtils.resolveFile( baseDir, xslFile ); + /* + * shouldn't throw out deprecation warnings before we know, + * the wrong version has been used. + */ + if( stylesheet.exists() ) + { + log( "DEPRECATED - the style attribute should be relative to the project\'s" ); + log( " basedir, not the tasks\'s basedir." ); + } + } + + // if we have an in file and out then process them + if( inFile != null && outFile != null ) + { + process( inFile, outFile, stylesheet ); + return; + } + + /* + * if we get here, in and out have not been specified, we are + * in batch processing mode. + */ + //-- make sure Source directory exists... + if( destDir == null ) + { + String msg = "destdir attributes must be set!"; + throw new BuildException( msg ); + } + scanner = getDirectoryScanner( baseDir ); + log( "Transforming into " + destDir, Project.MSG_INFO ); + + // Process all the files marked for styling + list = scanner.getIncludedFiles(); + for( int i = 0; i < list.length; ++i ) + { + process( baseDir, list[i], destDir, stylesheet ); + } + + // Process all the directoried marked for styling + dirs = scanner.getIncludedDirectories(); + for( int j = 0; j < dirs.length; ++j ) + { + list = new File( baseDir, dirs[j] ).list(); + for( int i = 0; i < list.length; ++i ) + process( baseDir, list[i], destDir, stylesheet ); + } + } + + protected XSLTLiaison getLiaison() + { + // if processor wasn't specified, see if TraX is available. If not, + // default it to xslp or xalan, depending on which is in the classpath + if( liaison == null ) + { + if( processor != null ) + { + try + { + resolveProcessor( processor ); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + else + { + try + { + resolveProcessor( "trax" ); + } + catch( Throwable e1 ) + { + try + { + resolveProcessor( "xalan" ); + } + catch( Throwable e2 ) + { + try + { + resolveProcessor( "adaptx" ); + } + catch( Throwable e3 ) + { + try + { + resolveProcessor( "xslp" ); + } + catch( Throwable e4 ) + { + e4.printStackTrace(); + e3.printStackTrace(); + e2.printStackTrace(); + throw new BuildException( e1 ); + } + } + } + } + } + } + return liaison; + } + + /** + * Loads the stylesheet and set xsl:param parameters. + * + * @param stylesheet Description of Parameter + * @exception BuildException Description of Exception + */ + protected void configureLiaison( File stylesheet ) + throws BuildException + { + if( stylesheetLoaded ) + { + return; + } + stylesheetLoaded = true; + + try + { + log( "Loading stylesheet " + stylesheet, Project.MSG_INFO ); + liaison.setStylesheet( stylesheet ); + for( Enumeration e = params.elements(); e.hasMoreElements(); ) + { + Param p = ( Param )e.nextElement(); + liaison.addParam( p.getName(), p.getExpression() ); + } + } + catch( Exception ex ) + { + log( "Failed to read stylesheet " + stylesheet, Project.MSG_INFO ); + throw new BuildException( ex ); + } + } + + private void ensureDirectoryFor( File targetFile ) + throws BuildException + { + File directory = new File( targetFile.getParent() ); + if( !directory.exists() ) + { + if( !directory.mkdirs() ) + { + throw new BuildException( "Unable to create directory: " + + directory.getAbsolutePath() ); + } + } + } + + /** + * Load named class either via the system classloader or a given custom + * classloader. + * + * @param classname Description of Parameter + * @return Description of the Returned Value + * @exception Exception Description of Exception + */ + private Class loadClass( String classname ) + throws Exception + { + if( classpath == null ) + { + return Class.forName( classname ); + } + else + { + AntClassLoader al = new AntClassLoader( project, classpath ); + Class c = al.loadClass( classname ); + AntClassLoader.initializeClass( c ); + return c; + } + } + + /** + * Processes the given input XML file and stores the result in the given + * resultFile. + * + * @param baseDir Description of Parameter + * @param xmlFile Description of Parameter + * @param destDir Description of Parameter + * @param stylesheet Description of Parameter + * @exception BuildException Description of Exception + */ + private void process( File baseDir, String xmlFile, File destDir, + File stylesheet ) + throws BuildException + { + + String fileExt = targetExtension; + File outFile = null; + File inFile = null; + + try + { + long styleSheetLastModified = stylesheet.lastModified(); + inFile = new File( baseDir, xmlFile ); + int dotPos = xmlFile.lastIndexOf( '.' ); + if( dotPos > 0 ) + { + outFile = new File( destDir, xmlFile.substring( 0, xmlFile.lastIndexOf( '.' ) ) + fileExt ); + } + else + { + outFile = new File( destDir, xmlFile + fileExt ); + } + if( force || + inFile.lastModified() > outFile.lastModified() || + styleSheetLastModified > outFile.lastModified() ) + { + ensureDirectoryFor( outFile ); + log( "Processing " + inFile + " to " + outFile ); + + configureLiaison( stylesheet ); + liaison.transform( inFile, outFile ); + } + } + catch( Exception ex ) + { + // If failed to process document, must delete target document, + // or it will not attempt to process it the second time + log( "Failed to process " + inFile, Project.MSG_INFO ); + if( outFile != null ) + { + outFile.delete(); + } + + throw new BuildException( ex ); + } + + }//-- processXML + + private void process( File inFile, File outFile, File stylesheet ) + throws BuildException + { + try + { + long styleSheetLastModified = stylesheet.lastModified(); + log( "In file " + inFile + " time: " + inFile.lastModified(), Project.MSG_DEBUG ); + log( "Out file " + outFile + " time: " + outFile.lastModified(), Project.MSG_DEBUG ); + log( "Style file " + xslFile + " time: " + styleSheetLastModified, Project.MSG_DEBUG ); + if( force || + inFile.lastModified() > outFile.lastModified() || + styleSheetLastModified > outFile.lastModified() ) + { + ensureDirectoryFor( outFile ); + log( "Processing " + inFile + " to " + outFile, Project.MSG_INFO ); + configureLiaison( stylesheet ); + liaison.transform( inFile, outFile ); + } + } + catch( Exception ex ) + { + log( "Failed to process " + inFile, Project.MSG_INFO ); + if( outFile != null ) + outFile.delete(); + throw new BuildException( ex ); + } + } + + /** + * Load processor here instead of in setProcessor - this will be called from + * within execute, so we have access to the latest classpath. + * + * @param proc Description of Parameter + * @exception Exception Description of Exception + */ + private void resolveProcessor( String proc ) + throws Exception + { + if( proc.equals( "trax" ) ) + { + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.TraXLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else if( proc.equals( "xslp" ) ) + { + log( "DEPRECATED - xslp processor is deprecated. Use trax or xalan instead." ); + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.XslpLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else if( proc.equals( "xalan" ) ) + { + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.XalanLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else if( proc.equals( "adaptx" ) ) + { + log( "DEPRECATED - adaptx processor is deprecated. Use trax or xalan instead." ); + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.AdaptxLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else + { + liaison = ( XSLTLiaison )loadClass( proc ).newInstance(); + } + } + + public class Param + { + private String name = null; + private String expression = null; + + public void setExpression( String expression ) + { + this.expression = expression; + } + + public void setName( String name ) + { + this.name = name; + } + + public String getExpression() + throws BuildException + { + if( expression == null ) + throw new BuildException( "Expression attribute is missing." ); + return expression; + } + + public String getName() + throws BuildException + { + if( name == null ) + throw new BuildException( "Name attribute is missing." ); + return name; + } + } + +}//-- XSLTProcess diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Zip.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Zip.java new file mode 100644 index 000000000..802ec0476 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/Zip.java @@ -0,0 +1,888 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Hashtable; +import java.util.Stack; +import java.util.Vector; +import java.util.zip.CRC32; +import java.util.zip.ZipInputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.FileScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.ant.types.ZipScanner; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.SourceFileScanner; +import org.apache.tools.zip.ZipEntry; +import org.apache.tools.zip.ZipOutputStream; + +/** + * Create a ZIP archive. + * + * @author James Davidson duncan@x180.com + * @author Jon S. Stevens jon@clearink.com + * @author Stefan Bodewig + */ +public class Zip extends MatchingTask +{ + + // For directories: + private final static long EMPTY_CRC = new CRC32().getValue(); + private boolean doCompress = true; + private boolean doUpdate = false; + private boolean doFilesonly = false; + protected String archiveType = "zip"; + protected String emptyBehavior = "skip"; + private Vector filesets = new Vector(); + protected Hashtable addedDirs = new Hashtable(); + private Vector addedFiles = new Vector(); + + protected File zipFile; + + /** + * true when we are adding new files into the Zip file, as opposed to adding + * back the unchanged files + */ + private boolean addingNewFiles; + private File baseDir; + + /** + * Encoding to use for filenames, defaults to the platform's default + * encoding. + */ + private String encoding; + + protected static String[][] grabFileNames( FileScanner[] scanners ) + { + String[][] result = new String[scanners.length][]; + for( int i = 0; i < scanners.length; i++ ) + { + String[] files = scanners[i].getIncludedFiles(); + String[] dirs = scanners[i].getIncludedDirectories(); + result[i] = new String[files.length + dirs.length]; + System.arraycopy( files, 0, result[i], 0, files.length ); + System.arraycopy( dirs, 0, result[i], files.length, dirs.length ); + } + return result; + } + + protected static File[] grabFiles( FileScanner[] scanners ) + { + return grabFiles( scanners, grabFileNames( scanners ) ); + } + + protected static File[] grabFiles( FileScanner[] scanners, + String[][] fileNames ) + { + Vector files = new Vector(); + for( int i = 0; i < fileNames.length; i++ ) + { + File thisBaseDir = scanners[i].getBasedir(); + for( int j = 0; j < fileNames[i].length; j++ ) + files.addElement( new File( thisBaseDir, fileNames[i][j] ) ); + } + File[] toret = new File[files.size()]; + files.copyInto( toret ); + return toret; + } + + /** + * This is the base directory to look in for things to zip. + * + * @param baseDir The new Basedir value + */ + public void setBasedir( File baseDir ) + { + this.baseDir = baseDir; + } + + /** + * Sets whether we want to compress the files or only store them. + * + * @param c The new Compress value + */ + public void setCompress( boolean c ) + { + doCompress = c; + } + + /** + * Encoding to use for filenames, defaults to the platform's default + * encoding.

      + * + * For a list of possible values see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * .

      + * + * @param encoding The new Encoding value + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * This is the name/location of where to create the .zip file. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.zipFile = file; + } + + /** + * Emulate Sun's jar utility by not adding parent dirs + * + * @param f The new Filesonly value + */ + public void setFilesonly( boolean f ) + { + doFilesonly = f; + } + + /** + * Sets whether we want to update the file (if it exists) or create a new + * one. + * + * @param c The new Update value + */ + public void setUpdate( boolean c ) + { + doUpdate = c; + } + + /** + * Sets behavior of the task when no files match. Possible values are: + * fail (throw an exception and halt the build); skip + * (do not create any archive, but issue a warning); create + * (make an archive with no entries). Default for zip tasks is skip + * ; for jar tasks, create. + * + * @param we The new Whenempty value + */ + public void setWhenempty( WhenEmpty we ) + { + emptyBehavior = we.getValue(); + } + + /** + * This is the name/location of where to create the .zip file. + * + * @param zipFile The new Zipfile value + * @deprecated Use setFile() instead + */ + public void setZipfile( File zipFile ) + { + log( "DEPRECATED - The zipfile attribute is deprecated. Use file attribute instead." ); + setFile( zipFile ); + } + + /** + * Are we updating an existing archive? + * + * @return The InUpdateMode value + */ + public boolean isInUpdateMode() + { + return doUpdate; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Adds a set of files (nested zipfileset attribute) that can be read from + * an archive and be given a prefix/fullpath. + * + * @param set The feature to be added to the Zipfileset attribute + */ + public void addZipfileset( ZipFileSet set ) + { + filesets.addElement( set ); + } + + public void execute() + throws BuildException + { + if( baseDir == null && filesets.size() == 0 && "zip".equals( archiveType ) ) + { + throw new BuildException( "basedir attribute must be set, or at least " + + "one fileset must be given!" ); + } + + if( zipFile == null ) + { + throw new BuildException( "You must specify the " + archiveType + " file to create!" ); + } + + // Renamed version of original file, if it exists + File renamedFile = null; + // Whether or not an actual update is required - + // we don't need to update if the original file doesn't exist + + addingNewFiles = true; + doUpdate = doUpdate && zipFile.exists(); + if( doUpdate ) + { + FileUtils fileUtils = FileUtils.newFileUtils(); + renamedFile = fileUtils.createTempFile( "zip", ".tmp", + fileUtils.getParentFile( zipFile ) ); + + try + { + if( !zipFile.renameTo( renamedFile ) ) + { + throw new BuildException( "Unable to rename old file to temporary file" ); + } + } + catch( SecurityException e ) + { + throw new BuildException( "Not allowed to rename old file to temporary file" ); + } + } + + // Create the scanners to pass to isUpToDate(). + Vector dss = new Vector(); + if( baseDir != null ) + { + dss.addElement( getDirectoryScanner( baseDir ) ); + } + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + dss.addElement( fs.getDirectoryScanner( project ) ); + } + int dssSize = dss.size(); + FileScanner[] scanners = new FileScanner[dssSize]; + dss.copyInto( scanners ); + + // quick exit if the target is up to date + // can also handle empty archives + if( isUpToDate( scanners, zipFile ) ) + { + return; + } + + String action = doUpdate ? "Updating " : "Building "; + + log( action + archiveType + ": " + zipFile.getAbsolutePath() ); + + boolean success = false; + try + { + ZipOutputStream zOut = + new ZipOutputStream( new FileOutputStream( zipFile ) ); + zOut.setEncoding( encoding ); + try + { + if( doCompress ) + { + zOut.setMethod( ZipOutputStream.DEFLATED ); + } + else + { + zOut.setMethod( ZipOutputStream.STORED ); + } + initZipOutputStream( zOut ); + + // Add the implicit fileset to the archive. + if( baseDir != null ) + { + addFiles( getDirectoryScanner( baseDir ), zOut, "", "" ); + } + // Add the explicit filesets to the archive. + addFiles( filesets, zOut ); + if( doUpdate ) + { + addingNewFiles = false; + ZipFileSet oldFiles = new ZipFileSet(); + oldFiles.setSrc( renamedFile ); + + StringBuffer exclusionPattern = new StringBuffer(); + for( int i = 0; i < addedFiles.size(); i++ ) + { + if( i != 0 ) + { + exclusionPattern.append( "," ); + } + exclusionPattern.append( ( String )addedFiles.elementAt( i ) ); + } + oldFiles.setExcludes( exclusionPattern.toString() ); + Vector tmp = new Vector(); + tmp.addElement( oldFiles ); + addFiles( tmp, zOut ); + } + finalizeZipOutputStream( zOut ); + success = true; + } + finally + { + // Close the output stream. + try + { + if( zOut != null ) + { + zOut.close(); + } + } + catch( IOException ex ) + { + // If we're in this finally clause because of an exception, we don't + // really care if there's an exception when closing the stream. E.g. if it + // throws "ZIP file must have at least one entry", because an exception happened + // before we added any files, then we must swallow this exception. Otherwise, + // the error that's reported will be the close() error, which is not the real + // cause of the problem. + if( success ) + throw ex; + } + } + } + catch( IOException ioe ) + { + String msg = "Problem creating " + archiveType + ": " + ioe.getMessage(); + + // delete a bogus ZIP file + if( !zipFile.delete() ) + { + msg += " (and the archive is probably corrupt but I could not delete it)"; + } + + if( doUpdate ) + { + if( !renamedFile.renameTo( zipFile ) ) + { + msg += " (and I couldn't rename the temporary file " + + renamedFile.getName() + " back)"; + } + } + + throw new BuildException( msg, ioe, location ); + } + finally + { + cleanUp(); + } + + // If we've been successful on an update, delete the temporary file + if( success && doUpdate ) + { + if( !renamedFile.delete() ) + { + log( "Warning: unable to delete temporary file " + + renamedFile.getName(), Project.MSG_WARN ); + } + } + } + + /** + * Indicates if the task is adding new files into the archive as opposed to + * copying back unchanged files from the backup copy + * + * @return The AddingNewFiles value + */ + protected boolean isAddingNewFiles() + { + return addingNewFiles; + } + + + /** + * Check whether the archive is up-to-date; and handle behavior for empty + * archives. + * + * @param scanners list of prepared scanners containing files to archive + * @param zipFile intended archive file (may or may not exist) + * @return true if nothing need be done (may have done something already); + * false if archive creation should proceed + * @exception BuildException if it likes + */ + protected boolean isUpToDate( FileScanner[] scanners, File zipFile ) + throws BuildException + { + String[][] fileNames = grabFileNames( scanners ); + File[] files = grabFiles( scanners, fileNames ); + if( files.length == 0 ) + { + if( emptyBehavior.equals( "skip" ) ) + { + log( "Warning: skipping " + archiveType + " archive " + zipFile + + " because no files were included.", Project.MSG_WARN ); + return true; + } + else if( emptyBehavior.equals( "fail" ) ) + { + throw new BuildException( "Cannot create " + archiveType + " archive " + zipFile + + ": no files were included.", location ); + } + else + { + // Create. + return createEmptyZip( zipFile ); + } + } + else + { + for( int i = 0; i < files.length; ++i ) + { + if( files[i].equals( zipFile ) ) + { + throw new BuildException( "A zip file cannot include itself", location ); + } + } + + if( !zipFile.exists() ) + return false; + + SourceFileScanner sfs = new SourceFileScanner( this ); + MergingMapper mm = new MergingMapper(); + mm.setTo( zipFile.getAbsolutePath() ); + for( int i = 0; i < scanners.length; i++ ) + { + if( sfs.restrict( fileNames[i], scanners[i].getBasedir(), null, + mm ).length > 0 ) + { + return false; + } + } + return true; + } + } + + /** + * Add all files of the given FileScanner to the ZipOutputStream prependig + * the given prefix to each filename.

      + * + * Ensure parent directories have been added as well. + * + * @param scanner The feature to be added to the Files attribute + * @param zOut The feature to be added to the Files attribute + * @param prefix The feature to be added to the Files attribute + * @param fullpath The feature to be added to the Files attribute + * @exception IOException Description of Exception + */ + protected void addFiles( FileScanner scanner, ZipOutputStream zOut, + String prefix, String fullpath ) + throws IOException + { + if( prefix.length() > 0 && fullpath.length() > 0 ) + throw new BuildException( "Both prefix and fullpath attributes may not be set on the same fileset." ); + + File thisBaseDir = scanner.getBasedir(); + + // directories that matched include patterns + String[] dirs = scanner.getIncludedDirectories(); + if( dirs.length > 0 && fullpath.length() > 0 ) + throw new BuildException( "fullpath attribute may only be specified for filesets that specify a single file." ); + for( int i = 0; i < dirs.length; i++ ) + { + if( "".equals( dirs[i] ) ) + { + continue; + } + String name = dirs[i].replace( File.separatorChar, '/' ); + if( !name.endsWith( "/" ) ) + { + name += "/"; + } + addParentDirs( thisBaseDir, name, zOut, prefix ); + } + + // files that matched include patterns + String[] files = scanner.getIncludedFiles(); + if( files.length > 1 && fullpath.length() > 0 ) + throw new BuildException( "fullpath attribute may only be specified for filesets that specify a single file." ); + for( int i = 0; i < files.length; i++ ) + { + File f = new File( thisBaseDir, files[i] ); + if( fullpath.length() > 0 ) + { + // Add this file at the specified location. + addParentDirs( null, fullpath, zOut, "" ); + zipFile( f, zOut, fullpath ); + } + else + { + // Add this file with the specified prefix. + String name = files[i].replace( File.separatorChar, '/' ); + addParentDirs( thisBaseDir, name, zOut, prefix ); + zipFile( f, zOut, prefix + name ); + } + } + } + + /** + * Iterate over the given Vector of (zip)filesets and add all files to the + * ZipOutputStream using the given prefix or fullpath. + * + * @param filesets The feature to be added to the Files attribute + * @param zOut The feature to be added to the Files attribute + * @exception IOException Description of Exception + */ + protected void addFiles( Vector filesets, ZipOutputStream zOut ) + throws IOException + { + // Add each fileset in the Vector. + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + + String prefix = ""; + String fullpath = ""; + if( fs instanceof ZipFileSet ) + { + ZipFileSet zfs = ( ZipFileSet )fs; + prefix = zfs.getPrefix(); + fullpath = zfs.getFullpath(); + } + + if( prefix.length() > 0 + && !prefix.endsWith( "/" ) + && !prefix.endsWith( "\\" ) ) + { + prefix += "/"; + } + + // Need to manually add either fullpath's parent directory, or + // the prefix directory, to the archive. + if( prefix.length() > 0 ) + { + addParentDirs( null, prefix, zOut, "" ); + zipDir( null, zOut, prefix ); + } + else if( fullpath.length() > 0 ) + { + addParentDirs( null, fullpath, zOut, "" ); + } + + if( fs instanceof ZipFileSet + && ( ( ZipFileSet )fs ).getSrc() != null ) + { + addZipEntries( ( ZipFileSet )fs, ds, zOut, prefix, fullpath ); + } + else + { + // Add the fileset. + addFiles( ds, zOut, prefix, fullpath ); + } + } + } + + /** + * Ensure all parent dirs of a given entry have been added. + * + * @param baseDir The feature to be added to the ParentDirs attribute + * @param entry The feature to be added to the ParentDirs attribute + * @param zOut The feature to be added to the ParentDirs attribute + * @param prefix The feature to be added to the ParentDirs attribute + * @exception IOException Description of Exception + */ + protected void addParentDirs( File baseDir, String entry, + ZipOutputStream zOut, String prefix ) + throws IOException + { + if( !doFilesonly ) + { + Stack directories = new Stack(); + int slashPos = entry.length(); + + while( ( slashPos = entry.lastIndexOf( ( int )'/', slashPos - 1 ) ) != -1 ) + { + String dir = entry.substring( 0, slashPos + 1 ); + if( addedDirs.get( prefix + dir ) != null ) + { + break; + } + directories.push( dir ); + } + + while( !directories.isEmpty() ) + { + String dir = ( String )directories.pop(); + File f = null; + if( baseDir != null ) + { + f = new File( baseDir, dir ); + } + else + { + f = new File( dir ); + } + zipDir( f, zOut, prefix + dir ); + } + } + } + + protected void addZipEntries( ZipFileSet fs, DirectoryScanner ds, + ZipOutputStream zOut, String prefix, String fullpath ) + throws IOException + { + if( prefix.length() > 0 && fullpath.length() > 0 ) + throw new BuildException( "Both prefix and fullpath attributes may not be set on the same fileset." ); + + ZipScanner zipScanner = ( ZipScanner )ds; + File zipSrc = fs.getSrc(); + + ZipEntry entry; + java.util.zip.ZipEntry origEntry; + ZipInputStream in = null; + try + { + in = new ZipInputStream( new FileInputStream( zipSrc ) ); + + while( ( origEntry = in.getNextEntry() ) != null ) + { + entry = new ZipEntry( origEntry ); + String vPath = entry.getName(); + if( zipScanner.match( vPath ) ) + { + if( fullpath.length() > 0 ) + { + addParentDirs( null, fullpath, zOut, "" ); + zipFile( in, zOut, fullpath, entry.getTime() ); + } + else + { + addParentDirs( null, vPath, zOut, prefix ); + if( !entry.isDirectory() ) + { + zipFile( in, zOut, prefix + vPath, entry.getTime() ); + } + } + } + } + } + finally + { + if( in != null ) + { + in.close(); + } + } + } + + /** + * Do any clean up necessary to allow this instance to be used again.

      + * + * When we get here, the Zip file has been closed and all we need to do is + * to reset some globals.

      + */ + protected void cleanUp() + { + addedDirs = new Hashtable(); + addedFiles = new Vector(); + filesets = new Vector(); + zipFile = null; + baseDir = null; + doCompress = true; + doUpdate = false; + doFilesonly = false; + addingNewFiles = false; + encoding = null; + } + + /** + * Create an empty zip file + * + * @param zipFile Description of Parameter + * @return true if the file is then considered up to date. + */ + protected boolean createEmptyZip( File zipFile ) + { + // In this case using java.util.zip will not work + // because it does not permit a zero-entry archive. + // Must create it manually. + log( "Note: creating empty " + archiveType + " archive " + zipFile, Project.MSG_INFO ); + try + { + OutputStream os = new FileOutputStream( zipFile ); + try + { + // Cf. PKZIP specification. + byte[] empty = new byte[22]; + empty[0] = 80;// P + empty[1] = 75;// K + empty[2] = 5; + empty[3] = 6; + // remainder zeros + os.write( empty ); + } + finally + { + os.close(); + } + } + catch( IOException ioe ) + { + throw new BuildException( "Could not create empty ZIP archive", ioe, location ); + } + return true; + } + + protected void finalizeZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException { } + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException { } + + protected void zipDir( File dir, ZipOutputStream zOut, String vPath ) + throws IOException + { + if( addedDirs.get( vPath ) != null ) + { + // don't add directories we've already added. + // no warning if we try, it is harmless in and of itself + return; + } + addedDirs.put( vPath, vPath ); + + ZipEntry ze = new ZipEntry( vPath ); + if( dir != null && dir.exists() ) + { + ze.setTime( dir.lastModified() ); + } + else + { + ze.setTime( System.currentTimeMillis() ); + } + ze.setSize( 0 ); + ze.setMethod( ZipEntry.STORED ); + // This is faintly ridiculous: + ze.setCrc( EMPTY_CRC ); + + // this is 040775 | MS-DOS directory flag in reverse byte order + ze.setExternalAttributes( 0x41FD0010L ); + + zOut.putNextEntry( ze ); + } + + protected void zipFile( InputStream in, ZipOutputStream zOut, String vPath, + long lastModified ) + throws IOException + { + ZipEntry ze = new ZipEntry( vPath ); + ze.setTime( lastModified ); + + /* + * XXX ZipOutputStream.putEntry expects the ZipEntry to know its + * size and the CRC sum before you start writing the data when using + * STORED mode. + * + * This forces us to process the data twice. + * + * I couldn't find any documentation on this, just found out by try + * and error. + */ + if( !doCompress ) + { + long size = 0; + CRC32 cal = new CRC32(); + if( !in.markSupported() ) + { + // Store data into a byte[] + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + size += count; + cal.update( buffer, 0, count ); + bos.write( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + in = new ByteArrayInputStream( bos.toByteArray() ); + + } + else + { + in.mark( Integer.MAX_VALUE ); + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + size += count; + cal.update( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + in.reset(); + } + ze.setSize( size ); + ze.setCrc( cal.getValue() ); + } + + zOut.putNextEntry( ze ); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + if( count != 0 ) + { + zOut.write( buffer, 0, count ); + } + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + addedFiles.addElement( vPath ); + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + if( file.equals( zipFile ) ) + { + throw new BuildException( "A zip file cannot include itself", location ); + } + + FileInputStream fIn = new FileInputStream( file ); + try + { + zipFile( fIn, zOut, vPath, file.lastModified() ); + } + finally + { + fIn.close(); + } + } + + + /** + * Possible behaviors when there are no matching files for the task. + * + * @author RT + */ + public static class WhenEmpty extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"fail", "skip", "create"}; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java new file mode 100644 index 000000000..f82338b01 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Javac; + +/** + * The interface that all compiler adapters must adher to.

      + * + * A compiler adapter is an adapter that interprets the javac's parameters in + * preperation to be passed off to the compier this adapter represents. As all + * the necessary values are stored in the Javac task itself, the only thing all + * adapters need is the javac task, the execute command and a parameterless + * constructor (for reflection).

      + * + * @author Jay Dickon Glanville + * jayglanville@home.com + */ + +public interface CompilerAdapter +{ + + /** + * Sets the compiler attributes, which are stored in the Javac task. + * + * @param attributes The new Javac value + */ + void setJavac( Javac attributes ); + + /** + * Executes the task. + * + * @return has the compilation been successful + * @exception BuildException Description of Exception + */ + boolean execute() + throws BuildException; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java new file mode 100644 index 000000000..5e0e91536 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Creates the necessary compiler adapter, given basic criteria. + * + * @author J D Glanville + */ +public class CompilerAdapterFactory +{ + + /** + * This is a singlton -- can't create instances!! + */ + private CompilerAdapterFactory() { } + + /** + * Based on the parameter passed in, this method creates the necessary + * factory desired. The current mapping for compiler names are as follows: + * + *
        + *
      • jikes = jikes compiler + *
      • classic, javac1.1, javac1.2 = the standard compiler from JDK + * 1.1/1.2 + *
      • modern, javac1.3 = the new compiler of JDK 1.3 + *
      • jvc, microsoft = the command line compiler from Microsoft's SDK + * for Java / Visual J++ + *
      • kjc = the kopi compiler
      • + *
      • gcj = the gcj compiler from gcc
      • + *
      • a fully quallified classname = the name of a compiler + * adapter + *
      + * + * + * @param compilerType either the name of the desired compiler, or the full + * classname of the compiler's adapter. + * @param task a task to log through. + * @return The Compiler value + * @throws BuildException if the compiler type could not be resolved into a + * compiler adapter. + */ + public static CompilerAdapter getCompiler( String compilerType, Task task ) + throws BuildException + { + /* + * If I've done things right, this should be the extent of the + * conditional statements required. + */ + if( compilerType.equalsIgnoreCase( "jikes" ) ) + { + return new Jikes(); + } + if( compilerType.equalsIgnoreCase( "extJavac" ) ) + { + return new JavacExternal(); + } + if( compilerType.equalsIgnoreCase( "classic" ) || + compilerType.equalsIgnoreCase( "javac1.1" ) || + compilerType.equalsIgnoreCase( "javac1.2" ) ) + { + return new Javac12(); + } + if( compilerType.equalsIgnoreCase( "modern" ) || + compilerType.equalsIgnoreCase( "javac1.3" ) || + compilerType.equalsIgnoreCase( "javac1.4" ) ) + { + // does the modern compiler exist? + try + { + Class.forName( "com.sun.tools.javac.Main" ); + } + catch( ClassNotFoundException cnfe ) + { + task.log( "Modern compiler is not available - using " + + "classic compiler", Project.MSG_WARN ); + return new Javac12(); + } + return new Javac13(); + } + if( compilerType.equalsIgnoreCase( "jvc" ) || + compilerType.equalsIgnoreCase( "microsoft" ) ) + { + return new Jvc(); + } + if( compilerType.equalsIgnoreCase( "kjc" ) ) + { + return new Kjc(); + } + if( compilerType.equalsIgnoreCase( "gcj" ) ) + { + return new Gcj(); + } + if( compilerType.equalsIgnoreCase( "sj" ) || + compilerType.equalsIgnoreCase( "symantec" ) ) + { + return new Sj(); + } + return resolveClassName( compilerType ); + } + + /** + * Tries to resolve the given classname into a compiler adapter. Throws a + * fit if it can't. + * + * @param className The fully qualified classname to be created. + * @return Description of the Returned Value + * @throws BuildException This is the fit that is thrown if className isn't + * an instance of CompilerAdapter. + */ + private static CompilerAdapter resolveClassName( String className ) + throws BuildException + { + try + { + Class c = Class.forName( className ); + Object o = c.newInstance(); + return ( CompilerAdapter )o; + } + catch( ClassNotFoundException cnfe ) + { + throw new BuildException( className + " can\'t be found.", cnfe ); + } + catch( ClassCastException cce ) + { + throw new BuildException( className + " isn\'t the classname of " + + "a compiler adapter.", cce ); + } + catch( Throwable t ) + { + // for all other possibilities + throw new BuildException( className + " caused an interesting " + + "exception.", t ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java new file mode 100644 index 000000000..a4675b48d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java @@ -0,0 +1,494 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.Javac; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.util.FileUtils; + +/** + * This is the default implementation for the CompilerAdapter interface. + * Currently, this is a cut-and-paste of the original javac task. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public abstract class DefaultCompilerAdapter implements CompilerAdapter +{ + protected static String lSep = System.getProperty( "line.separator" ); + protected boolean debug = false; + protected boolean optimize = false; + protected boolean deprecation = false; + protected boolean depend = false; + protected boolean verbose = false; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + protected Javac attributes; + protected Path bootclasspath; + protected Path compileClasspath; + + protected File[] compileList; + protected File destDir; + protected String encoding; + protected Path extdirs; + protected boolean includeAntRuntime; + protected boolean includeJavaRuntime; + protected Location location; + protected String memoryInitialSize; + protected String memoryMaximumSize; + protected Project project; + + /* + * jdg - TODO - all these attributes are currently protected, but they + * should probably be private in the near future. + */ + protected Path src; + protected String target; + + public void setJavac( Javac attributes ) + { + this.attributes = attributes; + src = attributes.getSrcdir(); + destDir = attributes.getDestdir(); + encoding = attributes.getEncoding(); + debug = attributes.getDebug(); + optimize = attributes.getOptimize(); + deprecation = attributes.getDeprecation(); + depend = attributes.getDepend(); + verbose = attributes.getVerbose(); + target = attributes.getTarget(); + bootclasspath = attributes.getBootclasspath(); + extdirs = attributes.getExtdirs(); + compileList = attributes.getFileList(); + compileClasspath = attributes.getClasspath(); + project = attributes.getProject(); + location = attributes.getLocation(); + includeAntRuntime = attributes.getIncludeantruntime(); + includeJavaRuntime = attributes.getIncludejavaruntime(); + memoryInitialSize = attributes.getMemoryInitialSize(); + memoryMaximumSize = attributes.getMemoryMaximumSize(); + } + + public Javac getJavac() + { + return attributes; + } + + protected Commandline setupJavacCommand() + { + return setupJavacCommand( false ); + } + + /** + * Does the command line argument processing for classic and adds the files + * to compile as well. + * + * @param debugLevelCheck Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline setupJavacCommand( boolean debugLevelCheck ) + { + Commandline cmd = new Commandline(); + setupJavacCommandlineSwitches( cmd, debugLevelCheck ); + logAndAddFilesToCompile( cmd ); + return cmd; + } + + protected Commandline setupJavacCommandlineSwitches( Commandline cmd ) + { + return setupJavacCommandlineSwitches( cmd, false ); + } + + /** + * Does the command line argument processing common to classic and modern. + * Doesn't add the files to compile. + * + * @param cmd Description of Parameter + * @param useDebugLevel Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline setupJavacCommandlineSwitches( Commandline cmd, + boolean useDebugLevel ) + { + Path classpath = getCompileClasspath(); + + // we cannot be using Java 1.0 when forking, so we only have to + // distinguish between Java 1.1, and Java 1.2 and higher, as Java 1.1 + // has its own parameter format + boolean usingJava1_1 = Project.getJavaVersion().equals( Project.JAVA_1_1 ); + String memoryParameterPrefix = usingJava1_1 ? "-J-" : "-J-X"; + if( memoryInitialSize != null ) + { + if( !attributes.isForkedJavac() ) + { + attributes.log( "Since fork is false, ignoring memoryInitialSize setting.", + Project.MSG_WARN ); + } + else + { + cmd.createArgument().setValue( memoryParameterPrefix + "ms" + memoryInitialSize ); + } + } + + if( memoryMaximumSize != null ) + { + if( !attributes.isForkedJavac() ) + { + attributes.log( "Since fork is false, ignoring memoryMaximumSize setting.", + Project.MSG_WARN ); + } + else + { + cmd.createArgument().setValue( memoryParameterPrefix + "mx" + memoryMaximumSize ); + } + } + + if( attributes.getNowarn() ) + { + cmd.createArgument().setValue( "-nowarn" ); + } + + if( deprecation == true ) + { + cmd.createArgument().setValue( "-deprecation" ); + } + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + cmd.createArgument().setValue( "-classpath" ); + + // Just add "sourcepath" to classpath ( for JDK1.1 ) + // as well as "bootclasspath" and "extdirs" + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + Path cp = new Path( project ); + /* + * XXX - This doesn't mix very well with build.systemclasspath, + */ + if( bootclasspath != null ) + { + cp.append( bootclasspath ); + } + if( extdirs != null ) + { + cp.addExtdirs( extdirs ); + } + cp.append( classpath ); + cp.append( src ); + cmd.createArgument().setPath( cp ); + } + else + { + cmd.createArgument().setPath( classpath ); + cmd.createArgument().setValue( "-sourcepath" ); + cmd.createArgument().setPath( src ); + if( target != null ) + { + cmd.createArgument().setValue( "-target" ); + cmd.createArgument().setValue( target ); + } + if( bootclasspath != null ) + { + cmd.createArgument().setValue( "-bootclasspath" ); + cmd.createArgument().setPath( bootclasspath ); + } + if( extdirs != null ) + { + cmd.createArgument().setValue( "-extdirs" ); + cmd.createArgument().setPath( extdirs ); + } + } + + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + if( debug ) + { + if( useDebugLevel + && Project.getJavaVersion() != Project.JAVA_1_0 + && Project.getJavaVersion() != Project.JAVA_1_1 ) + { + + String debugLevel = attributes.getDebugLevel(); + if( debugLevel != null ) + { + cmd.createArgument().setValue( "-g:" + debugLevel ); + } + else + { + cmd.createArgument().setValue( "-g" ); + } + } + else + { + cmd.createArgument().setValue( "-g" ); + } + } + else if( Project.getJavaVersion() != Project.JAVA_1_0 && + Project.getJavaVersion() != Project.JAVA_1_1 ) + { + cmd.createArgument().setValue( "-g:none" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "-O" ); + } + + if( depend ) + { + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + cmd.createArgument().setValue( "-depend" ); + } + else if( Project.getJavaVersion().startsWith( "1.2" ) ) + { + cmd.createArgument().setValue( "-Xdepend" ); + } + else + { + attributes.log( "depend attribute is not supported by the modern compiler", + Project.MSG_WARN ); + } + } + + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + + addCurrentCompilerArgs( cmd ); + + return cmd; + } + + /** + * Does the command line argument processing for modern and adds the files + * to compile as well. + * + * @return Description of the Returned Value + */ + protected Commandline setupModernJavacCommand() + { + Commandline cmd = new Commandline(); + setupModernJavacCommandlineSwitches( cmd ); + + logAndAddFilesToCompile( cmd ); + return cmd; + } + + /** + * Does the command line argument processing for modern. Doesn't add the + * files to compile. + * + * @param cmd Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline setupModernJavacCommandlineSwitches( Commandline cmd ) + { + setupJavacCommandlineSwitches( cmd, true ); + if( attributes.getSource() != null ) + { + cmd.createArgument().setValue( "-source" ); + cmd.createArgument().setValue( attributes.getSource() ); + } + return cmd; + } + + /** + * Builds the compilation classpath. + * + * @return The CompileClasspath value + */ + protected Path getCompileClasspath() + { + Path classpath = new Path( project ); + + // add dest dir to classpath so that previously compiled and + // untouched classes are on classpath + + if( destDir != null ) + { + classpath.setLocation( destDir ); + } + + // Combine the build classpath with the system classpath, in an + // order determined by the value of build.classpath + + if( compileClasspath == null ) + { + if( includeAntRuntime ) + { + classpath.addExisting( Path.systemClasspath ); + } + } + else + { + if( includeAntRuntime ) + { + classpath.addExisting( compileClasspath.concatSystemClasspath( "last" ) ); + } + else + { + classpath.addExisting( compileClasspath.concatSystemClasspath( "ignore" ) ); + } + } + + if( includeJavaRuntime ) + { + classpath.addJavaRuntime(); + } + + return classpath; + } + + /** + * Adds the command line arguments specifc to the current implementation. + * + * @param cmd The feature to be added to the CurrentCompilerArgs attribute + */ + protected void addCurrentCompilerArgs( Commandline cmd ) + { + cmd.addArguments( getJavac().getCurrentCompilerArgs() ); + } + + /** + * Do the compile with the specified arguments. + * + * @param args - arguments to pass to process on command line + * @param firstFileName - index of the first source file in args + * @return Description of the Returned Value + */ + protected int executeExternalCompile( String[] args, int firstFileName ) + { + String[] commandArray = null; + File tmpFile = null; + + try + { + /* + * Many system have been reported to get into trouble with + * long command lines - no, not only Windows ;-). + * + * POSIX seems to define a lower limit of 4k, so use a temporary + * file if the total length of the command line exceeds this limit. + */ + if( Commandline.toString( args ).length() > 4096 ) + { + PrintWriter out = null; + try + { + tmpFile = fileUtils.createTempFile( "jikes", "", null ); + out = new PrintWriter( new FileWriter( tmpFile ) ); + for( int i = firstFileName; i < args.length; i++ ) + { + out.println( args[i] ); + } + out.flush(); + commandArray = new String[firstFileName + 1]; + System.arraycopy( args, 0, commandArray, 0, firstFileName ); + commandArray[firstFileName] = "@" + tmpFile.getAbsolutePath(); + } + catch( IOException e ) + { + throw new BuildException( "Error creating temporary file", e, location ); + } + finally + { + if( out != null ) + { + try + { + out.close(); + } + catch( Throwable t ) + {} + } + } + } + else + { + commandArray = args; + } + + try + { + Execute exe = new Execute( new LogStreamHandler( attributes, + Project.MSG_INFO, + Project.MSG_WARN ) ); + exe.setAntRun( project ); + exe.setWorkingDirectory( project.getBaseDir() ); + exe.setCommandline( commandArray ); + exe.execute(); + return exe.getExitValue(); + } + catch( IOException e ) + { + throw new BuildException( "Error running " + args[0] + + " compiler", e, location ); + } + } + finally + { + if( tmpFile != null ) + { + tmpFile.delete(); + } + } + } + + /** + * Logs the compilation parameters, adds the files to compile and logs the + * &qout;niceSourceList" + * + * @param cmd Description of Parameter + */ + protected void logAndAddFilesToCompile( Commandline cmd ) + { + attributes.log( "Compilation args: " + cmd.toString(), + Project.MSG_VERBOSE ); + + StringBuffer niceSourceList = new StringBuffer( "File" ); + if( compileList.length != 1 ) + { + niceSourceList.append( "s" ); + } + niceSourceList.append( " to be compiled:" ); + + niceSourceList.append( lSep ); + + for( int i = 0; i < compileList.length; i++ ) + { + String arg = compileList[i].getAbsolutePath(); + cmd.createArgument().setValue( arg ); + niceSourceList.append( " " + arg + lSep ); + } + + attributes.log( niceSourceList.toString(), Project.MSG_VERBOSE ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Gcj.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Gcj.java new file mode 100644 index 000000000..ee0918e10 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Gcj.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the gcj compiler. This is primarily a cut-and-paste + * from the jikes. + * + * @author Takashi Okamoto + */ +public class Gcj extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the gcj compiler. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @author tora@debian.org + */ + public boolean execute() + throws BuildException + { + Commandline cmd; + attributes.log( "Using gcj compiler", Project.MSG_VERBOSE ); + cmd = setupGCJCommand(); + + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + + protected Commandline setupGCJCommand() + { + Commandline cmd = new Commandline(); + Path classpath = new Path( project ); + + // gcj doesn't support bootclasspath dir (-bootclasspath) + // so we'll emulate it for compatibility and convenience. + if( bootclasspath != null ) + { + classpath.append( bootclasspath ); + } + + // gcj doesn't support an extension dir (-extdir) + // so we'll emulate it for compatibility and convenience. + classpath.addExtdirs( extdirs ); + + if( ( bootclasspath == null ) || ( bootclasspath.size() == 0 ) ) + { + // no bootclasspath, therefore, get one from the java runtime + includeJavaRuntime = true; + } + classpath.append( getCompileClasspath() ); + + // Gcj has no option for source-path so we + // will add it to classpath. + classpath.append( src ); + + cmd.setExecutable( "gcj" ); + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + + if( destDir.mkdirs() ) + { + throw new BuildException( "Can't make output directories. Maybe permission is wrong. " ); + } + ; + } + + cmd.createArgument().setValue( "-classpath" ); + cmd.createArgument().setPath( classpath ); + + if( encoding != null ) + { + cmd.createArgument().setValue( "--encoding=" + encoding ); + } + if( debug ) + { + cmd.createArgument().setValue( "-g1" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "-O" ); + } + + /** + * gcj should be set for generate class. + */ + cmd.createArgument().setValue( "-C" ); + + addCurrentCompilerArgs( cmd ); + + return cmd; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac12.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac12.java new file mode 100644 index 000000000..9eee2523a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac12.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.types.Commandline; + +/** + * The implementation of the javac compiler for JDK 1.2 This is primarily a + * cut-and-paste from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Javac12 extends DefaultCompilerAdapter +{ + + public boolean execute() + throws BuildException + { + attributes.log( "Using classic compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupJavacCommand( true ); + + OutputStream logstr = new LogOutputStream( attributes, Project.MSG_WARN ); + try + { + // Create an instance of the compiler, redirecting output to + // the project log + Class c = Class.forName( "sun.tools.javac.Main" ); + Constructor cons = c.getConstructor( new Class[]{OutputStream.class, String.class} ); + Object compiler = cons.newInstance( new Object[]{logstr, "javac"} ); + + // Call the compile() method + Method compile = c.getMethod( "compile", new Class[]{String[].class} ); + Boolean ok = ( Boolean )compile.invoke( compiler, new Object[]{cmd.getArguments()} ); + return ok.booleanValue(); + } + catch( ClassNotFoundException ex ) + { + throw new BuildException( "Cannot use classic compiler, as it is not available" + + " A common solution is to set the environment variable" + + " JAVA_HOME to your jdk directory.", location ); + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting classic compiler: ", ex, location ); + } + } + finally + { + try + { + logstr.close(); + } + catch( IOException e ) + { + // plain impossible + throw new BuildException( e ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac13.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac13.java new file mode 100644 index 000000000..7516d43c9 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Javac13.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.lang.reflect.Method; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + +/** + * The implementation of the javac compiler for JDK 1.3 This is primarily a + * cut-and-paste from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Javac13 extends DefaultCompilerAdapter +{ + + /** + * Integer returned by the "Modern" jdk1.3 compiler to indicate success. + */ + private final static int MODERN_COMPILER_SUCCESS = 0; + + public boolean execute() + throws BuildException + { + attributes.log( "Using modern compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupModernJavacCommand(); + + // Use reflection to be able to build on all JDKs >= 1.1: + try + { + Class c = Class.forName( "com.sun.tools.javac.Main" ); + Object compiler = c.newInstance(); + Method compile = c.getMethod( "compile", + new Class[]{( new String[]{} ).getClass()} ); + int result = ( ( Integer )compile.invoke + ( compiler, new Object[]{cmd.getArguments()} ) ).intValue(); + return ( result == MODERN_COMPILER_SUCCESS ); + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting modern compiler", ex, location ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java new file mode 100644 index 000000000..528b7cd43 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + +/** + * Performs a compile using javac externally. + * + * @author Brian Deitte + */ +public class JavacExternal extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the Javac externally. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean execute() + throws BuildException + { + attributes.log( "Using external javac compiler", Project.MSG_VERBOSE ); + + Commandline cmd = new Commandline(); + cmd.setExecutable( getJavac().getJavacExecutable() ); + setupModernJavacCommandlineSwitches( cmd ); + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jikes.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jikes.java new file mode 100644 index 000000000..a45f64c3c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jikes.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the jikes compiler. This is primarily a cut-and-paste + * from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Jikes extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the Jikes compiler from IBM.. Mostly of this + * code is identical to doClassicCompile() However, it does not support all + * options like bootclasspath, extdirs, deprecation and so on, because there + * is no option in jikes and I don't understand what they should do. It has + * been successfully tested with jikes >1.10 + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @author skanthak@muehlheim.de + */ + public boolean execute() + throws BuildException + { + attributes.log( "Using jikes compiler", Project.MSG_VERBOSE ); + + Path classpath = new Path( project ); + + // Jikes doesn't support bootclasspath dir (-bootclasspath) + // so we'll emulate it for compatibility and convenience. + if( bootclasspath != null ) + { + classpath.append( bootclasspath ); + } + + // Jikes doesn't support an extension dir (-extdir) + // so we'll emulate it for compatibility and convenience. + classpath.addExtdirs( extdirs ); + + if( ( bootclasspath == null ) || ( bootclasspath.size() == 0 ) ) + { + // no bootclasspath, therefore, get one from the java runtime + includeJavaRuntime = true; + } + else + { + // there is a bootclasspath stated. By default, the + // includeJavaRuntime is false. If the user has stated a + // bootclasspath and said to include the java runtime, it's on + // their head! + } + classpath.append( getCompileClasspath() ); + + // Jikes has no option for source-path so we + // will add it to classpath. + classpath.append( src ); + + // if the user has set JIKESPATH we should add the contents as well + String jikesPath = System.getProperty( "jikes.class.path" ); + if( jikesPath != null ) + { + classpath.append( new Path( project, jikesPath ) ); + } + + Commandline cmd = new Commandline(); + cmd.setExecutable( "jikes" ); + + if( deprecation == true ) + cmd.createArgument().setValue( "-deprecation" ); + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + cmd.createArgument().setValue( "-classpath" ); + cmd.createArgument().setPath( classpath ); + + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + if( debug ) + { + cmd.createArgument().setValue( "-g" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "-O" ); + } + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + if( depend ) + { + cmd.createArgument().setValue( "-depend" ); + } + /** + * XXX Perhaps we shouldn't use properties for these three options + * (emacs mode, warnings and pedantic), but include it in the javac + * directive? + */ + + /** + * Jikes has the nice feature to print error messages in a form readable + * by emacs, so that emacs can directly set the cursor to the place, + * where the error occured. + */ + String emacsProperty = project.getProperty( "build.compiler.emacs" ); + if( emacsProperty != null && Project.toBoolean( emacsProperty ) ) + { + cmd.createArgument().setValue( "+E" ); + } + + /** + * Jikes issues more warnings that javac, for example, when you have + * files in your classpath that don't exist. As this is often the case, + * these warning can be pretty annoying. + */ + String warningsProperty = project.getProperty( "build.compiler.warnings" ); + if( warningsProperty != null ) + { + attributes.log( "!! the build.compiler.warnings property is deprecated. !!", + Project.MSG_WARN ); + attributes.log( "!! Use the nowarn attribute instead. !!", + Project.MSG_WARN ); + if( !Project.toBoolean( warningsProperty ) ) + { + cmd.createArgument().setValue( "-nowarn" ); + } + } + if( attributes.getNowarn() ) + { + /* + * FIXME later + * + * let the magic property win over the attribute for backwards + * compatibility + */ + cmd.createArgument().setValue( "-nowarn" ); + } + + /** + * Jikes can issue pedantic warnings. + */ + String pedanticProperty = project.getProperty( "build.compiler.pedantic" ); + if( pedanticProperty != null && Project.toBoolean( pedanticProperty ) ) + { + cmd.createArgument().setValue( "+P" ); + } + + /** + * Jikes supports something it calls "full dependency checking", see the + * jikes documentation for differences between -depend and +F. + */ + String fullDependProperty = project.getProperty( "build.compiler.fulldepend" ); + if( fullDependProperty != null && Project.toBoolean( fullDependProperty ) ) + { + cmd.createArgument().setValue( "+F" ); + } + + addCurrentCompilerArgs( cmd ); + + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jvc.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jvc.java new file mode 100644 index 000000000..ccb0b0f2a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Jvc.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the jvc compiler from microsoft. This is primarily a + * cut-and-paste from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Jvc extends DefaultCompilerAdapter +{ + + public boolean execute() + throws BuildException + { + attributes.log( "Using jvc compiler", Project.MSG_VERBOSE ); + + Path classpath = new Path( project ); + + // jvc doesn't support bootclasspath dir (-bootclasspath) + // so we'll emulate it for compatibility and convenience. + if( bootclasspath != null ) + { + classpath.append( bootclasspath ); + } + + // jvc doesn't support an extension dir (-extdir) + // so we'll emulate it for compatibility and convenience. + classpath.addExtdirs( extdirs ); + + if( ( bootclasspath == null ) || ( bootclasspath.size() == 0 ) ) + { + // no bootclasspath, therefore, get one from the java runtime + includeJavaRuntime = true; + } + else + { + // there is a bootclasspath stated. By default, the + // includeJavaRuntime is false. If the user has stated a + // bootclasspath and said to include the java runtime, it's on + // their head! + } + classpath.append( getCompileClasspath() ); + + // jvc has no option for source-path so we + // will add it to classpath. + classpath.append( src ); + + Commandline cmd = new Commandline(); + cmd.setExecutable( "jvc" ); + + if( destDir != null ) + { + cmd.createArgument().setValue( "/d" ); + cmd.createArgument().setFile( destDir ); + } + + // Add the Classpath before the "internal" one. + cmd.createArgument().setValue( "/cp:p" ); + cmd.createArgument().setPath( classpath ); + + // Enable MS-Extensions and ... + cmd.createArgument().setValue( "/x-" ); + // ... do not display a Message about this. + cmd.createArgument().setValue( "/nomessage" ); + // Do not display Logo + cmd.createArgument().setValue( "/nologo" ); + + if( debug ) + { + cmd.createArgument().setValue( "/g" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "/O" ); + } + if( verbose ) + { + cmd.createArgument().setValue( "/verbose" ); + } + + addCurrentCompilerArgs( cmd ); + + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Kjc.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Kjc.java new file mode 100644 index 000000000..2cb2bdc48 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Kjc.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.lang.reflect.Method; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the Java compiler for KJC. This is primarily a + * cut-and-paste from Jikes.java and DefaultCompilerAdapter. + * + * @author Takashi Okamoto + + */ +public class Kjc extends DefaultCompilerAdapter +{ + + public boolean execute() + throws BuildException + { + attributes.log( "Using kjc compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupKjcCommand(); + + try + { + Class c = Class.forName( "at.dms.kjc.Main" ); + + // Call the compile() method + Method compile = c.getMethod( "compile", + new Class[]{String[].class} ); + Boolean ok = ( Boolean )compile.invoke( null, + new Object[]{cmd.getArguments()} ); + return ok.booleanValue(); + } + catch( ClassNotFoundException ex ) + { + throw new BuildException( "Cannot use kjc compiler, as it is not available" + + " A common solution is to set the environment variable" + + " CLASSPATH to your kjc archive (kjc.jar).", location ); + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting kjc compiler: ", ex, location ); + } + } + } + + /** + * setup kjc command arguments. + * + * @return Description of the Returned Value + */ + protected Commandline setupKjcCommand() + { + Commandline cmd = new Commandline(); + + // generate classpath, because kjc does't support sourcepath. + Path classpath = getCompileClasspath(); + + if( deprecation == true ) + { + cmd.createArgument().setValue( "-deprecation" ); + } + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + // generate the clsspath + cmd.createArgument().setValue( "-classpath" ); + + Path cp = new Path( project ); + + // kjc don't have bootclasspath option. + if( bootclasspath != null ) + { + cp.append( bootclasspath ); + } + + if( extdirs != null ) + { + cp.addExtdirs( extdirs ); + } + + cp.append( classpath ); + cp.append( src ); + + cmd.createArgument().setPath( cp ); + + // kjc-1.5A doesn't support -encoding option now. + // but it will be supported near the feature. + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + + if( debug ) + { + cmd.createArgument().setValue( "-g" ); + } + + if( optimize ) + { + cmd.createArgument().setValue( "-O2" ); + } + + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + + addCurrentCompilerArgs( cmd ); + + logAndAddFilesToCompile( cmd ); + return cmd; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Sj.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Sj.java new file mode 100644 index 000000000..3ab63f48b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/compilers/Sj.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + +/** + * The implementation of the sj compiler. Uses the defaults for + * DefaultCompilerAdapter + * + * @author Don Ferguson + */ +public class Sj extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the sj compiler from Symantec. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @author don@bea.com + */ + public boolean execute() + throws BuildException + { + attributes.log( "Using symantec java compiler", Project.MSG_VERBOSE ); + + Commandline cmd = setupJavacCommand(); + cmd.setExecutable( "sj" ); + + int firstFileName = cmd.size() - compileList.length; + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/And.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/And.java new file mode 100644 index 000000000..44b9810ac --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/And.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.util.Enumeration; +import org.apache.tools.ant.BuildException; + +/** + * <and> condition container.

      + * + * Iterates over all conditions and returns false as soon as one evaluates to + * false.

      + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class And extends ConditionBase implements Condition +{ + + public boolean eval() + throws BuildException + { + Enumeration enum = getConditions(); + while( enum.hasMoreElements() ) + { + Condition c = ( Condition )enum.nextElement(); + if( !c.eval() ) + { + return false; + } + } + return true; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Condition.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Condition.java new file mode 100644 index 000000000..fc589867c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Condition.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; + +import org.apache.myrmidon.api.TaskException; + +/** + * Interface for conditions to use inside the <condition> task. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public interface Condition +{ + /** + * Is this condition true? + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + boolean eval() + throws TaskException; +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/ConditionBase.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/ConditionBase.java new file mode 100644 index 000000000..a8a13820d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/ConditionBase.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.util.Enumeration; +import java.util.NoSuchElementException; +import java.util.Vector; +import org.apache.tools.ant.ProjectComponent; +import org.apache.tools.ant.taskdefs.Available; +import org.apache.tools.ant.taskdefs.Checksum; +import org.apache.tools.ant.taskdefs.UpToDate; +import org.apache.myrmidon.framework.Os; + +/** + * Baseclass for the <condition> task as well as several conditions - + * ensures that the types of conditions inside the task and the "container" + * conditions are in sync. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public abstract class ConditionBase extends ProjectComponent +{ + private Vector conditions = new Vector(); + + /** + * Add an <and> condition "container". + * + * @param a The feature to be added to the And attribute + * @since 1.1 + */ + public void addAnd( And a ) + { + conditions.addElement( a ); + } + + /** + * Add an <available> condition. + * + * @param a The feature to be added to the Available attribute + * @since 1.1 + */ + public void addAvailable( Available a ) + { + conditions.addElement( a ); + } + + /** + * Add an <checksum> condition. + * + * @param c The feature to be added to the Checksum attribute + * @since 1.4 + */ + public void addChecksum( Checksum c ) + { + conditions.addElement( c ); + } + + /** + * Add an <equals> condition. + * + * @param e The feature to be added to the Equals attribute + * @since 1.1 + */ + public void addEquals( Equals e ) + { + conditions.addElement( e ); + } + + /** + * Add an <http> condition. + * + * @param h The feature to be added to the Http attribute + * @since 1.7 + */ + public void addHttp( Http h ) + { + conditions.addElement( h ); + } + + /** + * Add an <isset> condition. + * + * @param i The feature to be added to the IsSet attribute + * @since 1.1 + */ + public void addIsSet( IsSet i ) + { + conditions.addElement( i ); + } + + /** + * Add an <not> condition "container". + * + * @param n The feature to be added to the Not attribute + * @since 1.1 + */ + public void addNot( Not n ) + { + conditions.addElement( n ); + } + + /** + * Add an <or> condition "container". + * + * @param o The feature to be added to the Or attribute + * @since 1.1 + */ + public void addOr( Or o ) + { + conditions.addElement( o ); + } + + /** + * Add an <os> condition. + * + * @param o The feature to be added to the Os attribute + * @since 1.1 + */ + public void addOs( Os o ) + { + conditions.addElement( o ); + } + + /** + * Add a <socket> condition. + * + * @param s The feature to be added to the Socket attribute + * @since 1.7 + */ + public void addSocket( Socket s ) + { + conditions.addElement( s ); + } + + /** + * Add an <uptodate> condition. + * + * @param u The feature to be added to the Uptodate attribute + * @since 1.1 + */ + public void addUptodate( UpToDate u ) + { + conditions.addElement( u ); + } + + /** + * Iterate through all conditions. + * + * @return The Conditions value + * @since 1.1 + */ + protected final Enumeration getConditions() + { + return new ConditionEnumeration(); + } + + /** + * Count the conditions. + * + * @return Description of the Returned Value + * @since 1.1 + */ + protected int countConditions() + { + return conditions.size(); + } + + /** + * Inner class that configures those conditions with a project instance that + * need it. + * + * @author RT + * @since 1.1 + */ + private class ConditionEnumeration implements Enumeration + { + private int currentElement = 0; + + public boolean hasMoreElements() + { + return countConditions() > currentElement; + } + + public Object nextElement() + throws NoSuchElementException + { + Object o = null; + try + { + o = conditions.elementAt( currentElement++ ); + } + catch( ArrayIndexOutOfBoundsException e ) + { + throw new NoSuchElementException(); + } + + if( o instanceof ProjectComponent ) + { + ( ( ProjectComponent )o ).setProject( getProject() ); + } + return o; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Equals.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Equals.java new file mode 100644 index 000000000..306f7928f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Equals.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import org.apache.tools.ant.BuildException; + +/** + * Simple String comparison condition. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class Equals implements Condition +{ + + private String arg1, arg2; + + public void setArg1( String a1 ) + { + arg1 = a1; + } + + public void setArg2( String a2 ) + { + arg2 = a2; + } + + public boolean eval() + throws BuildException + { + if( arg1 == null || arg2 == null ) + { + throw new BuildException( "both arg1 and arg2 are required in equals" ); + } + return arg1.equals( arg2 ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Http.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Http.java new file mode 100644 index 000000000..b70aa228a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Http.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; + +/** + * Condition to wait for a HTTP request to succeed. Its attribute(s) are: url - + * the URL of the request. + * + * @author Denis Hennessy + */ +public class Http extends ProjectComponent implements Condition +{ + String spec = null; + + public void setUrl( String url ) + { + spec = url; + } + + public boolean eval() + throws BuildException + { + if( spec == null ) + { + throw new BuildException( "No url specified in HTTP task" ); + } + log( "Checking for " + spec, Project.MSG_VERBOSE ); + try + { + URL url = new URL( spec ); + try + { + URLConnection conn = url.openConnection(); + if( conn instanceof HttpURLConnection ) + { + HttpURLConnection http = ( HttpURLConnection )conn; + int code = http.getResponseCode(); + log( "Result code for " + spec + " was " + code, Project.MSG_VERBOSE ); + if( code > 0 && code < 500 ) + { + return true; + } + else + { + return false; + } + } + } + catch( java.io.IOException e ) + { + return false; + } + } + catch( MalformedURLException e ) + { + throw new BuildException( "Badly formed URL: " + spec, e ); + } + return true; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/IsSet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/IsSet.java new file mode 100644 index 000000000..e7a6a11be --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/IsSet.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ProjectComponent; + +/** + * Condition that tests whether a given property has been set. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class IsSet extends ProjectComponent implements Condition +{ + private String property; + + public void setProperty( String p ) + { + property = p; + } + + public boolean eval() + throws BuildException + { + if( property == null ) + { + throw new BuildException( "No property specified for isset condition" ); + } + + return getProject().getProperty( property ) != null; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Not.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Not.java new file mode 100644 index 000000000..1af3b0bdb --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Not.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import org.apache.tools.ant.BuildException; + +/** + * <not> condition. Evaluates to true if the single condition nested into + * it is false and vice versa. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class Not extends ConditionBase implements Condition +{ + + public boolean eval() + throws BuildException + { + if( countConditions() > 1 ) + { + throw new BuildException( "You must not nest more than one condition into " ); + } + if( countConditions() < 1 ) + { + throw new BuildException( "You must nest a condition into " ); + } + return !( ( Condition )getConditions().nextElement() ).eval(); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Or.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Or.java new file mode 100644 index 000000000..62ca4641c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Or.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.util.Enumeration; +import org.apache.tools.ant.BuildException; + +/** + * <or> condition container.

      + * + * Iterates over all conditions and returns true as soon as one evaluates to + * true.

      + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class Or extends ConditionBase implements Condition +{ + + public boolean eval() + throws BuildException + { + Enumeration enum = getConditions(); + while( enum.hasMoreElements() ) + { + Condition c = ( Condition )enum.nextElement(); + if( c.eval() ) + { + return true; + } + } + return false; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Socket.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Socket.java new file mode 100644 index 000000000..b1e47526a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/condition/Socket.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; + +/** + * Condition to wait for a TCP/IP socket to have a listener. Its attribute(s) + * are: server - the name of the server. port - the port number of the socket. + * + * @author Denis Hennessy + */ +public class Socket extends ProjectComponent implements Condition +{ + String server = null; + int port = 0; + + public void setPort( int port ) + { + this.port = port; + } + + public void setServer( String server ) + { + this.server = server; + } + + public boolean eval() + throws BuildException + { + if( server == null ) + { + throw new BuildException( "No server specified in Socket task" ); + } + if( port == 0 ) + { + throw new BuildException( "No port specified in Socket task" ); + } + log( "Checking for listener at " + server + ":" + port, Project.MSG_VERBOSE ); + try + { + java.net.Socket socket = new java.net.Socket( server, port ); + } + catch( IOException e ) + { + return false; + } + return true; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/defaults.properties b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/defaults.properties new file mode 100644 index 000000000..c680c8f92 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/defaults.properties @@ -0,0 +1,141 @@ +# standard ant tasks +mkdir=org.apache.tools.ant.taskdefs.Mkdir +javac=org.apache.tools.ant.taskdefs.Javac +chmod=org.apache.tools.ant.taskdefs.Chmod +delete=org.apache.tools.ant.taskdefs.Delete +copy=org.apache.tools.ant.taskdefs.Copy +move=org.apache.tools.ant.taskdefs.Move +jar=org.apache.tools.ant.taskdefs.Jar +rmic=org.apache.tools.ant.taskdefs.Rmic +cvs=org.apache.tools.ant.taskdefs.Cvs +get=org.apache.tools.ant.taskdefs.Get +unzip=org.apache.tools.ant.taskdefs.Expand +unjar=org.apache.tools.ant.taskdefs.Expand +unwar=org.apache.tools.ant.taskdefs.Expand +echo=org.apache.tools.ant.taskdefs.Echo +javadoc=org.apache.tools.ant.taskdefs.Javadoc +zip=org.apache.tools.ant.taskdefs.Zip +gzip=org.apache.tools.ant.taskdefs.GZip +gunzip=org.apache.tools.ant.taskdefs.GUnzip +replace=org.apache.tools.ant.taskdefs.Replace +java=org.apache.tools.ant.taskdefs.Java +tstamp=org.apache.tools.ant.taskdefs.Tstamp +property=org.apache.tools.ant.taskdefs.Property +taskdef=org.apache.tools.ant.taskdefs.Taskdef +ant=org.apache.tools.ant.taskdefs.Ant +exec=org.apache.tools.ant.taskdefs.ExecTask +tar=org.apache.tools.ant.taskdefs.Tar +untar=org.apache.tools.ant.taskdefs.Untar +available=org.apache.tools.ant.taskdefs.Available +filter=org.apache.tools.ant.taskdefs.Filter +fixcrlf=org.apache.tools.ant.taskdefs.FixCRLF +patch=org.apache.tools.ant.taskdefs.Patch +style=org.apache.tools.ant.taskdefs.XSLTProcess +touch=org.apache.tools.ant.taskdefs.Touch +signjar=org.apache.tools.ant.taskdefs.SignJar +genkey=org.apache.tools.ant.taskdefs.GenerateKey +antstructure=org.apache.tools.ant.taskdefs.AntStructure +execon=org.apache.tools.ant.taskdefs.ExecuteOn +antcall=org.apache.tools.ant.taskdefs.CallTarget +sql=org.apache.tools.ant.taskdefs.SQLExec +mail=org.apache.tools.ant.taskdefs.SendEmail +fail=org.apache.tools.ant.taskdefs.Exit +war=org.apache.tools.ant.taskdefs.War +uptodate=org.apache.tools.ant.taskdefs.UpToDate +apply=org.apache.tools.ant.taskdefs.Transform +record=org.apache.tools.ant.taskdefs.Recorder +cvspass=org.apache.tools.ant.taskdefs.CVSPass +typedef=org.apache.tools.ant.taskdefs.Typedef +sleep=org.apache.tools.ant.taskdefs.Sleep +pathconvert=org.apache.tools.ant.taskdefs.PathConvert +ear=org.apache.tools.ant.taskdefs.Ear +parallel=org.apache.tools.ant.taskdefs.Parallel +sequential=org.apache.tools.ant.taskdefs.Sequential +condition=org.apache.tools.ant.taskdefs.ConditionTask +dependset=org.apache.tools.ant.taskdefs.DependSet +bzip2=org.apache.tools.ant.taskdefs.BZip2 +bunzip2=org.apache.tools.ant.taskdefs.BUnzip2 +checksum=org.apache.tools.ant.taskdefs.Checksum +waitfor=org.apache.tools.ant.taskdefs.WaitFor +input=org.apache.tools.ant.taskdefs.Input +manifest=org.apache.tools.ant.taskdefs.Manifest + +# optional tasks +script=org.apache.tools.ant.taskdefs.optional.Script +netrexxc=org.apache.tools.ant.taskdefs.optional.NetRexxC +renameext=org.apache.tools.ant.taskdefs.optional.RenameExtensions +ejbc=org.apache.tools.ant.taskdefs.optional.ejb.Ejbc +ddcreator=org.apache.tools.ant.taskdefs.optional.ejb.DDCreator +wlrun=org.apache.tools.ant.taskdefs.optional.ejb.WLRun +wlstop=org.apache.tools.ant.taskdefs.optional.ejb.WLStop +vssget=org.apache.tools.ant.taskdefs.optional.vss.MSVSSGET +ejbjar=org.apache.tools.ant.taskdefs.optional.ejb.EjbJar +mparse=org.apache.tools.ant.taskdefs.optional.metamata.MParse +mmetrics=org.apache.tools.ant.taskdefs.optional.metamata.MMetrics +maudit=org.apache.tools.ant.taskdefs.optional.metamata.MAudit +junit=org.apache.tools.ant.taskdefs.optional.junit.JUnitTask +cab=org.apache.tools.ant.taskdefs.optional.Cab +ftp=org.apache.tools.ant.taskdefs.optional.net.FTP +icontract=org.apache.tools.ant.taskdefs.optional.IContract +javacc=org.apache.tools.ant.taskdefs.optional.javacc.JavaCC +jjtree=org.apache.tools.ant.taskdefs.optional.javacc.JJTree +starteam=org.apache.tools.ant.taskdefs.optional.scm.AntStarTeamCheckOut +wljspc=org.apache.tools.ant.taskdefs.optional.jsp.WLJspc +jlink=org.apache.tools.ant.taskdefs.optional.jlink.JlinkTask +native2ascii=org.apache.tools.ant.taskdefs.optional.Native2Ascii +propertyfile=org.apache.tools.ant.taskdefs.optional.PropertyFile +depend=org.apache.tools.ant.taskdefs.optional.depend.Depend +antlr=org.apache.tools.ant.taskdefs.optional.ANTLR +vajload=org.apache.tools.ant.taskdefs.optional.ide.VAJLoadProjects +vajexport=org.apache.tools.ant.taskdefs.optional.ide.VAJExport +vajimport=org.apache.tools.ant.taskdefs.optional.ide.VAJImport +telnet=org.apache.tools.ant.taskdefs.optional.net.TelnetTask +csc=org.apache.tools.ant.taskdefs.optional.dotnet.CSharp +ilasm=org.apache.tools.ant.taskdefs.optional.dotnet.Ilasm +stylebook=org.apache.tools.ant.taskdefs.optional.StyleBook +test=org.apache.tools.ant.taskdefs.optional.Test +pvcs=org.apache.tools.ant.taskdefs.optional.pvcs.Pvcs +p4change=org.apache.tools.ant.taskdefs.optional.perforce.P4Change +p4label=org.apache.tools.ant.taskdefs.optional.perforce.P4Label +p4have=org.apache.tools.ant.taskdefs.optional.perforce.P4Have +p4sync=org.apache.tools.ant.taskdefs.optional.perforce.P4Sync +p4edit=org.apache.tools.ant.taskdefs.optional.perforce.P4Edit +p4submit=org.apache.tools.ant.taskdefs.optional.perforce.P4Submit +p4counter=org.apache.tools.ant.taskdefs.optional.perforce.P4Counter +javah=org.apache.tools.ant.taskdefs.optional.Javah +ccupdate=org.apache.tools.ant.taskdefs.optional.clearcase.CCUpdate +cccheckout=org.apache.tools.ant.taskdefs.optional.clearcase.CCCheckout +cccheckin=org.apache.tools.ant.taskdefs.optional.clearcase.CCCheckin +ccuncheckout=org.apache.tools.ant.taskdefs.optional.clearcase.CCUnCheckout +sound=org.apache.tools.ant.taskdefs.optional.sound.SoundTask +junitreport=org.apache.tools.ant.taskdefs.optional.junit.XMLResultAggregator +vsslabel=org.apache.tools.ant.taskdefs.optional.vss.MSVSSLABEL +vsshistory=org.apache.tools.ant.taskdefs.optional.vss.MSVSSHISTORY +blgenclient=org.apache.tools.ant.taskdefs.optional.ejb.BorlandGenerateClient +rpm=org.apache.tools.ant.taskdefs.optional.Rpm +xmlvalidate=org.apache.tools.ant.taskdefs.optional.XMLValidateTask +vsscheckin=org.apache.tools.ant.taskdefs.optional.vss.MSVSSCHECKIN +vsscheckout=org.apache.tools.ant.taskdefs.optional.vss.MSVSSCHECKOUT +iplanet-ejbc=org.apache.tools.ant.taskdefs.optional.ejb.IPlanetEjbcTask +jdepend=org.apache.tools.ant.taskdefs.optional.jdepend.JDependTask +mimemail=org.apache.tools.ant.taskdefs.optional.net.MimeMail +ccmcheckin=org.apache.tools.ant.taskdefs.optional.ccm.CCMCheckin +ccmcheckout=org.apache.tools.ant.taskdefs.optional.ccm.CCMCheckout +ccmcheckintask=org.apache.tools.ant.taskdefs.optional.ccm.CCMCheckinDefault +ccmreconfigure=org.apache.tools.ant.taskdefs.optional.ccm.CCMReconfigure +ccmcreatetask=org.apache.tools.ant.taskdefs.optional.ccm.CCMCreateTask +jpcoverage=org.apache.tools.ant.taskdefs.optional.sitraka.Coverage +jpcovmerge=org.apache.tools.ant.taskdefs.optional.sitraka.CovMerge +jpcovreport=org.apache.tools.ant.taskdefs.optional.sitraka.CovReport +p4add=org.apache.tools.ant.taskdefs.optional.perforce.P4Add +jspc=org.apache.tools.ant.taskdefs.optional.jsp.JspC +replaceregexp=org.apache.tools.ant.taskdefs.optional.ReplaceRegExp +translate=org.apache.tools.ant.taskdefs.optional.i18n.Translate + +# deprecated ant tasks (kept for back compatibility) +javadoc2=org.apache.tools.ant.taskdefs.Javadoc +#compileTask=org.apache.tools.ant.taskdefs.CompileTask +copydir=org.apache.tools.ant.taskdefs.Copydir +copyfile=org.apache.tools.ant.taskdefs.Copyfile +deltree=org.apache.tools.ant.taskdefs.Deltree +rename=org.apache.tools.ant.taskdefs.Rename diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ANTLR.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ANTLR.java new file mode 100644 index 000000000..7269bf2b2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ANTLR.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.URL; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ExitException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteJava; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; + +/** + * ANTLR task. + * + * @author Erik Meade + * @author <classpath> allows classpath to be set because a + * directory might be given for Antlr debug... + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + return commandline.createClasspath( project ).createPath(); + } + + /** + * Create a new JVM argument. Ignored if no JVM is forked. + * + * @return create a new JVM argument so that any argument can be passed to + * the JVM. + * @see #setFork(boolean) + */ + public Commandline.Argument createJvmarg() + { + return commandline.createVmArgument(); + } + + public void execute() + throws BuildException + { + validateAttributes(); + + //TODO: use ANTLR to parse the grammer file to do this. + if( target.lastModified() > getGeneratedFile().lastModified() ) + { + commandline.createArgument().setValue( "-o" ); + commandline.createArgument().setValue( outputDirectory.toString() ); + commandline.createArgument().setValue( target.toString() ); + + if( fork ) + { + log( "Forking " + commandline.toString(), Project.MSG_VERBOSE ); + int err = run( commandline.getCommandline() ); + if( err == 1 ) + { + throw new BuildException( "ANTLR returned: " + err, location ); + } + } + else + { + ExecuteJava exe = new ExecuteJava(); + exe.setJavaCommand( commandline.getJavaCommand() ); + exe.setClasspath( commandline.getClasspath() ); + try + { + exe.execute( project ); + } + catch( ExitException e ) + { + if( e.getStatus() != 0 ) + { + throw new BuildException( "ANTLR returned: " + e.getStatus(), location ); + } + } + } + } + } + + /** + * Adds the jars or directories containing Antlr this should make the forked + * JVM work without having to specify it directly. + * + * @exception BuildException Description of Exception + */ + public void init() + throws BuildException + { + addClasspathEntry( "/antlr/Tool.class" ); + } + + /** + * Search for the given resource and add the directory or archive that + * contains it to the classpath.

      + * + * Doesn't work for archives in JDK 1.1 as the URL returned by getResource + * doesn't contain the name of the archive.

      + * + * @param resource The feature to be added to the ClasspathEntry attribute + */ + protected void addClasspathEntry( String resource ) + { + URL url = getClass().getResource( resource ); + if( url != null ) + { + String u = url.toString(); + if( u.startsWith( "jar:file:" ) ) + { + int pling = u.indexOf( "!" ); + String jarName = u.substring( 9, pling ); + log( "Implicitly adding " + jarName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( jarName ) ).getAbsolutePath() ) ); + } + else if( u.startsWith( "file:" ) ) + { + int tail = u.indexOf( resource ); + String dirName = u.substring( 5, tail ); + log( "Implicitly adding " + dirName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( dirName ) ).getAbsolutePath() ) ); + } + else + { + log( "Don\'t know how to handle resource URL " + u, + Project.MSG_DEBUG ); + } + } + else + { + log( "Couldn\'t find " + resource, Project.MSG_DEBUG ); + } + } + + private File getGeneratedFile() + throws BuildException + { + String generatedFileName = null; + try + { + BufferedReader in = new BufferedReader( new FileReader( target ) ); + String line; + while( ( line = in.readLine() ) != null ) + { + int extendsIndex = line.indexOf( " extends " ); + if( line.startsWith( "class " ) && extendsIndex > -1 ) + { + generatedFileName = line.substring( 6, extendsIndex ).trim(); + break; + } + } + in.close(); + } + catch( Exception e ) + { + throw new BuildException( "Unable to determine generated class", e ); + } + if( generatedFileName == null ) + { + throw new BuildException( "Unable to determine generated class" ); + } + return new File( outputDirectory, generatedFileName + ".java" ); + } + + /** + * execute in a forked VM + * + * @param command Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int run( String[] command ) + throws BuildException + { + Execute exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ), null ); + exe.setAntRun( project ); + if( workingdir != null ) + { + exe.setWorkingDirectory( workingdir ); + } + exe.setCommandline( command ); + try + { + return exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + private void validateAttributes() + throws BuildException + { + if( target == null || !target.isFile() ) + { + throw new BuildException( "Invalid target: " + target ); + } + + // if no output directory is specified, used the target's directory + if( outputDirectory == null ) + { + String fileName = target.toString(); + setOutputdirectory( new File( target.getParent() ) ); + } + if( !outputDirectory.isDirectory() ) + { + throw new BuildException( "Invalid output directory: " + outputDirectory ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java new file mode 100644 index 000000000..53d6d80ed --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.XSLTLiaison; +import org.exolab.adaptx.xslt.XSLTProcessor; +import org.exolab.adaptx.xslt.XSLTReader; +import org.exolab.adaptx.xslt.XSLTStylesheet; + +/** + * @author
      Arnaud Blandin + * @version $Revision$ $Date$ + */ +public class AdaptxLiaison implements XSLTLiaison +{ + + protected XSLTProcessor processor; + protected XSLTStylesheet xslSheet; + + public AdaptxLiaison() + { + processor = new XSLTProcessor(); + } + + public void setOutputtype( String type ) + throws Exception + { + if( !type.equals( "xml" ) ) + throw new BuildException( "Unsupported output type: " + type ); + } + + public void setStylesheet( File fileName ) + throws Exception + { + XSLTReader xslReader = new XSLTReader(); + xslSheet = xslReader.read( fileName.getAbsolutePath() ); + } + + public void addParam( String name, String expression ) + { + processor.setProperty( name, expression ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileOutputStream fos = new FileOutputStream( outfile ); + OutputStreamWriter out = new OutputStreamWriter( fos, "UTF8" ); + processor.process( infile.getAbsolutePath(), xslSheet, out ); + } + +}//-- AdaptxLiaison diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Cab.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Cab.java new file mode 100644 index 000000000..65510cf56 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Cab.java @@ -0,0 +1,349 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.ExecTask; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.FileUtils; + + +/** + * Create a CAB archive. + * + * @author Roger Vaughn + * rvaughn@seaconinc.com + */ + +public class Cab extends MatchingTask +{ + private Vector filesets = new Vector(); + private boolean doCompress = true; + private boolean doVerbose = false; + + protected String archiveType = "cab"; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + private File baseDir; + + private File cabFile; + private String cmdOptions; + + /** + * This is the base directory to look in for things to cab. + * + * @param baseDir The new Basedir value + */ + public void setBasedir( File baseDir ) + { + this.baseDir = baseDir; + } + + /** + * This is the name/location of where to create the .cab file. + * + * @param cabFile The new Cabfile value + */ + public void setCabfile( File cabFile ) + { + this.cabFile = cabFile; + } + + /** + * Sets whether we want to compress the files or only store them. + * + * @param compress The new Compress value + */ + public void setCompress( boolean compress ) + { + doCompress = compress; + } + + /** + * Sets additional cabarc options that aren't supported directly. + * + * @param options The new Options value + */ + public void setOptions( String options ) + { + cmdOptions = options; + } + + /** + * Sets whether we want to see or suppress cabarc output. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + doVerbose = verbose; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + public void execute() + throws BuildException + { + + checkConfiguration(); + + Vector files = getFileList(); + + // quick exit if the target is up to date + if( isUpToDate( files ) ) + return; + + log( "Building " + archiveType + ": " + cabFile.getAbsolutePath() ); + + if( !Os.isFamily( "windows" ) ) + { + log( "Using listcab/libcabinet", Project.MSG_VERBOSE ); + + StringBuffer sb = new StringBuffer(); + + Enumeration fileEnum = files.elements(); + + while( fileEnum.hasMoreElements() ) + { + sb.append( fileEnum.nextElement() ).append( "\n" ); + } + sb.append( "\n" ).append( cabFile.getAbsolutePath() ).append( "\n" ); + + try + { + Process p = Runtime.getRuntime().exec( "listcab" ); + OutputStream out = p.getOutputStream(); + out.write( sb.toString().getBytes() ); + out.flush(); + out.close(); + } + catch( IOException ex ) + { + String msg = "Problem creating " + cabFile + " " + ex.getMessage(); + throw new BuildException( msg ); + } + } + else + { + try + { + File listFile = createListFile( files ); + ExecTask exec = createExec(); + File outFile = null; + + // die if cabarc fails + exec.setFailonerror( true ); + exec.setDir( baseDir ); + + if( !doVerbose ) + { + outFile = fileUtils.createTempFile( "ant", "", null ); + exec.setOutput( outFile ); + } + + exec.setCommand( createCommand( listFile ) ); + exec.execute(); + + if( outFile != null ) + { + outFile.delete(); + } + + listFile.delete(); + } + catch( IOException ioe ) + { + String msg = "Problem creating " + cabFile + " " + ioe.getMessage(); + throw new BuildException( msg ); + } + } + } + + /** + * Get the complete list of files to be included in the cab. Filenames are + * gathered from filesets if any have been added, otherwise from the + * traditional include parameters. + * + * @return The FileList value + * @exception BuildException Description of Exception + */ + protected Vector getFileList() + throws BuildException + { + Vector files = new Vector(); + + if( filesets.size() == 0 ) + { + // get files from old methods - includes and nested include + appendFiles( files, super.getDirectoryScanner( baseDir ) ); + } + else + { + // get files from filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + if( fs != null ) + { + appendFiles( files, fs.getDirectoryScanner( project ) ); + } + } + } + + return files; + } + + /** + * Check to see if the target is up to date with respect to input files. + * + * @param files Description of Parameter + * @return true if the cab file is newer than its dependents. + */ + protected boolean isUpToDate( Vector files ) + { + boolean upToDate = true; + for( int i = 0; i < files.size() && upToDate; i++ ) + { + String file = files.elementAt( i ).toString(); + if( new File( baseDir, file ).lastModified() > + cabFile.lastModified() ) + upToDate = false; + } + return upToDate; + } + + /** + * Append all files found by a directory scanner to a vector. + * + * @param files Description of Parameter + * @param ds Description of Parameter + */ + protected void appendFiles( Vector files, DirectoryScanner ds ) + { + String[] dsfiles = ds.getIncludedFiles(); + + for( int i = 0; i < dsfiles.length; i++ ) + { + files.addElement( dsfiles[i] ); + } + } + + /* + * I'm not fond of this pattern: "sub-method expected to throw + * task-cancelling exceptions". It feels too much like programming + * for side-effects to me... + */ + protected void checkConfiguration() + throws BuildException + { + if( baseDir == null ) + { + throw new BuildException( "basedir attribute must be set!" ); + } + if( !baseDir.exists() ) + { + throw new BuildException( "basedir does not exist!" ); + } + if( cabFile == null ) + { + throw new BuildException( "cabfile attribute must be set!" ); + } + } + + /** + * Create the cabarc command line to use. + * + * @param listFile Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline createCommand( File listFile ) + { + Commandline command = new Commandline(); + command.setExecutable( "cabarc" ); + command.createArgument().setValue( "-r" ); + command.createArgument().setValue( "-p" ); + + if( !doCompress ) + { + command.createArgument().setValue( "-m" ); + command.createArgument().setValue( "none" ); + } + + if( cmdOptions != null ) + { + command.createArgument().setLine( cmdOptions ); + } + + command.createArgument().setValue( "n" ); + command.createArgument().setFile( cabFile ); + command.createArgument().setValue( "@" + listFile.getAbsolutePath() ); + + return command; + } + + /** + * Create a new exec delegate. The delegate task is populated so that it + * appears in the logs to be the same task as this one. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected ExecTask createExec() + throws BuildException + { + ExecTask exec = ( ExecTask )project.createTask( "exec" ); + exec.setOwningTarget( this.getOwningTarget() ); + exec.setTaskName( this.getTaskName() ); + exec.setDescription( this.getDescription() ); + + return exec; + } + + /** + * Creates a list file. This temporary file contains a list of all files to + * be included in the cab, one file per line. + * + * @param files Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + protected File createListFile( Vector files ) + throws IOException + { + File listFile = fileUtils.createTempFile( "ant", "", null ); + + PrintWriter writer = new PrintWriter( new FileOutputStream( listFile ) ); + + for( int i = 0; i < files.size(); i++ ) + { + writer.println( files.elementAt( i ).toString() ); + } + writer.close(); + + return listFile; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/IContract.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/IContract.java new file mode 100644 index 000000000..cc52a4708 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/IContract.java @@ -0,0 +1,1105 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Date; +import java.util.Properties; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.Javac; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.Mkdir; +import org.apache.tools.ant.taskdefs.compilers.DefaultCompilerAdapter; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Instruments Java classes with + * iContract DBC preprocessor.
      + * The task can generate a properties file for iControl , a graphical + * user interface that lets you turn on/off assertions. iControl generates a + * control file that you can refer to from this task using the controlfile + * attribute.

      + * + * Thanks to Rainer Schmitz for enhancements and comments. + * + * @author Aslak Hellesøy

      + * + * + * + * + * + * + * + * Attribute + * + * + * + * Description + * + * + * + * Required + * + * + * + * + * + * + * + * srcdir + * + * + * + * Location of the java files. + * + * + * + * Yes + * + * + * + * + * + * + * + * instrumentdir + * + * + * + * Indicates where the instrumented source files should go. + * + * + * + * Yes + * + * + * + * + * + * + * + * repositorydir + * + * + * + * Indicates where the repository source files should go. + * + * + * + * Yes + * + * + * + * + * + * + * + * builddir + * + * + * + * Indicates where the compiled instrumented classes should go. + * Defaults to the value of instrumentdir.

      NOTE: Don't + * use the same directory for compiled instrumented classes and + * uninstrumented classes. It will break the dependency checking. + * (Classes will not be reinstrumented if you change them). + * + * + * + * No + * + * + * + * + * + * + * + * repositorybuilddir + * + * + * + * Indicates where the compiled repository classes should go. + * Defaults to the value of repositorydir. + * + * + * + * No + * + * + * + * + * + * + * + * pre + * + * + * + * Indicates whether or not to instrument for preconditions. Defaults + * to true unless controlfile is specified, in which + * case it defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * post + * + * + * + * Indicates whether or not to instrument for postconditions. + * Defaults to true unless controlfile is specified, in + * which case it defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * invariant + * + * + * + * Indicates whether or not to instrument for invariants. Defaults to + * true unless controlfile is specified, in which case + * it defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * failthrowable + * + * + * + * The full name of the Throwable (Exception) that should be thrown + * when an assertion is violated. Defaults to java.lang.Error + * + * + * + * + * No + * + * + * + * + * + * + * + * verbosity + * + * + * + * Indicates the verbosity level of iContract. Any combination of + * error*,warning*,note*,info*,progress*,debug* (comma + * separated) can be used. Defaults to error* + * + * + * + * No + * + * + * + * + * + * + * + * quiet + * + * + * + * Indicates if iContract should be quiet. Turn it off if many your + * classes extend uninstrumented classes and you don't want warnings + * about this. Defaults to false + * + * + * + * No + * + * + * + * + * + * + * + * updateicontrol + * + * + * + * If set to true, it indicates that the properties file for iControl + * in the current directory should be updated (or created if it + * doesn't exist). Defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * controlfile + * + * + * + * The name of the control file to pass to iContract. Consider using + * iControl to generate the file. Default is not to pass a file. + * + * + * + * + * Only if updateicontrol=true + * + * + * + * + * + * + * + * classdir + * + * + * + * Indicates where compiled (unistrumented) classes are located. This + * is required in order to properly update the icontrol.properties + * file, not for instrumentation. + * + * + * + * Only if updateicontrol=true + * + * + * + * + * + * + * + * targets + * + * + * + * Name of the file that will be generated by this task, which lists + * all the classes that iContract will instrument. If specified, the + * file will not be deleted after execution. If not specified, a file + * will still be created, but it will be deleted after execution. + * + * + * + * + * No + * + * + * + * + * + *

      + * + * Note: iContract will use the java compiler indicated by the + * project's build.compiler property. See documentation of the + * Javac task for more information.

      + * + * Nested includes and excludes are also supported.

      + * + * Example:

      + * <icontract
      + *    srcdir="${build.src}"
      + *    instrumentdir="${build.instrument}"
      + *    repositorydir="${build.repository}"
      + *    builddir="${build.instrclasses}"
      + *    updateicontrol="true"
      + *    classdir="${build.classes}"
      + *    controlfile="control"
      + *    targets="targets"
      + *    verbosity="error*,warning*"
      + *    quiet="true"
      + * >
      + *    <classpath refid="compile-classpath"/>
      + * </icontract>
      + * 
      + */ +public class IContract extends MatchingTask +{ + + private final static String ICONTROL_PROPERTIES_HEADER = + " You might want to set classRoot to point to your normal compilation class root directory."; + + private final static String ICONTROL_PROPERTIES_MESSAGE = + "You should probably modify icontrol.properties' classRoot to where comiled (uninstrumented) classes go."; + + /** + * \ on windows, / on linux/unix + */ + private final static String ps = System.getProperty( "path.separator" ); + + /** + * compiler to use for instrumenation + */ + private String icCompiler = "javac"; + + /** + * temporary file with file names of all java files to be instrumented + */ + private File targets = null; + + /** + * will be set to true if any of the sourca files are newer than the + * instrumented files + */ + private boolean dirty = false; + + /** + * set to true if the iContract jar is missing + */ + private boolean iContractMissing = false; + + /** + * source file root + */ + private File srcDir = null; + + /** + * instrumentation src root + */ + private File instrumentDir = null; + + /** + * instrumentation build root + */ + private File buildDir = null; + + /** + * repository src root + */ + private File repositoryDir = null; + + /** + * repository build root + */ + private File repBuildDir = null; + + /** + * classpath + */ + private Path classpath = null; + + /** + * The class of the Throwable to be thrown on failed assertions + */ + private String failThrowable = "java.lang.Error"; + + /** + * The -v option + */ + private String verbosity = "error*"; + + /** + * The -q option + */ + private boolean quiet = false; + + /** + * Indicates whether or not to use internal compilation + */ + private boolean internalcompilation = false; + + /** + * The -m option + */ + private File controlFile = null; + + /** + * Indicates whether or not to instrument for preconditions + */ + private boolean pre = true; + private boolean preModified = false; + + /** + * Indicates whether or not to instrument for postconditions + */ + private boolean post = true; + private boolean postModified = false; + + /** + * Indicates whether or not to instrument for invariants + */ + private boolean invariant = true; + private boolean invariantModified = false; + + /** + * Indicates whether or not to instrument all files regardless of timestamp + */ + // can't be explicitly set, is set if control file exists and is newer than any source file + private boolean instrumentall = false; + + /** + * Indicates the name of a properties file (intentionally for iControl) + * where the classpath property should be updated. + */ + private boolean updateIcontrol = false; + + /** + * Regular compilation class root + */ + private File classDir = null; + + /** + * Sets the build directory for instrumented classes + * + * @param buildDir the build directory + */ + public void setBuilddir( File buildDir ) + { + this.buildDir = buildDir; + } + + /** + * Sets the class directory (uninstrumented classes) + * + * @param classDir The new Classdir value + */ + public void setClassdir( File classDir ) + { + this.classDir = classDir; + } + + /** + * Sets the classpath to be used for invocation of iContract. + * + * @param path The new Classpath value + * @path the classpath + */ + public void setClasspath( Path path ) + { + createClasspath().append( path ); + } + + /** + * Adds a reference to a classpath defined elsewhere. + * + * @param reference referenced classpath + */ + public void setClasspathRef( Reference reference ) + { + createClasspath().setRefid( reference ); + } + + /** + * Sets the control file to pass to iContract. + * + * @param controlFile the control file + */ + public void setControlfile( File controlFile ) + { + if( !controlFile.exists() ) + { + log( "WARNING: Control file " + controlFile.getAbsolutePath() + " doesn't exist. iContract will be run without control file." ); + } + this.controlFile = controlFile; + } + + /** + * Sets the Throwable (Exception) to be thrown on assertion violation + * + * @param clazz the fully qualified Throwable class name + */ + public void setFailthrowable( String clazz ) + { + this.failThrowable = clazz; + } + + /** + * Sets the instrumentation directory + * + * @param instrumentDir the source directory + */ + public void setInstrumentdir( File instrumentDir ) + { + this.instrumentDir = instrumentDir; + if( this.buildDir == null ) + { + setBuilddir( instrumentDir ); + } + } + + /** + * Turns on/off invariant instrumentation + * + * @param invariant true turns it on + */ + public void setInvariant( boolean invariant ) + { + this.invariant = invariant; + invariantModified = true; + } + + /** + * Turns on/off postcondition instrumentation + * + * @param post true turns it on + */ + public void setPost( boolean post ) + { + this.post = post; + postModified = true; + } + + /** + * Turns on/off precondition instrumentation + * + * @param pre true turns it on + */ + public void setPre( boolean pre ) + { + this.pre = pre; + preModified = true; + } + + /** + * Tells iContract to be quiet. + * + * @param quiet true if iContract should be quiet. + */ + public void setQuiet( boolean quiet ) + { + this.quiet = quiet; + } + + /** + * Sets the build directory for instrumented classes + * + * @param repBuildDir The new Repbuilddir value + */ + public void setRepbuilddir( File repBuildDir ) + { + this.repBuildDir = repBuildDir; + } + + /** + * Sets the build directory for repository classes + * + * @param repositoryDir the source directory + */ + public void setRepositorydir( File repositoryDir ) + { + this.repositoryDir = repositoryDir; + if( this.repBuildDir == null ) + { + setRepbuilddir( repositoryDir ); + } + } + + /** + * Sets the source directory + * + * @param srcDir the source directory + */ + public void setSrcdir( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * Sets the name of the file where targets will be written. That is the file + * that tells iContract what files to process. + * + * @param targets the targets file name + */ + public void setTargets( File targets ) + { + this.targets = targets; + } + + /** + * Decides whether or not to update iControl properties file + * + * @param updateIcontrol true if iControl properties file should be updated + */ + public void setUpdateicontrol( boolean updateIcontrol ) + { + this.updateIcontrol = updateIcontrol; + } + + /** + * Sets the verbosity level of iContract. Any combination of + * error*,warning*,note*,info*,progress*,debug* (comma separated) can be + * used. Defaults to error*,warning* + * + * @param verbosity verbosity level + */ + public void setVerbosity( String verbosity ) + { + this.verbosity = verbosity; + } + + /** + * Creates a nested classpath element + * + * @return the nested classpath element + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( getProject() ); + } + return classpath; + } + + /** + * Executes the task + * + * @exception BuildException if the instrumentation fails + */ + public void execute() + throws BuildException + { + preconditions(); + scan(); + if( dirty ) + { + + // turn off assertions if we're using controlfile, unless they are not explicitly set. + boolean useControlFile = ( controlFile != null ) && controlFile.exists(); + if( useControlFile && !preModified ) + { + pre = false; + } + if( useControlFile && !postModified ) + { + post = false; + } + if( useControlFile && !invariantModified ) + { + invariant = false; + } + // issue warning if pre,post or invariant is used together with controlfile + if( ( pre || post || invariant ) && controlFile != null ) + { + log( "WARNING: specifying pre,post or invariant will override control file settings" ); + } + + + // We want to be notified if iContract jar is missing. This makes life easier for the user + // who didn't understand that iContract is a separate library (duh!) + getProject().addBuildListener( new IContractPresenceDetector() ); + + // Prepare the directories for iContract. iContract will make them if they + // don't exist, but for some reason I don't know, it will complain about the REP files + // afterwards + Mkdir mkdir = ( Mkdir )project.createTask( "mkdir" ); + mkdir.setDir( instrumentDir ); + mkdir.execute(); + mkdir.setDir( buildDir ); + mkdir.execute(); + mkdir.setDir( repositoryDir ); + mkdir.execute(); + + // Set the classpath that is needed for regular Javac compilation + Path baseClasspath = createClasspath(); + + // Might need to add the core classes if we're not using Sun's Javac (like Jikes) + String compiler = project.getProperty( "build.compiler" ); + ClasspathHelper classpathHelper = new ClasspathHelper( compiler ); + classpathHelper.modify( baseClasspath ); + + // Create the classpath required to compile the sourcefiles BEFORE instrumentation + Path beforeInstrumentationClasspath = ( ( Path )baseClasspath.clone() ); + beforeInstrumentationClasspath.append( new Path( getProject(), srcDir.getAbsolutePath() ) ); + + // Create the classpath required to compile the sourcefiles AFTER instrumentation + Path afterInstrumentationClasspath = ( ( Path )baseClasspath.clone() ); + afterInstrumentationClasspath.append( new Path( getProject(), instrumentDir.getAbsolutePath() ) ); + afterInstrumentationClasspath.append( new Path( getProject(), repositoryDir.getAbsolutePath() ) ); + afterInstrumentationClasspath.append( new Path( getProject(), srcDir.getAbsolutePath() ) ); + afterInstrumentationClasspath.append( new Path( getProject(), buildDir.getAbsolutePath() ) ); + + // Create the classpath required to automatically compile the repository files + Path repositoryClasspath = ( ( Path )baseClasspath.clone() ); + repositoryClasspath.append( new Path( getProject(), instrumentDir.getAbsolutePath() ) ); + repositoryClasspath.append( new Path( getProject(), srcDir.getAbsolutePath() ) ); + repositoryClasspath.append( new Path( getProject(), repositoryDir.getAbsolutePath() ) ); + repositoryClasspath.append( new Path( getProject(), buildDir.getAbsolutePath() ) ); + + // Create the classpath required for iContract itself + Path iContractClasspath = ( ( Path )baseClasspath.clone() ); + iContractClasspath.append( new Path( getProject(), System.getProperty( "java.home" ) + File.separator + ".." + File.separator + "lib" + File.separator + "tools.jar" ) ); + iContractClasspath.append( new Path( getProject(), srcDir.getAbsolutePath() ) ); + iContractClasspath.append( new Path( getProject(), repositoryDir.getAbsolutePath() ) ); + iContractClasspath.append( new Path( getProject(), instrumentDir.getAbsolutePath() ) ); + iContractClasspath.append( new Path( getProject(), buildDir.getAbsolutePath() ) ); + + // Create a forked java process + Java iContract = ( Java )project.createTask( "java" ); + iContract.setTaskName( getTaskName() ); + iContract.setFork( true ); + iContract.setClassname( "com.reliablesystems.iContract.Tool" ); + iContract.setClasspath( iContractClasspath ); + + // Build the arguments to iContract + StringBuffer args = new StringBuffer(); + args.append( directiveString() ); + args.append( "-v" ).append( verbosity ).append( " " ); + args.append( "-b" ).append( "\"" ).append( icCompiler ).append( " -classpath " ).append( beforeInstrumentationClasspath ).append( "\" " ); + args.append( "-c" ).append( "\"" ).append( icCompiler ).append( " -classpath " ).append( afterInstrumentationClasspath ).append( " -d " ).append( buildDir ).append( "\" " ); + args.append( "-n" ).append( "\"" ).append( icCompiler ).append( " -classpath " ).append( repositoryClasspath ).append( "\" " ); + args.append( "-d" ).append( failThrowable ).append( " " ); + args.append( "-o" ).append( instrumentDir ).append( File.separator ).append( "@p" ).append( File.separator ).append( "@f.@e " ); + args.append( "-k" ).append( repositoryDir ).append( File.separator ).append( "@p " ); + args.append( quiet ? "-q " : "" ); + args.append( instrumentall ? "-a " : "" );// reinstrument everything if controlFile exists and is newer than any class + args.append( "@" ).append( targets.getAbsolutePath() ); + iContract.createArg().setLine( args.toString() ); + +//System.out.println( "JAVA -classpath " + iContractClasspath + " com.reliablesystems.iContract.Tool " + args.toString() ); + + // update iControlProperties if it's set. + if( updateIcontrol ) + { + Properties iControlProps = new Properties(); + try + {// to read existing propertiesfile + iControlProps.load( new FileInputStream( "icontrol.properties" ) ); + } + catch( IOException e ) + { + log( "File icontrol.properties not found. That's ok. Writing a default one." ); + } + iControlProps.setProperty( "sourceRoot", srcDir.getAbsolutePath() ); + iControlProps.setProperty( "classRoot", classDir.getAbsolutePath() ); + iControlProps.setProperty( "classpath", afterInstrumentationClasspath.toString() ); + iControlProps.setProperty( "controlFile", controlFile.getAbsolutePath() ); + iControlProps.setProperty( "targetsFile", targets.getAbsolutePath() ); + + try + {// to read existing propertiesfile + iControlProps.store( new FileOutputStream( "icontrol.properties" ), ICONTROL_PROPERTIES_HEADER ); + log( "Updated icontrol.properties" ); + } + catch( IOException e ) + { + log( "Couldn't write icontrol.properties." ); + } + } + + // do it! + int result = iContract.executeJava(); + if( result != 0 ) + { + if( iContractMissing ) + { + log( "iContract can't be found on your classpath. Your classpath is:" ); + log( classpath.toString() ); + log( "If you don't have the iContract jar, go get it at http://www.reliable-systems.com/tools/" ); + } + throw new BuildException( "iContract instrumentation failed. Code=" + result ); + } + + } + else + {// not dirty + //log( "Nothing to do. Everything up to date." ); + } + } + + + /** + * Creates the -m option based on the values of controlFile, pre, post and + * invariant. + * + * @return Description of the Returned Value + */ + private final String directiveString() + { + StringBuffer sb = new StringBuffer(); + boolean comma = false; + + boolean useControlFile = ( controlFile != null ) && controlFile.exists(); + if( useControlFile || pre || post || invariant ) + { + sb.append( "-m" ); + } + if( useControlFile ) + { + sb.append( "@" ).append( controlFile ); + comma = true; + } + if( pre ) + { + if( comma ) + { + sb.append( "," ); + } + sb.append( "pre" ); + comma = true; + } + if( post ) + { + if( comma ) + { + sb.append( "," ); + } + sb.append( "post" ); + comma = true; + } + if( invariant ) + { + if( comma ) + { + sb.append( "," ); + } + sb.append( "inv" ); + } + sb.append( " " ); + return sb.toString(); + } + + /** + * Checks that the required attributes are set. + * + * @exception BuildException Description of Exception + */ + private void preconditions() + throws BuildException + { + if( srcDir == null ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir \"" + srcDir.getPath() + "\" does not exist!", location ); + } + if( instrumentDir == null ) + { + throw new BuildException( "instrumentdir attribute must be set!", location ); + } + if( repositoryDir == null ) + { + throw new BuildException( "repositorydir attribute must be set!", location ); + } + if( updateIcontrol == true && classDir == null ) + { + throw new BuildException( "classdir attribute must be specified when updateicontrol=true!", location ); + } + if( updateIcontrol == true && controlFile == null ) + { + throw new BuildException( "controlfile attribute must be specified when updateicontrol=true!", location ); + } + } + + /** + * Verifies whether any of the source files have changed. Done by comparing + * date of source/class files. The whole lot is "dirty" if at least one + * source file or the control file is newer than the instrumented files. If + * not dirty, iContract will not be executed.
      + * Also creates a temporary file with a list of the source files, that will + * be deleted upon exit. + * + * @exception BuildException Description of Exception + */ + private void scan() + throws BuildException + { + long now = ( new Date() ).getTime(); + + DirectoryScanner ds = null; + + ds = getDirectoryScanner( srcDir ); + String[] files = ds.getIncludedFiles(); + + FileOutputStream targetOutputStream = null; + PrintStream targetPrinter = null; + boolean writeTargets = false; + try + { + if( targets == null ) + { + targets = new File( "targets" ); + log( "Warning: targets file not specified. generating file: " + targets.getName() ); + writeTargets = true; + } + else if( !targets.exists() ) + { + log( "Specified targets file doesn't exist. generating file: " + targets.getName() ); + writeTargets = true; + } + if( writeTargets ) + { + log( "You should consider using iControl to create a target file." ); + targetOutputStream = new FileOutputStream( targets ); + targetPrinter = new PrintStream( targetOutputStream ); + } + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".java" ) ) + { + // print the target, while we're at here. (Only if generatetarget=true). + if( targetPrinter != null ) + { + targetPrinter.println( srcFile.getAbsolutePath() ); + } + File classFile = new File( buildDir, files[i].substring( 0, files[i].indexOf( ".java" ) ) + ".class" ); + + if( srcFile.lastModified() > now ) + { + log( "Warning: file modified in the future: " + + files[i], Project.MSG_WARN ); + } + + if( !classFile.exists() || srcFile.lastModified() > classFile.lastModified() ) + { + //log( "Found a file newer than the instrumentDir class file: " + srcFile.getPath() + " newer than " + classFile.getPath() + ". Running iContract again..." ); + dirty = true; + } + } + } + if( targetPrinter != null ) + { + targetPrinter.flush(); + targetPrinter.close(); + } + } + catch( IOException e ) + { + throw new BuildException( "Could not create target file:" + e.getMessage() ); + } + + // also, check controlFile timestamp + long controlFileTime = -1; + try + { + if( controlFile != null ) + { + if( controlFile.exists() && buildDir.exists() ) + { + controlFileTime = controlFile.lastModified(); + ds = getDirectoryScanner( buildDir ); + files = ds.getIncludedFiles(); + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".class" ) ) + { + if( controlFileTime > srcFile.lastModified() ) + { + if( !dirty ) + { + log( "Control file " + controlFile.getAbsolutePath() + " has been updated. Instrumenting all files..." ); + } + dirty = true; + instrumentall = true; + } + } + } + } + } + } + catch( Throwable t ) + { + throw new BuildException( "Got an interesting exception:" + t.getMessage() ); + } + } + + /** + * This class is a helper to set correct classpath for other compilers, like + * Jikes. It reuses the logic from DefaultCompilerAdapter, which is + * protected, so we have to subclass it. + * + * @author RT + */ + private class ClasspathHelper extends DefaultCompilerAdapter + { + private final String compiler; + + public ClasspathHelper( String compiler ) + { + super(); + this.compiler = compiler; + } + + // dummy implementation. Never called + public void setJavac( Javac javac ) { } + + public boolean execute() + { + return true; + } + + // make it public + public void modify( Path path ) + { + // depending on what compiler to use, set the includeJavaRuntime flag + if( "jikes".equals( compiler ) ) + { + icCompiler = compiler; + includeJavaRuntime = true; + path.append( getCompileClasspath() ); + } + } + } + + /** + * BuildListener that sets the iContractMissing flag to true if a message + * about missing iContract is missing. Used to indicate a more verbose error + * to the user, with advice about how to solve the problem + * + * @author RT + */ + private class IContractPresenceDetector implements BuildListener + { + public void buildFinished( BuildEvent event ) { } + + public void buildStarted( BuildEvent event ) { } + + public void messageLogged( BuildEvent event ) + { + if( "java.lang.NoClassDefFoundError: com/reliablesystems/iContract/Tool".equals( event.getMessage() ) ) + { + iContractMissing = true; + } + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) { } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Javah.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Javah.java new file mode 100644 index 000000000..76d48b24c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Javah.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Task to generate JNI header files using javah. This task can take the + * following arguments: + *
        + *
      • classname - the fully-qualified name of a class
      • + *
      • outputFile - Concatenates the resulting header or source files for all + * the classes listed into this file
      • + *
      • destdir - Sets the directory where javah saves the header files or the + * stub files
      • + *
      • classpath
      • + *
      • bootclasspath
      • + *
      • force - Specifies that output files should always be written (JDK1.2 + * only)
      • + *
      • old - Specifies that old JDK1.0-style header files should be generated + * (otherwise output file contain JNI-style native method function prototypes) + * (JDK1.2 only)
      • + *
      • stubs - generate C declarations from the Java object file (used with + * old)
      • + *
      • verbose - causes javah to print a message to stdout concerning the + * status of the generated files
      • + *
      • extdirs - Override location of installed extensions
      • + *
      + * Of these arguments, either outputFile or destdir is required, + * but not both. More than one classname may be specified, using a + * comma-separated list or by using <class name="xxx"> + * elements within the task.

      + * + * When this task executes, it will generate C header and source files that are + * needed to implement native methods. + * + * @author Rick Beton + * richard.beton@physics.org + */ + +public class Javah extends Task +{ + + private final static String FAIL_MSG = "Compile failed, messages should have been provided."; + //private Path extdirs; + private static String lSep = System.getProperty( "line.separator" ); + + private Vector classes = new Vector( 2 ); + private Path classpath = null; + private File outputFile = null; + private boolean verbose = false; + private boolean force = false; + private boolean old = false; + private boolean stubs = false; + private Path bootclasspath; + private String cls; + private File destDir; + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new BootClasspathRef value + */ + public void setBootClasspathRef( Reference r ) + { + createBootclasspath().setRefid( r ); + } + + public void setBootclasspath( Path src ) + { + if( bootclasspath == null ) + { + bootclasspath = src; + } + else + { + bootclasspath.append( src ); + } + } + + public void setClass( String cls ) + { + this.cls = cls; + } + + public void setClasspath( Path src ) + { + if( classpath == null ) + { + classpath = src; + } + else + { + classpath.append( src ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Set the destination directory into which the Java source files should be + * compiled. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Set the force-write flag. + * + * @param force The new Force value + */ + public void setForce( boolean force ) + { + this.force = force; + } + + /** + * Set the old flag. + * + * @param old The new Old value + */ + public void setOld( boolean old ) + { + this.old = old; + } + + ///** + // * Sets the extension directories that will be used during the + // * compilation. + // */ + //public void setExtdirs(Path extdirs) { + // if (this.extdirs == null) { + // this.extdirs = extdirs; + // } else { + // this.extdirs.append(extdirs); + // } + //} + + ///** + // * Maybe creates a nested classpath element. + // */ + //public Path createExtdirs() { + // if (extdirs == null) { + // extdirs = new Path(project); + // } + // return extdirs.createPath(); + //} + + /** + * Set the output file name. + * + * @param outputFile The new OutputFile value + */ + public void setOutputFile( File outputFile ) + { + this.outputFile = outputFile; + } + + /** + * Set the stubs flag. + * + * @param stubs The new Stubs value + */ + public void setStubs( boolean stubs ) + { + this.stubs = stubs; + } + + /** + * Set the verbose flag. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + this.verbose = verbose; + } + + public Path createBootclasspath() + { + if( bootclasspath == null ) + { + bootclasspath = new Path( project ); + } + return bootclasspath.createPath(); + } + + public ClassArgument createClass() + { + ClassArgument ga = new ClassArgument(); + classes.addElement( ga ); + return ga; + } + + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir + + if( ( cls == null ) && ( classes.size() == 0 ) ) + { + throw new BuildException( "class attribute must be set!", location ); + } + + if( ( cls != null ) && ( classes.size() > 0 ) ) + { + throw new BuildException( "set class attribute or class element, not both.", location ); + } + + if( destDir != null ) + { + if( !destDir.isDirectory() ) + { + throw new BuildException( "destination directory \"" + destDir + "\" does not exist or is not a directory", location ); + } + if( outputFile != null ) + { + throw new BuildException( "destdir and outputFile are mutually exclusive", location ); + } + } + + if( classpath == null ) + { + classpath = Path.systemClasspath; + } + + String compiler = project.getProperty( "build.compiler" ); + if( compiler == null ) + { + if( Project.getJavaVersion() != Project.JAVA_1_1 && + Project.getJavaVersion() != Project.JAVA_1_2 ) + { + compiler = "modern"; + } + else + { + compiler = "classic"; + } + } + + doClassicCompile(); + } + + /** + * Logs the compilation parameters, adds the files to compile and logs the + * &qout;niceSourceList" + * + * @param cmd Description of Parameter + */ + protected void logAndAddFilesToCompile( Commandline cmd ) + { + int n = 0; + log( "Compilation args: " + cmd.toString(), + Project.MSG_VERBOSE ); + + StringBuffer niceClassList = new StringBuffer(); + if( cls != null ) + { + StringTokenizer tok = new StringTokenizer( cls, ",", false ); + while( tok.hasMoreTokens() ) + { + String aClass = tok.nextToken().trim(); + cmd.createArgument().setValue( aClass ); + niceClassList.append( " " + aClass + lSep ); + n++; + } + } + + Enumeration enum = classes.elements(); + while( enum.hasMoreElements() ) + { + ClassArgument arg = ( ClassArgument )enum.nextElement(); + String aClass = arg.getName(); + cmd.createArgument().setValue( aClass ); + niceClassList.append( " " + aClass + lSep ); + n++; + } + + StringBuffer prefix = new StringBuffer( "Class" ); + if( n > 1 ) + { + prefix.append( "es" ); + } + prefix.append( " to be compiled:" ); + prefix.append( lSep ); + + log( prefix.toString() + niceClassList.toString(), Project.MSG_VERBOSE ); + } + + /** + * Does the command line argument processing common to classic and modern. + * + * @return Description of the Returned Value + */ + private Commandline setupJavahCommand() + { + Commandline cmd = new Commandline(); + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + if( outputFile != null ) + { + cmd.createArgument().setValue( "-o" ); + cmd.createArgument().setFile( outputFile ); + } + + if( classpath != null ) + { + cmd.createArgument().setValue( "-classpath" ); + cmd.createArgument().setPath( classpath ); + } + + // JDK1.1 is rather simpler than JDK1.2 + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + if( verbose ) + { + cmd.createArgument().setValue( "-v" ); + } + } + else + { + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + if( old ) + { + cmd.createArgument().setValue( "-old" ); + } + if( force ) + { + cmd.createArgument().setValue( "-force" ); + } + } + + if( stubs ) + { + if( !old ) + { + throw new BuildException( "stubs only available in old mode.", location ); + } + cmd.createArgument().setValue( "-stubs" ); + } + if( bootclasspath != null ) + { + cmd.createArgument().setValue( "-bootclasspath" ); + cmd.createArgument().setPath( bootclasspath ); + } + + logAndAddFilesToCompile( cmd ); + return cmd; + } + + // XXX + // we need a way to not use the current classpath. + + /** + * Peforms a compile using the classic compiler that shipped with JDK 1.1 + * and 1.2. + * + * @exception BuildException Description of Exception + */ + + private void doClassicCompile() + throws BuildException + { + Commandline cmd = setupJavahCommand(); + + // Use reflection to be able to build on all JDKs + /* + * / provide the compiler a different message sink - namely our own + * sun.tools.javac.Main compiler = + * new sun.tools.javac.Main(new LogOutputStream(this, Project.MSG_WARN), "javac"); + * if (!compiler.compile(cmd.getArguments())) { + * throw new BuildException("Compile failed"); + * } + */ + try + { + // Javac uses logstr to change the output stream and calls + // the constructor's invoke method to create a compiler instance + // dynamically. However, javah has a different interface and this + // makes it harder, so here's a simple alternative. + //------------------------------------------------------------------ + com.sun.tools.javah.Main main = new com.sun.tools.javah.Main( cmd.getArguments() ); + main.run(); + } + //catch (ClassNotFoundException ex) { + // throw new BuildException("Cannot use javah because it is not available"+ + // " A common solution is to set the environment variable"+ + // " JAVA_HOME to your jdk directory.", location); + //} + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting javah: ", ex, location ); + } + } + } + + public class ClassArgument + { + private String name; + + public ClassArgument() { } + + public void setName( String name ) + { + this.name = name; + log( "ClassArgument.name=" + name ); + } + + public String getName() + { + return name; + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ManifestFile.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ManifestFile.java new file mode 100644 index 000000000..4ef6878db --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ManifestFile.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.ListIterator; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +/** + * Task for creating a manifest file for a jar archiv. use:

      + *   
      + *   
      + *     
      + *         
      + *       
      + * + * @author Thomas Kerle + * @version 1.0 2001-10-11 + */ +public class ManifestFile extends Task +{ + + private final static String newLine = System.getProperty( "line.separator" ); + private final static String keyValueSeparator = ":"; + private final static String UPDATE_ = "update"; + private final static String REPLACEALL_ = "replaceAll"; + private EntryContainer container; + private String currentMethod; + private Vector entries; + + private File manifestFile; + + public ManifestFile() + { + entries = new Vector(); + container = new EntryContainer(); + } + + /** + * Setter for the file attribute + * + * @param f The new File value + */ + public void setFile( File f ) + { + manifestFile = f; + } + + /** + * Setter for the method attribute (update/replaceAll) + * + * @param method Method to set task + */ + public void setMethod( String method ) + { + currentMethod = method.toUpperCase(); + } + + /** + * creating entries by Ant + * + * @return Description of the Returned Value + */ + public Entry createEntry() + { + Entry entry = new Entry(); + entries.addElement( entry ); + return entry; + } + + /** + * execute task + * + * @exception BuildException : Failure in building + */ + public void execute() + throws BuildException + { + checkParameters(); + if( isUpdate( currentMethod ) ) + readFile(); + + executeOperation(); + writeFile(); + } + + private StringTokenizer getLineTokens( StringBuffer buffer ) + { + String manifests = buffer.toString(); + StringTokenizer strTokens = new StringTokenizer( manifests, newLine ); + return strTokens; + } + + private boolean isReplaceAll( String method ) + { + return method.equals( REPLACEALL_.toUpperCase() ); + } + + + private boolean isUpdate( String method ) + { + return method.equals( UPDATE_.toUpperCase() ); + } + + private void addLine( String line ) + { + Entry entry = new Entry(); + + entry.setValue( line ); + entry.addTo( container ); + } + + + private StringBuffer buildBuffer() + { + StringBuffer buffer = new StringBuffer(); + + ListIterator iterator = container.elements(); + + while( iterator.hasNext() ) + { + Entry entry = ( Entry )iterator.next(); + + String key = ( String )entry.getKey(); + String value = ( String )entry.getValue(); + String entry_string = key + keyValueSeparator + value; + + buffer.append( entry_string + this.newLine ); + } + + return buffer; + } + + private boolean checkParam( String param ) + { + return !( ( param == null ) || ( param.equals( "null" ) ) ); + } + + private boolean checkParam( File param ) + { + return !( param == null ); + } + + private void checkParameters() + throws BuildException + { + if( !checkParam( manifestFile ) ) + { + throw new BuildException( "file token must not be null.", location ); + } + } + + /** + * adding entries to a container + * + * @exception BuildException + */ + private void executeOperation() + throws BuildException + { + Enumeration enum = entries.elements(); + + while( enum.hasMoreElements() ) + { + Entry entry = ( Entry )enum.nextElement(); + entry.addTo( container ); + } + } + + private void readFile() + throws BuildException + { + + if( manifestFile.exists() ) + { + this.log( "update existing manifest file " + manifestFile.getAbsolutePath() ); + + if( container != null ) + { + try + { + FileInputStream fis = new FileInputStream( manifestFile ); + + int c; + StringBuffer buffer = new StringBuffer( "" ); + boolean stop = false; + while( !stop ) + { + c = fis.read(); + if( c == -1 ) + { + stop = true; + } + else + buffer.append( ( char )c ); + } + fis.close(); + StringTokenizer lineTokens = getLineTokens( buffer ); + while( lineTokens.hasMoreElements() ) + { + String currentLine = ( String )lineTokens.nextElement(); + addLine( currentLine ); + } + } + catch( FileNotFoundException fnfe ) + { + throw new BuildException( "File not found exception " + fnfe.toString() ); + } + catch( IOException ioe ) + { + throw new BuildException( "Unknown input/output exception " + ioe.toString() ); + } + } + } + + } + + + private void writeFile() + throws BuildException + { + try + { + manifestFile.delete(); + log( "Replacing or creating new manifest file " + manifestFile.getAbsolutePath() ); + if( manifestFile.createNewFile() ) + { + FileOutputStream fos = new FileOutputStream( manifestFile ); + + StringBuffer buffer = buildBuffer(); + + int size = buffer.length(); + + for( int i = 0; i < size; i++ ) + { + fos.write( ( char )buffer.charAt( i ) ); + } + + fos.flush(); + fos.close(); + } + else + { + throw new BuildException( "Can't create manifest file" ); + } + + } + catch( IOException ioe ) + { + throw new BuildException( "An input/ouput error occured" + ioe.toString() ); + } + } + + public class Entry implements Comparator + { + //extern format + private String value = null; + + //intern representation + private String val = null; + private String key = null; + + public Entry() { } + + public void setValue( String value ) + { + this.value = new String( value ); + } + + public String getKey() + { + return key; + } + + public String getValue() + { + return val; + } + + public int compare( Object o1, Object o2 ) + { + int result = -1; + + try + { + Entry e1 = ( Entry )o1; + Entry e2 = ( Entry )o2; + + String key_1 = e1.getKey(); + String key_2 = e2.getKey(); + + result = key_1.compareTo( key_2 ); + } + catch( Exception e ) + { + + } + return result; + } + + + public boolean equals( Object obj ) + { + Entry ent = new Entry(); + boolean result = false; + int res = ent.compare( this, ( Entry )obj ); + if( res == 0 ) + result = true; + + return result; + } + + + protected void addTo( EntryContainer container ) + throws BuildException + { + checkFormat(); + split(); + container.set( this ); + } + + private void checkFormat() + throws BuildException + { + + if( value == null ) + { + throw new BuildException( "no argument for value" ); + } + + StringTokenizer st = new StringTokenizer( value, ManifestFile.keyValueSeparator ); + int size = st.countTokens(); + + if( size < 2 ) + { + throw new BuildException( "value has not the format of a manifest entry" ); + } + } + + private void split() + { + StringTokenizer st = new StringTokenizer( value, ManifestFile.keyValueSeparator ); + key = ( String )st.nextElement(); + val = ( String )st.nextElement(); + } + + } + + public class EntryContainer + { + + private ArrayList list = null; + + public EntryContainer() + { + list = new ArrayList(); + } + + public void set( Entry entry ) + { + + if( list.contains( entry ) ) + { + int index = list.indexOf( entry ); + + list.remove( index ); + list.add( index, entry ); + } + else + { + list.add( entry ); + } + } + + public ListIterator elements() + { + ListIterator iterator = list.listIterator(); + return iterator; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java new file mode 100644 index 000000000..882335c60 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.IdentityMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Convert files from native encodings to ascii. + * + * @author Drew Sudell + * @author Stefan Bodewig + */ +public class Native2Ascii extends MatchingTask +{ + + private boolean reverse = false;// convert from ascii back to native + private String encoding = null;// encoding to convert to/from + private File srcDir = null;// Where to find input files + private File destDir = null;// Where to put output files + private String extension = null;// Extension of output files if different + + private Mapper mapper; + + + /** + * Set the destination dirctory to place converted files into. + * + * @param destDir directory to place output file into. + */ + public void setDest( File destDir ) + { + this.destDir = destDir; + } + + /** + * Set the encoding to translate to/from. If unset, the default encoding for + * the JVM is used. + * + * @param encoding String containing the name of the Native encoding to + * convert from or to. + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Set the extension which converted files should have. If unset, files will + * not be renamed. + * + * @param ext File extension to use for converted files. + */ + public void setExt( String ext ) + { + this.extension = ext; + } + + /** + * Flag the conversion to run in the reverse sense, that is Ascii to Native + * encoding. + * + * @param reverse True if the conversion is to be reversed, otherwise false; + */ + public void setReverse( boolean reverse ) + { + this.reverse = reverse; + } + + /** + * Set the source directory in which to find files to convert. + * + * @param srcDir Direcrory to find input file in. + */ + public void setSrc( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapper != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapper = new Mapper( project ); + return mapper; + } + + public void execute() + throws BuildException + { + + Commandline baseCmd = null;// the common portion of our cmd line + DirectoryScanner scanner = null;// Scanner to find our inputs + String[] files;// list of files to process + + // default srcDir to basedir + if( srcDir == null ) + { + srcDir = project.resolveFile( "." ); + } + + // Require destDir + if( destDir == null ) + { + throw new BuildException( "The dest attribute must be set." ); + } + + // if src and dest dirs are the same, require the extension + // to be set, so we don't stomp every file. One could still + // include a file with the same extension, but .... + if( srcDir.equals( destDir ) && extension == null && mapper == null ) + { + throw new BuildException( "The ext attribute or a mapper must be set if" + + " src and dest dirs are the same." ); + } + + FileNameMapper m = null; + if( mapper == null ) + { + if( extension == null ) + { + m = new IdentityMapper(); + } + else + { + m = new ExtMapper(); + } + } + else + { + m = mapper.getImplementation(); + } + + scanner = getDirectoryScanner( srcDir ); + files = scanner.getIncludedFiles(); + SourceFileScanner sfs = new SourceFileScanner( this ); + files = sfs.restrict( files, srcDir, destDir, m ); + int count = files.length; + if( count == 0 ) + { + return; + } + String message = "Converting " + count + " file" + + ( count != 1 ? "s" : "" ) + " from "; + log( message + srcDir + " to " + destDir ); + for( int i = 0; i < files.length; i++ ) + { + convert( files[i], m.mapFileName( files[i] )[0] ); + } + } + + /** + * Convert a single file. + * + * @param srcName Description of Parameter + * @param destName Description of Parameter + * @exception BuildException Description of Exception + */ + private void convert( String srcName, String destName ) + throws BuildException + { + + Commandline cmd = new Commandline();// Command line to run + File srcFile;// File to convert + File destFile;// where to put the results + + // Set up the basic args (this could be done once, but + // it's cleaner here) + if( reverse ) + { + cmd.createArgument().setValue( "-reverse" ); + } + + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + + // Build the full file names + srcFile = new File( srcDir, srcName ); + destFile = new File( destDir, destName ); + + cmd.createArgument().setFile( srcFile ); + cmd.createArgument().setFile( destFile ); + // Make sure we're not about to clobber something + if( srcFile.equals( destFile ) ) + { + throw new BuildException( "file " + srcFile + + " would overwrite its self" ); + } + + // Make intermediate directories if needed + // XXX JDK 1.1 dosen't have File.getParentFile, + String parentName = destFile.getParent(); + if( parentName != null ) + { + File parentFile = new File( parentName ); + + if( ( !parentFile.exists() ) && ( !parentFile.mkdirs() ) ) + { + throw new BuildException( "cannot create parent directory " + + parentName ); + } + } + + log( "converting " + srcName, Project.MSG_VERBOSE ); + sun.tools.native2ascii.Main n2a + = new sun.tools.native2ascii.Main(); + if( !n2a.convert( cmd.getArguments() ) ) + { + throw new BuildException( "conversion failed" ); + } + } + + private class ExtMapper implements FileNameMapper + { + + public void setFrom( String s ) { } + + public void setTo( String s ) { } + + public String[] mapFileName( String fileName ) + { + int lastDot = fileName.lastIndexOf( '.' ); + if( lastDot >= 0 ) + { + return new String[]{fileName.substring( 0, lastDot ) + extension}; + } + else + { + return new String[]{fileName + extension}; + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/NetRexxC.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/NetRexxC.java new file mode 100644 index 000000000..3b05253a1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/NetRexxC.java @@ -0,0 +1,766 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.Vector; +import netrexx.lang.Rexx; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; + +/** + * Task to compile NetRexx source files. This task can take the following + * arguments: + *
        + *
      • binary
      • + *
      • classpath
      • + *
      • comments
      • + *
      • compile
      • + *
      • console
      • + *
      • crossref
      • + *
      • decimal
      • + *
      • destdir
      • + *
      • diag
      • + *
      • explicit
      • + *
      • format
      • + *
      • keep
      • + *
      • logo
      • + *
      • replace
      • + *
      • savelog
      • + *
      • srcdir
      • + *
      • sourcedir
      • + *
      • strictargs
      • + *
      • strictassign
      • + *
      • strictcase
      • + *
      • strictimport
      • + *
      • symbols
      • + *
      • time
      • + *
      • trace
      • + *
      • utf8
      • + *
      • verbose
      • + *
      + * Of these arguments, the srcdir argument is required.

      + * + * When this task executes, it will recursively scan the srcdir looking for + * NetRexx source files to compile. This task makes its compile decision based + * on timestamp.

      + * + * Before files are compiled they and any other file in the srcdir will be + * copied to the destdir allowing support files to be located properly in the + * classpath. The reason for copying the source files before the compile is that + * NetRexxC has only two destinations for classfiles: + *

        + *
      1. The current directory, and,
      2. + *
      3. The directory the source is in (see sourcedir option) + *
      + * + * + * @author dIon Gillard + * dion@multitask.com.au + */ + +public class NetRexxC extends MatchingTask +{ + private boolean compile = true; + private boolean decimal = true; + private boolean logo = true; + private boolean sourcedir = true; + private String trace = "trace2"; + private String verbose = "verbose3"; + + // other implementation variables + private Vector compileList = new Vector(); + private Hashtable filecopyList = new Hashtable(); + private String oldClasspath = System.getProperty( "java.class.path" ); + + // variables to hold arguments + private boolean binary; + private String classpath; + private boolean comments; + private boolean compact; + private boolean console; + private boolean crossref; + private File destDir; + private boolean diag; + private boolean explicit; + private boolean format; + private boolean java; + private boolean keep; + private boolean replace; + private boolean savelog; + private File srcDir;// ?? Should this be the default for ant? + private boolean strictargs; + private boolean strictassign; + private boolean strictcase; + private boolean strictimport; + private boolean strictprops; + private boolean strictsignal; + private boolean symbols; + private boolean time; + private boolean utf8; + + + /** + * Set whether literals are treated as binary, rather than NetRexx types + * + * @param binary The new Binary value + */ + public void setBinary( boolean binary ) + { + this.binary = binary; + } + + /** + * Set the classpath used for NetRexx compilation + * + * @param classpath The new Classpath value + */ + public void setClasspath( String classpath ) + { + this.classpath = classpath; + } + + /** + * Set whether comments are passed through to the generated java source. + * Valid true values are "on" or "true". Anything else sets the flag to + * false. The default value is false + * + * @param comments The new Comments value + */ + public void setComments( boolean comments ) + { + this.comments = comments; + } + + /** + * Set whether error messages come out in compact or verbose format. Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false + * + * @param compact The new Compact value + */ + public void setCompact( boolean compact ) + { + this.compact = compact; + } + + /** + * Set whether the NetRexx compiler should compile the generated java code + * Valid true values are "on" or "true". Anything else sets the flag to + * false. The default value is true. Setting this flag to false, will + * automatically set the keep flag to true. + * + * @param compile The new Compile value + */ + public void setCompile( boolean compile ) + { + this.compile = compile; + if( !this.compile && !this.keep ) + this.keep = true; + } + + /** + * Set whether or not messages should be displayed on the 'console' Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is true. + * + * @param console The new Console value + */ + public void setConsole( boolean console ) + { + this.console = console; + } + + /** + * Whether variable cross references are generated + * + * @param crossref The new Crossref value + */ + public void setCrossref( boolean crossref ) + { + this.crossref = crossref; + } + + /** + * Set whether decimal arithmetic should be used for the netrexx code. + * Binary arithmetic is used when this flag is turned off. Valid true values + * are "on" or "true". Anything else sets the flag to false. The default + * value is true. + * + * @param decimal The new Decimal value + */ + public void setDecimal( boolean decimal ) + { + this.decimal = decimal; + } + + /** + * Set the destination directory into which the NetRexx source files should + * be copied and then compiled. + * + * @param destDirName The new DestDir value + */ + public void setDestDir( File destDirName ) + { + destDir = destDirName; + } + + /** + * Whether diagnostic information about the compile is generated + * + * @param diag The new Diag value + */ + public void setDiag( boolean diag ) + { + this.diag = diag; + } + + /** + * Sets whether variables must be declared explicitly before use. Valid true + * values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param explicit The new Explicit value + */ + public void setExplicit( boolean explicit ) + { + this.explicit = explicit; + } + + /** + * Whether the generated java code is formatted nicely or left to match + * NetRexx line numbers for call stack debugging + * + * @param format The new Format value + */ + public void setFormat( boolean format ) + { + this.format = format; + } + + /** + * Whether the generated java code is produced Valid true values are "on" or + * "true". Anything else sets the flag to false. The default value is false. + * + * @param java The new Java value + */ + public void setJava( boolean java ) + { + this.java = java; + } + + + /** + * Sets whether the generated java source file should be kept after + * compilation. The generated files will have an extension of .java.keep, + * not .java Valid true values are "on" or "true". Anything else sets + * the flag to false. The default value is false. + * + * @param keep The new Keep value + */ + public void setKeep( boolean keep ) + { + this.keep = keep; + } + + /** + * Whether the compiler text logo is displayed when compiling + * + * @param logo The new Logo value + */ + public void setLogo( boolean logo ) + { + this.logo = logo; + } + + /** + * Whether the generated .java file should be replaced when compiling Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param replace The new Replace value + */ + public void setReplace( boolean replace ) + { + this.replace = replace; + } + + /** + * Sets whether the compiler messages will be written to NetRexxC.log as + * well as to the console Valid true values are "on" or "true". Anything + * else sets the flag to false. The default value is false. + * + * @param savelog The new Savelog value + */ + public void setSavelog( boolean savelog ) + { + this.savelog = savelog; + } + + /** + * Tells the NetRexx compiler to store the class files in the same directory + * as the source files. The alternative is the working directory Valid true + * values are "on" or "true". Anything else sets the flag to false. The + * default value is true. + * + * @param sourcedir The new Sourcedir value + */ + public void setSourcedir( boolean sourcedir ) + { + this.sourcedir = sourcedir; + } + + /** + * Set the source dir to find the source Java files. + * + * @param srcDirName The new SrcDir value + */ + public void setSrcDir( File srcDirName ) + { + srcDir = srcDirName; + } + + /** + * Tells the NetRexx compiler that method calls always need parentheses, + * even if no arguments are needed, e.g. aStringVar.getBytes + * vs. aStringVar.getBytes() Valid true values are "on" or + * "true". Anything else sets the flag to false. The default value is false. + * + * @param strictargs The new Strictargs value + */ + public void setStrictargs( boolean strictargs ) + { + this.strictargs = strictargs; + } + + /** + * Tells the NetRexx compile that assignments must match exactly on type + * + * @param strictassign The new Strictassign value + */ + public void setStrictassign( boolean strictassign ) + { + this.strictassign = strictassign; + } + + /** + * Specifies whether the NetRexx compiler should be case sensitive or not + * + * @param strictcase The new Strictcase value + */ + public void setStrictcase( boolean strictcase ) + { + this.strictcase = strictcase; + } + + /** + * Sets whether classes need to be imported explicitly using an import + * statement. By default the NetRexx compiler will import certain packages + * automatically Valid true values are "on" or "true". Anything else sets + * the flag to false. The default value is false. + * + * @param strictimport The new Strictimport value + */ + public void setStrictimport( boolean strictimport ) + { + this.strictimport = strictimport; + } + + /** + * Sets whether local properties need to be qualified explicitly using + * this Valid true values are "on" or "true". Anything else + * sets the flag to false. The default value is false. + * + * @param strictprops The new Strictprops value + */ + public void setStrictprops( boolean strictprops ) + { + this.strictprops = strictprops; + } + + + /** + * Whether the compiler should force catching of exceptions by explicitly + * named types + * + * @param strictsignal The new Strictsignal value + */ + public void setStrictsignal( boolean strictsignal ) + { + this.strictsignal = strictsignal; + } + + /** + * Sets whether debug symbols should be generated into the class file Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param symbols The new Symbols value + */ + public void setSymbols( boolean symbols ) + { + this.symbols = symbols; + } + + /** + * Asks the NetRexx compiler to print compilation times to the console Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param time The new Time value + */ + public void setTime( boolean time ) + { + this.time = time; + } + + /** + * Turns on or off tracing and directs the resultant trace output Valid + * values are: "trace", "trace1", "trace2" and "notrace". "trace" and + * "trace2" + * + * @param trace The new Trace value + */ + public void setTrace( String trace ) + { + if( trace.equalsIgnoreCase( "trace" ) + || trace.equalsIgnoreCase( "trace1" ) + || trace.equalsIgnoreCase( "trace2" ) + || trace.equalsIgnoreCase( "notrace" ) ) + { + this.trace = trace; + } + else + { + throw new BuildException( "Unknown trace value specified: '" + trace + "'" ); + } + } + + /** + * Tells the NetRexx compiler that the source is in UTF8 Valid true values + * are "on" or "true". Anything else sets the flag to false. The default + * value is false. + * + * @param utf8 The new Utf8 value + */ + public void setUtf8( boolean utf8 ) + { + this.utf8 = utf8; + } + + /** + * Whether lots of warnings and error messages should be generated + * + * @param verbose The new Verbose value + */ + public void setVerbose( String verbose ) + { + this.verbose = verbose; + } + + /** + * Executes the task, i.e. does the actual compiler call + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + + // first off, make sure that we've got a srcdir and destdir + if( srcDir == null || destDir == null ) + { + throw new BuildException( "srcDir and destDir attributes must be set!" ); + } + + // scan source and dest dirs to build up both copy lists and + // compile lists + // scanDir(srcDir, destDir); + DirectoryScanner ds = getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + + scanDir( srcDir, destDir, files ); + + // copy the source and support files + copyFilesToDestination(); + + // compile the source files + if( compileList.size() > 0 ) + { + log( "Compiling " + compileList.size() + " source file" + + ( compileList.size() == 1 ? "" : "s" ) + + " to " + destDir ); + doNetRexxCompile(); + } + } + + /** + * Builds the compilation classpath. + * + * @return The CompileClasspath value + */ + private String getCompileClasspath() + { + StringBuffer classpath = new StringBuffer(); + + // add dest dir to classpath so that previously compiled and + // untouched classes are on classpath + classpath.append( destDir.getAbsolutePath() ); + + // add our classpath to the mix + if( this.classpath != null ) + { + addExistingToClasspath( classpath, this.classpath ); + } + + // add the system classpath + // addExistingToClasspath(classpath,System.getProperty("java.class.path")); + return classpath.toString(); + } + + /** + * This + * + * @return The CompileOptionsAsArray value + */ + private String[] getCompileOptionsAsArray() + { + Vector options = new Vector(); + options.addElement( binary ? "-binary" : "-nobinary" ); + options.addElement( comments ? "-comments" : "-nocomments" ); + options.addElement( compile ? "-compile" : "-nocompile" ); + options.addElement( compact ? "-compact" : "-nocompact" ); + options.addElement( console ? "-console" : "-noconsole" ); + options.addElement( crossref ? "-crossref" : "-nocrossref" ); + options.addElement( decimal ? "-decimal" : "-nodecimal" ); + options.addElement( diag ? "-diag" : "-nodiag" ); + options.addElement( explicit ? "-explicit" : "-noexplicit" ); + options.addElement( format ? "-format" : "-noformat" ); + options.addElement( keep ? "-keep" : "-nokeep" ); + options.addElement( logo ? "-logo" : "-nologo" ); + options.addElement( replace ? "-replace" : "-noreplace" ); + options.addElement( savelog ? "-savelog" : "-nosavelog" ); + options.addElement( sourcedir ? "-sourcedir" : "-nosourcedir" ); + options.addElement( strictargs ? "-strictargs" : "-nostrictargs" ); + options.addElement( strictassign ? "-strictassign" : "-nostrictassign" ); + options.addElement( strictcase ? "-strictcase" : "-nostrictcase" ); + options.addElement( strictimport ? "-strictimport" : "-nostrictimport" ); + options.addElement( strictprops ? "-strictprops" : "-nostrictprops" ); + options.addElement( strictsignal ? "-strictsignal" : "-nostrictsignal" ); + options.addElement( symbols ? "-symbols" : "-nosymbols" ); + options.addElement( time ? "-time" : "-notime" ); + options.addElement( "-" + trace ); + options.addElement( utf8 ? "-utf8" : "-noutf8" ); + options.addElement( "-" + verbose ); + String[] results = new String[options.size()]; + options.copyInto( results ); + return results; + } + + /** + * Takes a classpath-like string, and adds each element of this string to a + * new classpath, if the components exist. Components that don't exist, + * aren't added. We do this, because jikes issues warnings for non-existant + * files/dirs in his classpath, and these warnings are pretty annoying. + * + * @param target - target classpath + * @param source - source classpath to get file objects. + */ + private void addExistingToClasspath( StringBuffer target, String source ) + { + StringTokenizer tok = new StringTokenizer( source, + System.getProperty( "path.separator" ), false ); + while( tok.hasMoreTokens() ) + { + File f = project.resolveFile( tok.nextToken() ); + + if( f.exists() ) + { + target.append( File.pathSeparator ); + target.append( f.getAbsolutePath() ); + } + else + { + log( "Dropping from classpath: " + + f.getAbsolutePath(), Project.MSG_VERBOSE ); + } + } + + } + + /** + * Copy eligible files from the srcDir to destDir + */ + private void copyFilesToDestination() + { + if( filecopyList.size() > 0 ) + { + log( "Copying " + filecopyList.size() + " file" + + ( filecopyList.size() == 1 ? "" : "s" ) + + " to " + destDir.getAbsolutePath() ); + Enumeration enum = filecopyList.keys(); + while( enum.hasMoreElements() ) + { + String fromFile = ( String )enum.nextElement(); + String toFile = ( String )filecopyList.get( fromFile ); + try + { + project.copyFile( fromFile, toFile ); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + } + } + } + + /** + * Peforms a copmile using the NetRexx 1.1.x compiler + * + * @exception BuildException Description of Exception + */ + private void doNetRexxCompile() + throws BuildException + { + log( "Using NetRexx compiler", Project.MSG_VERBOSE ); + String classpath = getCompileClasspath(); + StringBuffer compileOptions = new StringBuffer(); + StringBuffer fileList = new StringBuffer(); + + // create an array of strings for input to the compiler: one array + // comes from the compile options, the other from the compileList + String[] compileOptionsArray = getCompileOptionsAsArray(); + String[] fileListArray = new String[compileList.size()]; + Enumeration e = compileList.elements(); + int j = 0; + while( e.hasMoreElements() ) + { + fileListArray[j] = ( String )e.nextElement(); + j++; + } + // create a single array of arguments for the compiler + String compileArgs[] = new String[compileOptionsArray.length + fileListArray.length]; + for( int i = 0; i < compileOptionsArray.length; i++ ) + { + compileArgs[i] = compileOptionsArray[i]; + } + for( int i = 0; i < fileListArray.length; i++ ) + { + compileArgs[i + compileOptionsArray.length] = fileListArray[i]; + } + + // print nice output about what we are doing for the log + compileOptions.append( "Compilation args: " ); + for( int i = 0; i < compileOptionsArray.length; i++ ) + { + compileOptions.append( compileOptionsArray[i] ); + compileOptions.append( " " ); + } + log( compileOptions.toString(), Project.MSG_VERBOSE ); + + String eol = System.getProperty( "line.separator" ); + StringBuffer niceSourceList = new StringBuffer( "Files to be compiled:" + eol ); + + for( int i = 0; i < compileList.size(); i++ ) + { + niceSourceList.append( " " ); + niceSourceList.append( compileList.elementAt( i ).toString() ); + niceSourceList.append( eol ); + } + + log( niceSourceList.toString(), Project.MSG_VERBOSE ); + + // need to set java.class.path property and restore it later + // since the NetRexx compiler has no option for the classpath + String currentClassPath = System.getProperty( "java.class.path" ); + Properties currentProperties = System.getProperties(); + currentProperties.put( "java.class.path", classpath ); + + try + { + StringWriter out = new StringWriter(); + int rc = + COM.ibm.netrexx.process.NetRexxC.main( new Rexx( compileArgs ), new PrintWriter( out ) ); + + if( rc > 1 ) + {// 1 is warnings from real NetRexxC + log( out.toString(), Project.MSG_ERR ); + String msg = "Compile failed, messages should have been provided."; + throw new BuildException( msg ); + } + else if( rc == 1 ) + { + log( out.toString(), Project.MSG_WARN ); + } + else + { + log( out.toString(), Project.MSG_INFO ); + } + } + finally + { + // need to reset java.class.path property + // since the NetRexx compiler has no option for the classpath + currentProperties = System.getProperties(); + currentProperties.put( "java.class.path", currentClassPath ); + } + } + + /** + * Scans the directory looking for source files to be compiled and support + * files to be copied. + * + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param files Description of Parameter + */ + private void scanDir( File srcDir, File destDir, String[] files ) + { + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + File destFile = new File( destDir, files[i] ); + String filename = files[i]; + // if it's a non source file, copy it if a later date than the + // dest + // if it's a source file, see if the destination class file + // needs to be recreated via compilation + if( filename.toLowerCase().endsWith( ".nrx" ) ) + { + File classFile = + new File( destDir, + filename.substring( 0, filename.lastIndexOf( '.' ) ) + ".class" ); + + if( !compile || srcFile.lastModified() > classFile.lastModified() ) + { + filecopyList.put( srcFile.getAbsolutePath(), destFile.getAbsolutePath() ); + compileList.addElement( destFile.getAbsolutePath() ); + } + } + else + { + if( srcFile.lastModified() > destFile.lastModified() ) + { + filecopyList.put( srcFile.getAbsolutePath(), destFile.getAbsolutePath() ); + } + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/PropertyFile.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/PropertyFile.java new file mode 100644 index 000000000..ab7bfd7b1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/PropertyFile.java @@ -0,0 +1,791 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.GregorianCalendar; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * PropertyFile task uses java.util.Properties to modify integer, String and + * Date settings in a property file.

      + * + * The following is an example of its usage: + *

        <target name="setState">
        + * + *
          <property
          + * + *
            name="header"
            + * value="##Generated file - do not modify!"/>
            + * <propertyfile file="apropfile.properties" comment="${header}"> + *
            + * <entry key="product.version.major" type="int" value="5"/>
            + * <entry key="product.version.minor" type="int" value="0"/>
            + * <entry key="product.build.major" type="int" value="0" />
            + * <entry key="product.build.minor" type="int" operation="+" />
            + * <entry key="product.build.date" type="date" operation="now" /> + *
            + * <entry key="intSet" type="int" operation="=" value="681"/>
            + * <entry key="intDec" type="int" operation="-"/>
            + * <entry key="NeverDate" type="date" operation="never"/>
            + * <entry key="StringEquals" type="string" value="testValue"/>
            + * <entry key="NowDate" type="date" operation="now"/>
            + * + *
          + * </propertyfile>
          + * + *
        + * </target> + *
      + *

      + * + * The <propertyfile> task must have:
      + * + *

        + *
      • file
      • + *
      + * Other parameters are:
      + * + *
        + *
      • comment, key, operation, type and value (the final four being + * eliminated shortly)
      • + *
      + * The <entry> task must have:
      + * + *
        + *
      • key
      • + *
      + * Other parameters are:
      + * + *
        + *
      • operation
      • + *
      • type
      • + *
      • value
      • + *
      • offset
      • + *
      + * If type is unspecified, it defaults to string Parameter values:
      + * + *
        + *
      • operation:
      • + *
          + *
        • "=" (set -- default)
        • + *
        • "-" (dec)
        • + *
        • "+" (inc)
        • + *
        • type:
        • + *
            + *
          • "int"
          • + *
          • "date"
          • + *
          • "string"
          • + *
          + * + *
        + * + *
      • value:
      • + *
          + *
        • holds the default value, if the property was not found in property + * file
        • + *
        • "now" In case of type "date", the value "now" will be replaced by + * the current date/time and used even if a valid date was found in the + * property file.
        • + *
        + * + *
      • offset:
        + * valid for "-" or "+", the offset (default set to 1) will be added or + * subtracted from "int" or "date" type value.
      • + *
      + * String property types can only use the "=" operation. Date property types can + * only use the "never" or "now" operations. Int property types can only use the + * "=", "-" or "+" operations.

      + * + * The message property is used for the property file header, with "\\" being a + * newline delimiter charater. + * + * @author Thomas Christen chr@active.ch + * @author Jeremy Mawson + * jem@loftinspace.com.au + */ +public class PropertyFile extends Task +{ + + /* + * ======================================================================== + * + * Static variables. + */ + private final static String NEWLINE = System.getProperty( "line.separator" ); + + private Vector entries = new Vector(); + + /* + * ======================================================================== + * + * Instance variables. + */ + // Use this to prepend a message to the properties file + private String m_comment; + + private Properties m_properties; + private File m_propertyfile; + + public void setComment( String hdr ) + { + m_comment = hdr; + } + + public void setFile( File file ) + { + m_propertyfile = file; + } + + public Entry createEntry() + { + Entry e = new Entry(); + entries.addElement( e ); + return e; + } + + /* + * ======================================================================== + * + * Constructors + */ + /* + * ======================================================================== + * + * Methods + */ + public void execute() + throws BuildException + { + checkParameters(); + readFile(); + executeOperation(); + writeFile(); + } + + /* + * Returns whether the given parameter has been defined. + */ + private boolean checkParam( String param ) + { + return !( ( param == null ) || ( param.equals( "null" ) ) ); + } + + private boolean checkParam( File param ) + { + return !( param == null ); + } + + private void checkParameters() + throws BuildException + { + if( !checkParam( m_propertyfile ) ) + { + throw new BuildException( "file token must not be null.", location ); + } + } + + private void executeOperation() + throws BuildException + { + for( Enumeration e = entries.elements(); e.hasMoreElements(); ) + { + Entry entry = ( Entry )e.nextElement(); + entry.executeOn( m_properties ); + } + } + + private void readFile() + throws BuildException + { + // Create the PropertyFile + m_properties = new Properties(); + try + { + if( m_propertyfile.exists() ) + { + log( "Updating property file: " + m_propertyfile.getAbsolutePath() ); + FileInputStream fis = null; + try + { + fis = new FileInputStream( m_propertyfile ); + BufferedInputStream bis = new BufferedInputStream( fis ); + m_properties.load( bis ); + } + finally + { + if( fis != null ) + { + fis.close(); + } + } + } + else + { + log( "Creating new property file: " + + m_propertyfile.getAbsolutePath() ); + FileOutputStream out = null; + try + { + out = new FileOutputStream( m_propertyfile.getAbsolutePath() ); + out.flush(); + } + finally + { + if( out != null ) + { + out.close(); + } + } + } + } + catch( IOException ioe ) + { + throw new BuildException( ioe.toString() ); + } + } + + private void writeFile() + throws BuildException + { + BufferedOutputStream bos = null; + try + { + bos = new BufferedOutputStream( new FileOutputStream( m_propertyfile ) ); + + // Properties.store is not available in JDK 1.1 + Method m = + Properties.class.getMethod( "store", + new Class[]{ + OutputStream.class, + String.class} + ); + m.invoke( m_properties, new Object[]{bos, m_comment} ); + + } + catch( NoSuchMethodException nsme ) + { + m_properties.save( bos, m_comment ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + throw new BuildException( t ); + } + catch( IllegalAccessException iae ) + { + // impossible + throw new BuildException( iae ); + } + catch( IOException ioe ) + { + throw new BuildException( ioe ); + } + finally + { + if( bos != null ) + { + try + { + bos.close(); + } + catch( IOException ioex ) + {} + } + } + } + + /** + * Instance of this class represents nested elements of a task propertyfile. + * + * @author RT + */ + public static class Entry + { + + final static String NOW_VALUE_ = "now"; + final static String NULL_VALUE_ = "never"; + + private final static int DEFAULT_INT_VALUE = 1; + private final static GregorianCalendar + DEFAULT_DATE_VALUE = new GregorianCalendar(); + + private String m_key = null; + private int m_type = Type.STRING_TYPE; + private int m_operation = Operation.EQUALS_OPER; + private String m_value = ""; + private String m_default = null; + private String m_pattern = null; + + public void setDefault( String value ) + { + this.m_default = value; + } + + public void setKey( String value ) + { + this.m_key = value; + } + + public void setOperation( Operation value ) + { + int newOperation = Operation.toOperation( value.getValue() ); + if( newOperation == Operation.NOW_VALUE ) + { + this.m_operation = Operation.EQUALS_OPER; + this.setValue( this.NOW_VALUE_ ); + } + else if( newOperation == Operation.NULL_VALUE ) + { + this.m_operation = Operation.EQUALS_OPER; + this.setValue( this.NULL_VALUE_ ); + } + else + { + this.m_operation = newOperation; + } + } + + public void setPattern( String value ) + { + this.m_pattern = value; + } + + public void setType( Type value ) + { + this.m_type = Type.toType( value.getValue() ); + } + + public void setValue( String value ) + { + this.m_value = value; + } + + protected void executeOn( Properties props ) + throws BuildException + { + checkParameters(); + + // m_type may be null because it wasn't set + try + { + if( m_type == Type.INTEGER_TYPE ) + { + executeInteger( ( String )props.get( m_key ) ); + } + else if( m_type == Type.DATE_TYPE ) + { + executeDate( ( String )props.get( m_key ) ); + } + else if( m_type == Type.STRING_TYPE ) + { + executeString( ( String )props.get( m_key ) ); + } + else + { + throw new BuildException( "Unknown operation type: " + m_type + "" ); + } + } + catch( NullPointerException npe ) + { + // Default to string type + // which means do nothing + npe.printStackTrace(); + } + // Insert as a string by default + props.put( m_key, m_value ); + + } + + /** + * Check if parameter combinations can be supported + * + * @exception BuildException Description of Exception + */ + private void checkParameters() + throws BuildException + { + if( m_type == Type.STRING_TYPE && + m_operation == Operation.DECREMENT_OPER ) + { + throw new BuildException( "- is not suported for string properties (key:" + m_key + ")" ); + } + if( m_value == null && m_default == null ) + { + throw new BuildException( "value and/or default must be specified (key:" + m_key + ")" ); + } + if( m_key == null ) + { + throw new BuildException( "key is mandatory" ); + } + if( m_type == Type.STRING_TYPE && + m_pattern != null ) + { + throw new BuildException( "pattern is not suported for string properties (key:" + m_key + ")" ); + } + } + + /** + * Handle operations for type date. + * + * @param oldValue the current value read from the property file or + * null if the key was not contained in + * the property file. + * @exception BuildException Description of Exception + */ + private void executeDate( String oldValue ) + throws BuildException + { + GregorianCalendar value = new GregorianCalendar(); + GregorianCalendar newValue = new GregorianCalendar(); + + if( m_pattern == null ) + m_pattern = "yyyy/MM/dd HH:mm"; + DateFormat fmt = new SimpleDateFormat( m_pattern ); + + // special case + if( m_default != null && + NOW_VALUE_.equals( m_default.toLowerCase() ) && + ( m_operation == Operation.INCREMENT_OPER || + m_operation == Operation.DECREMENT_OPER ) ) + { + oldValue = null; + } + + if( oldValue != null ) + { + try + { + value.setTime( fmt.parse( oldValue ) ); + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + + if( m_value != null ) + { + if( NOW_VALUE_.equals( m_value.toLowerCase() ) ) + { + value.setTime( new Date() ); + } + else if( NULL_VALUE_.equals( m_value.toLowerCase() ) ) + { + value = null; + } + else + { + try + { + value.setTime( fmt.parse( m_value ) ); + } + catch( Exception ex ) + { + // obviously not a date, try a simple int + try + { + int offset = Integer.parseInt( m_value ); + value.clear(); + value.set( Calendar.DAY_OF_YEAR, offset ); + } + catch( Exception ex_ ) + { + value.clear(); + value.set( Calendar.DAY_OF_YEAR, 1 ); + } + } + + } + } + + if( m_default != null && oldValue == null ) + { + if( NOW_VALUE_.equals( m_default.toLowerCase() ) ) + { + value.setTime( new Date() ); + } + else if( NULL_VALUE_.equals( m_default.toLowerCase() ) ) + { + value = null; + } + else + { + try + { + value.setTime( fmt.parse( m_default ) ); + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + } + + if( m_operation == Operation.EQUALS_OPER ) + { + newValue = value; + } + else if( m_operation == Operation.INCREMENT_OPER ) + { + newValue.add( Calendar.SECOND, value.get( Calendar.SECOND ) ); + newValue.add( Calendar.MINUTE, value.get( Calendar.MINUTE ) ); + newValue.add( Calendar.HOUR_OF_DAY, value.get( Calendar.HOUR_OF_DAY ) ); + newValue.add( Calendar.DAY_OF_YEAR, value.get( Calendar.DAY_OF_YEAR ) ); + } + else if( m_operation == Operation.DECREMENT_OPER ) + { + newValue.add( Calendar.SECOND, -1 * value.get( Calendar.SECOND ) ); + newValue.add( Calendar.MINUTE, -1 * value.get( Calendar.MINUTE ) ); + newValue.add( Calendar.HOUR_OF_DAY, -1 * value.get( Calendar.HOUR_OF_DAY ) ); + newValue.add( Calendar.DAY_OF_YEAR, -1 * value.get( Calendar.DAY_OF_YEAR ) ); + } + if( newValue != null ) + { + m_value = fmt.format( newValue.getTime() ); + } + else + { + m_value = ""; + } + } + + + /** + * Handle operations for type int. + * + * @param oldValue the current value read from the property file or + * null if the key was not contained in + * the property file. + * @exception BuildException Description of Exception + */ + private void executeInteger( String oldValue ) + throws BuildException + { + int value = 0; + int newValue = 0; + + DecimalFormat fmt = ( m_pattern != null ) ? new DecimalFormat( m_pattern ) + : new DecimalFormat(); + + if( oldValue != null ) + { + try + { + value = fmt.parse( oldValue ).intValue(); + } + catch( NumberFormatException nfe ) + { + /* + * swollow + */ + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + if( m_value != null ) + { + try + { + value = fmt.parse( m_value ).intValue(); + } + catch( NumberFormatException nfe ) + { + /* + * swollow + */ + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + if( m_default != null && oldValue == null ) + { + try + { + value = fmt.parse( m_default ).intValue(); + } + catch( NumberFormatException nfe ) + { + /* + * swollow + */ + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + + if( m_operation == Operation.EQUALS_OPER ) + { + newValue = value; + } + else if( m_operation == Operation.INCREMENT_OPER ) + { + newValue = ++value; + } + else if( m_operation == Operation.DECREMENT_OPER ) + { + newValue = --value; + } + m_value = fmt.format( newValue ); + } + + /** + * Handle operations for type string. + * + * @param oldValue the current value read from the property file or + * null if the key was not contained in + * the property file. + * @exception BuildException Description of Exception + */ + private void executeString( String oldValue ) + throws BuildException + { + String value = ""; + String newValue = ""; + + // the order of events is, of course, very important here + // default initially to the old value + if( oldValue != null ) + { + value = oldValue; + } + // but if a value is specified, use it + if( m_value != null ) + { + value = m_value; + } + // even if value is specified, ignore it and set to the default + // value if it is specified and there is no previous value + if( m_default != null && oldValue == null ) + { + value = m_default; + } + + if( m_operation == Operation.EQUALS_OPER ) + { + newValue = value; + } + else if( m_operation == Operation.INCREMENT_OPER ) + { + newValue += value; + } + m_value = newValue; + } + + /** + * Enumerated attribute with the values "+", "-", "=", "now" and + * "never". + * + * @author RT + */ + public static class Operation extends EnumeratedAttribute + { + + // Property type operations + public final static int INCREMENT_OPER = 0; + public final static int DECREMENT_OPER = 1; + public final static int EQUALS_OPER = 2; + + // Special values + public final static int NOW_VALUE = 3; + public final static int NULL_VALUE = 4; + + public static int toOperation( String oper ) + { + if( "+".equals( oper ) ) + { + return INCREMENT_OPER; + } + else if( "-".equals( oper ) ) + { + return DECREMENT_OPER; + } + else if( NOW_VALUE_.equals( oper ) ) + { + return NOW_VALUE; + } + else if( NULL_VALUE_.equals( oper ) ) + { + return NULL_VALUE; + } + return EQUALS_OPER; + } + + public String[] getValues() + { + return new String[]{"+", "-", "=", NOW_VALUE_, NULL_VALUE_}; + } + } + + /** + * Enumerated attribute with the values "int", "date" and "string". + * + * @author RT + */ + public static class Type extends EnumeratedAttribute + { + + // Property types + public final static int INTEGER_TYPE = 0; + public final static int DATE_TYPE = 1; + public final static int STRING_TYPE = 2; + + public static int toType( String type ) + { + if( "int".equals( type ) ) + { + return INTEGER_TYPE; + } + else if( "date".equals( type ) ) + { + return DATE_TYPE; + } + return STRING_TYPE; + } + + public String[] getValues() + { + return new String[]{"int", "date", "string"}; + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java new file mode 100644 index 000000000..1aa5ab674 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.Move; +import org.apache.tools.ant.types.Mapper; + +/** + * @author dIon Gillard + * dion@multitask.com.au + * @author Stefan Bodewig + * @version 1.2 + */ +public class RenameExtensions extends MatchingTask +{ + + private String fromExtension = ""; + private String toExtension = ""; + private boolean replace = false; + + private Mapper.MapperType globType; + private File srcDir; + + + /** + * Creates new RenameExtensions + */ + public RenameExtensions() + { + super(); + globType = new Mapper.MapperType(); + globType.setValue( "glob" ); + } + + /** + * store fromExtension * + * + * @param from The new FromExtension value + */ + public void setFromExtension( String from ) + { + fromExtension = from; + } + + /** + * store replace attribute - this determines whether the target file should + * be overwritten if present + * + * @param replace The new Replace value + */ + public void setReplace( boolean replace ) + { + this.replace = replace; + } + + /** + * Set the source dir to find the files to be renamed. + * + * @param srcDir The new SrcDir value + */ + public void setSrcDir( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * store toExtension * + * + * @param to The new ToExtension value + */ + public void setToExtension( String to ) + { + toExtension = to; + } + + /** + * Executes the task, i.e. does the actual compiler call + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + + // first off, make sure that we've got a from and to extension + if( fromExtension == null || toExtension == null || srcDir == null ) + { + throw new BuildException( "srcDir, fromExtension and toExtension " + + "attributes must be set!" ); + } + + log( "DEPRECATED - The renameext task is deprecated. Use move instead.", + Project.MSG_WARN ); + log( "Replace this with:", Project.MSG_INFO ); + log( "", + Project.MSG_INFO ); + log( " ", Project.MSG_INFO ); + log( " ", Project.MSG_INFO ); + log( "", Project.MSG_INFO ); + log( "using the same patterns on as you\'ve used here", + Project.MSG_INFO ); + + Move move = ( Move )project.createTask( "move" ); + move.setOwningTarget( target ); + move.setTaskName( getTaskName() ); + move.setLocation( getLocation() ); + move.setTodir( srcDir ); + move.setOverwrite( replace ); + + fileset.setDir( srcDir ); + move.addFileset( fileset ); + + Mapper me = move.createMapper(); + me.setType( globType ); + me.setFrom( "*" + fromExtension ); + me.setTo( "*" + toExtension ); + + move.execute(); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java new file mode 100644 index 000000000..ece6ed56e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.PrintWriter; +import java.util.Random; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.RegularExpression; +import org.apache.tools.ant.types.Substitution; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.regexp.Regexp; + +/** + *

      + * Task to do regular expression string replacements in a text
      + * file.  The input file(s) must be able to be properly processed by
      + * a Reader instance.  That is, they must be text only, no binary.
      + *
      + * The syntax of the regular expression depends on the implemtation that
      + * you choose to use. The system property ant.regexp.regexpimpl
      + * will be the classname of the implementation that will be used (the default is
      + * org.apache.tools.ant.util.regexp.JakartaOroRegexp and requires
      + * the Jakarta Oro Package). 
      + * For jdk  <= 1.3, there are two available implementations:
      + *   org.apache.tools.ant.util.regexp.JakartaOroRegexp (the default)
      + *        Requires  the jakarta-oro package
      + *
      + *   org.apache.tools.ant.util.regexp.JakartaRegexpRegexp
      + *        Requires the jakarta-regexp package
      + *
      + * For jdk >= 1.4 an additional implementation is available:
      + *   org.apache.tools.ant.util.regexp.Jdk14RegexpRegexp
      + *        Requires the jdk 1.4 built in regular expression package.
      + * 
      Usage: Call Syntax: <replaceregexp file="file" match="pattern" + * replace="pattern" flags="options"? byline="true|false"? > + * regularexpression? substitution? fileset* </replaceregexp> NOTE: You + * must have either the file attribute specified, or at least one fileset + * subelement to operation on. You may not have the file attribute specified if + * you nest fileset elements inside this task. Also, you cannot specify both + * match and a regular expression subelement at the same time, nor can you + * specify the replace attribute and the substitution subelement at the same + * time. Attributes: file --> A single file to operation on (mutually + * exclusive with the fileset subelements) match --> The Regular expression + * to match replace --> The Expression replacement string flags --> The + * options to give to the replacement g = Substitute all occurrences. default is + * to replace only the first one i = Case insensitive match byline --> Should + * this file be processed a single line at a time (default is false) "true" + * indicates to perform replacement on a line by line basis "false" indicates to + * perform replacement on the whole file at once. Example: The following call + * could be used to replace an old property name in a ".properties" file with a + * new name. In the replace attribute, you can refer to any part of the match + * expression in parenthesis using backslash followed by a number like '\1'. + * <replaceregexp file="test.properties" match="MyProperty=(.*)" + * replace="NewProperty=\1" byline="true" />
      + * + * @author Matthew Inger + */ +public class ReplaceRegExp extends Task +{ + + private FileUtils fileUtils = FileUtils.newFileUtils(); + private boolean byline; + + private File file; + private Vector filesets; + private String flags;// Keep jdk 1.1 compliant so others can use this + private RegularExpression regex; + private Substitution subs; + + /** + * Default Constructor + */ + public ReplaceRegExp() + { + super(); + this.file = null; + this.filesets = new Vector(); + this.flags = ""; + this.byline = false; + + this.regex = null; + this.subs = null; + } + + public void setByLine( String byline ) + { + Boolean res = Boolean.valueOf( byline ); + if( res == null ) + res = Boolean.FALSE; + this.byline = res.booleanValue(); + } + + public void setFile( File file ) + { + this.file = file; + } + + public void setFlags( String flags ) + { + this.flags = flags; + } + + public void setMatch( String match ) + { + if( regex != null ) + throw new BuildException( "Only one regular expression is allowed" ); + + regex = new RegularExpression(); + regex.setPattern( match ); + } + + public void setReplace( String replace ) + { + if( subs != null ) + throw new BuildException( "Only one substitution expression is allowed" ); + + subs = new Substitution(); + subs.setExpression( replace ); + } + + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + public RegularExpression createRegularExpression() + { + if( regex != null ) + throw new BuildException( "Only one regular expression is allowed." ); + + regex = new RegularExpression(); + return regex; + } + + public Substitution createSubstitution() + { + if( subs != null ) + throw new BuildException( "Only one substitution expression is allowed" ); + + subs = new Substitution(); + return subs; + } + + public void execute() + throws BuildException + { + if( regex == null ) + throw new BuildException( "No expression to match." ); + if( subs == null ) + throw new BuildException( "Nothing to replace expression with." ); + + if( file != null && filesets.size() > 0 ) + throw new BuildException( "You cannot supply the 'file' attribute and filesets at the same time." ); + + int options = 0; + + if( flags.indexOf( 'g' ) != -1 ) + options |= Regexp.REPLACE_ALL; + + if( flags.indexOf( 'i' ) != -1 ) + options |= Regexp.MATCH_CASE_INSENSITIVE; + + if( flags.indexOf( 'm' ) != -1 ) + options |= Regexp.MATCH_MULTILINE; + + if( flags.indexOf( 's' ) != -1 ) + options |= Regexp.MATCH_SINGLELINE; + + if( file != null && file.exists() ) + { + try + { + doReplace( file, options ); + } + catch( IOException e ) + { + log( "An error occurred processing file: '" + file.getAbsolutePath() + "': " + e.toString(), + Project.MSG_ERR ); + } + } + else if( file != null ) + { + log( "The following file is missing: '" + file.getAbsolutePath() + "'", + Project.MSG_ERR ); + } + + int sz = filesets.size(); + for( int i = 0; i < sz; i++ ) + { + FileSet fs = ( FileSet )( filesets.elementAt( i ) ); + DirectoryScanner ds = fs.getDirectoryScanner( getProject() ); + + String files[] = ds.getIncludedFiles(); + for( int j = 0; j < files.length; j++ ) + { + File f = new File( files[j] ); + if( f.exists() ) + { + try + { + doReplace( f, options ); + } + catch( Exception e ) + { + log( "An error occurred processing file: '" + f.getAbsolutePath() + "': " + e.toString(), + Project.MSG_ERR ); + } + } + else + { + log( "The following file is missing: '" + file.getAbsolutePath() + "'", + Project.MSG_ERR ); + } + } + } + } + + + protected String doReplace( RegularExpression r, + Substitution s, + String input, + int options ) + { + String res = input; + Regexp regexp = r.getRegexp( project ); + + if( regexp.matches( input, options ) ) + { + res = regexp.substitute( input, s.getExpression( project ), options ); + } + + return res; + } + + /** + * Perform the replace on the entire file + * + * @param f Description of Parameter + * @param options Description of Parameter + * @exception IOException Description of Exception + */ + protected void doReplace( File f, int options ) + throws IOException + { + File parentDir = new File( new File( f.getAbsolutePath() ).getParent() ); + File temp = fileUtils.createTempFile( "replace", ".txt", parentDir ); + + FileReader r = null; + FileWriter w = null; + + try + { + r = new FileReader( f ); + w = new FileWriter( temp ); + + BufferedReader br = new BufferedReader( r ); + BufferedWriter bw = new BufferedWriter( w ); + PrintWriter pw = new PrintWriter( bw ); + + boolean changes = false; + + log( "Replacing pattern '" + regex.getPattern( project ) + "' with '" + subs.getExpression( project ) + + "' in '" + f.getPath() + "'" + + ( byline ? " by line" : "" ) + + ( flags.length() > 0 ? " with flags: '" + flags + "'" : "" ) + + ".", + Project.MSG_WARN ); + + if( byline ) + { + LineNumberReader lnr = new LineNumberReader( br ); + String line = null; + + while( ( line = lnr.readLine() ) != null ) + { + String res = doReplace( regex, subs, line, options ); + if( !res.equals( line ) ) + changes = true; + + pw.println( res ); + } + pw.flush(); + } + else + { + int flen = ( int )( f.length() ); + char tmpBuf[] = new char[flen]; + int numread = 0; + int totread = 0; + while( numread != -1 && totread < flen ) + { + numread = br.read( tmpBuf, totread, flen ); + totread += numread; + } + + String buf = new String( tmpBuf ); + + String res = doReplace( regex, subs, buf, options ); + if( !res.equals( buf ) ) + changes = true; + + pw.println( res ); + pw.flush(); + } + + r.close(); + r = null; + w.close(); + w = null; + + if( changes ) + { + f.delete(); + temp.renameTo( f ); + } + else + { + temp.delete(); + } + } + finally + { + try + { + if( r != null ) + r.close(); + } + catch( Exception e ) + {} + ; + + try + { + if( w != null ) + r.close(); + } + catch( Exception e ) + {} + ; + } + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Rpm.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Rpm.java new file mode 100644 index 000000000..3811e701b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Rpm.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.taskdefs.PumpStreamHandler; +import org.apache.tools.ant.types.Commandline; + +/** + * @author lucas@collab.net + */ +public class Rpm extends Task +{ + + /** + * the rpm command to use + */ + private String command = "-bb"; + + /** + * clean BUILD directory + */ + private boolean cleanBuildDir = false; + + /** + * remove spec file + */ + private boolean removeSpec = false; + + /** + * remove sources + */ + private boolean removeSource = false; + + /** + * the file to direct standard error from the command + */ + private File error; + + /** + * the file to direct standard output from the command + */ + private File output; + + /** + * the spec file + */ + private String specFile; + + /** + * the rpm top dir + */ + private File topDir; + + public void setCleanBuildDir( boolean cbd ) + { + cleanBuildDir = cbd; + } + + public void setCommand( String c ) + { + this.command = c; + } + + public void setError( File error ) + { + this.error = error; + } + + public void setOutput( File output ) + { + this.output = output; + } + + public void setRemoveSource( boolean rs ) + { + removeSource = rs; + } + + public void setRemoveSpec( boolean rs ) + { + removeSpec = rs; + } + + public void setSpecFile( String sf ) + { + if( ( sf == null ) || ( sf.trim().equals( "" ) ) ) + { + throw new BuildException( "You must specify a spec file", location ); + } + this.specFile = sf; + } + + public void setTopDir( File td ) + { + this.topDir = td; + } + + public void execute() + throws BuildException + { + + Commandline toExecute = new Commandline(); + + toExecute.setExecutable( "rpm" ); + if( topDir != null ) + { + toExecute.createArgument().setValue( "--define" ); + toExecute.createArgument().setValue( "_topdir" + topDir ); + } + + toExecute.createArgument().setLine( command ); + + if( cleanBuildDir ) + { + toExecute.createArgument().setValue( "--clean" ); + } + if( removeSpec ) + { + toExecute.createArgument().setValue( "--rmspec" ); + } + if( removeSource ) + { + toExecute.createArgument().setValue( "--rmsource" ); + } + + toExecute.createArgument().setValue( "SPECS/" + specFile ); + + ExecuteStreamHandler streamhandler = null; + OutputStream outputstream = null; + OutputStream errorstream = null; + if( error == null && output == null ) + { + streamhandler = new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ); + } + else + { + if( output != null ) + { + try + { + outputstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( output ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + outputstream = new LogOutputStream( this, Project.MSG_INFO ); + } + if( error != null ) + { + try + { + errorstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( error ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + errorstream = new LogOutputStream( this, Project.MSG_WARN ); + } + streamhandler = new PumpStreamHandler( outputstream, errorstream ); + } + + Execute exe = new Execute( streamhandler, null ); + + exe.setAntRun( project ); + if( topDir == null ) + topDir = project.getBaseDir(); + exe.setWorkingDirectory( topDir ); + + exe.setCommandline( toExecute.getCommandline() ); + try + { + exe.execute(); + log( "Building the RPM based on the " + specFile + " file" ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + if( output != null ) + { + try + { + outputstream.close(); + } + catch( IOException e ) + {} + } + if( error != null ) + { + try + { + errorstream.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Script.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Script.java new file mode 100644 index 000000000..43a545ec1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Script.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import com.ibm.bsf.BSFException; +import com.ibm.bsf.BSFManager; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Execute a script + * + * @author Sam Ruby rubys@us.ibm.com + */ +public class Script extends Task +{ + private String script = ""; + private Hashtable beans = new Hashtable(); + private String language; + + /** + * Defines the language (required). + * + * @param language The new Language value + */ + public void setLanguage( String language ) + { + this.language = language; + } + + /** + * Load the script from an external file + * + * @param fileName The new Src value + */ + public void setSrc( String fileName ) + { + File file = new File( fileName ); + if( !file.exists() ) + throw new BuildException( "file " + fileName + " not found." ); + + int count = ( int )file.length(); + byte data[] = new byte[count]; + + try + { + FileInputStream inStream = new FileInputStream( file ); + inStream.read( data ); + inStream.close(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + script += new String( data ); + } + + /** + * Defines the script. + * + * @param text The feature to be added to the Text attribute + */ + public void addText( String text ) + { + this.script += text; + } + + /** + * Do the work. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + try + { + addBeans( project.getProperties() ); + addBeans( project.getUserProperties() ); + addBeans( project.getTargets() ); + addBeans( project.getReferences() ); + + beans.put( "project", getProject() ); + + beans.put( "self", this ); + + BSFManager manager = new BSFManager(); + + for( Enumeration e = beans.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + Object value = beans.get( key ); + manager.declareBean( key, value, value.getClass() ); + } + + // execute the script + manager.exec( language, "", 0, 0, script ); + } + catch( BSFException be ) + { + Throwable t = be; + Throwable te = be.getTargetException(); + if( te != null ) + { + if( te instanceof BuildException ) + { + throw ( BuildException )te; + } + else + { + t = te; + } + } + throw new BuildException( t ); + } + } + + /** + * Add a list of named objects to the list to be exported to the script + * + * @param dictionary The feature to be added to the Beans attribute + */ + private void addBeans( Hashtable dictionary ) + { + for( Enumeration e = dictionary.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + + boolean isValid = key.length() > 0 && + Character.isJavaIdentifierStart( key.charAt( 0 ) ); + + for( int i = 1; isValid && i < key.length(); i++ ) + isValid = Character.isJavaIdentifierPart( key.charAt( i ) ); + + if( isValid ) + beans.put( key, dictionary.get( key ) ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/StyleBook.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/StyleBook.java new file mode 100644 index 000000000..0f37fc687 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/StyleBook.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Java; + +/** + * Basic task for apache stylebook. + * + * @author Peter Donald + * @author Marcus + * Börger + */ +public class StyleBook + extends Java +{ + protected File m_book; + protected String m_loaderConfig; + protected File m_skinDirectory; + protected File m_targetDirectory; + + public StyleBook() + { + setClassname( "org.apache.stylebook.StyleBook" ); + setFork( true ); + setFailonerror( true ); + } + + public void setBook( final File book ) + { + m_book = book; + } + + public void setLoaderConfig( final String loaderConfig ) + { + m_loaderConfig = loaderConfig; + } + + public void setSkinDirectory( final File skinDirectory ) + { + m_skinDirectory = skinDirectory; + } + + public void setTargetDirectory( final File targetDirectory ) + { + m_targetDirectory = targetDirectory; + } + + public void execute() + throws BuildException + { + + if( null == m_targetDirectory ) + { + throw new BuildException( "TargetDirectory attribute not set." ); + } + + if( null == m_skinDirectory ) + { + throw new BuildException( "SkinDirectory attribute not set." ); + } + + if( null == m_book ) + { + throw new BuildException( "book attribute not set." ); + } + + createArg().setValue( "targetDirectory=" + m_targetDirectory ); + createArg().setValue( m_book.toString() ); + createArg().setValue( m_skinDirectory.toString() ); + if( null != m_loaderConfig ) + { + createArg().setValue( "loaderConfig=" + m_loaderConfig ); + } + + super.execute(); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Test.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Test.java new file mode 100644 index 000000000..3f248fdc4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/Test.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Java; + +/** + * @author Peter Donald + */ +public class Test + extends Java +{ + + protected Vector m_tests = new Vector(); + + public Test() + { + setClassname( "org.apache.testlet.engine.TextTestEngine" ); + } + + public void setForceShowTrace( final boolean forceShowTrace ) + { + createArg().setValue( "-f=" + forceShowTrace ); + } + + public void setShowBanner( final String showBanner ) + { + createArg().setValue( "-b=" + showBanner ); + } + + public void setShowSuccess( final boolean showSuccess ) + { + createArg().setValue( "-s=" + showSuccess ); + } + + public void setShowTrace( final boolean showTrace ) + { + createArg().setValue( "-t=" + showTrace ); + } + + public TestletEntry createTestlet() + { + final TestletEntry entry = new TestletEntry(); + m_tests.addElement( entry ); + return entry; + } + + public void execute() + throws BuildException + { + + final int size = m_tests.size(); + + for( int i = 0; i < size; i++ ) + { + createArg().setValue( m_tests.elementAt( i ).toString() ); + } + + super.execute(); + } + + protected final static class TestletEntry + { + + protected String m_testname = ""; + + public void addText( final String testname ) + { + m_testname += testname; + } + + public String toString() + { + return m_testname; + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java new file mode 100644 index 000000000..e608bfcd2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import javax.xml.transform.ErrorListener; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import org.apache.tools.ant.taskdefs.XSLTLiaison; +import org.apache.tools.ant.taskdefs.XSLTLogger; +import org.apache.tools.ant.taskdefs.XSLTLoggerAware; + +/** + * Concrete liaison for XSLT processor implementing TraX. (ie JAXP 1.1) + * + * @author Sam Ruby + * @author Davanum Srinivas + * @author Stephane Bailliez + */ +public class TraXLiaison implements XSLTLiaison, ErrorListener, XSLTLoggerAware +{ + + /** + * The trax TransformerFactory + */ + private TransformerFactory tfactory = null; + + /** + * stylesheet stream, close it asap + */ + private FileInputStream xslStream = null; + + /** + * Stylesheet template + */ + private Templates templates = null; + + /** + * transformer + */ + private Transformer transformer = null; + + private XSLTLogger logger; + + public TraXLiaison() + throws Exception + { + tfactory = TransformerFactory.newInstance(); + tfactory.setErrorListener( this ); + } + + public void setLogger( XSLTLogger l ) + { + logger = l; + } + + public void setOutputtype( String type ) + throws Exception + { + transformer.setOutputProperty( OutputKeys.METHOD, type ); + } + +//------------------- IMPORTANT + // 1) Don't use the StreamSource(File) ctor. It won't work with + // xalan prior to 2.2 because of systemid bugs. + + // 2) Use a stream so that you can close it yourself quickly + // and avoid keeping the handle until the object is garbaged. + // (always keep control), otherwise you won't be able to delete + // the file quickly on windows. + + // 3) Always set the systemid to the source for imports, includes... + // in xsl and xml... + + public void setStylesheet( File stylesheet ) + throws Exception + { + xslStream = new FileInputStream( stylesheet ); + StreamSource src = new StreamSource( xslStream ); + src.setSystemId( getSystemId( stylesheet ) ); + templates = tfactory.newTemplates( src ); + transformer = templates.newTransformer(); + transformer.setErrorListener( this ); + } + + public void addParam( String name, String value ) + { + transformer.setParameter( name, value ); + } + + public void error( TransformerException e ) + { + logError( e, "Error" ); + } + + public void fatalError( TransformerException e ) + { + logError( e, "Fatal Error" ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileInputStream fis = null; + FileOutputStream fos = null; + try + { + fis = new FileInputStream( infile ); + fos = new FileOutputStream( outfile ); + StreamSource src = new StreamSource( fis ); + src.setSystemId( getSystemId( infile ) ); + StreamResult res = new StreamResult( fos ); + // not sure what could be the need of this... + res.setSystemId( getSystemId( outfile ) ); + + transformer.transform( src, res ); + } + finally + { + // make sure to close all handles, otherwise the garbage + // collector will close them...whenever possible and + // Windows may complain about not being able to delete files. + try + { + if( xslStream != null ) + { + xslStream.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fis != null ) + { + fis.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fos != null ) + { + fos.close(); + } + } + catch( IOException ignored ) + {} + } + } + + public void warning( TransformerException e ) + { + logError( e, "Warning" ); + } + + // make sure that the systemid is made of '/' and not '\' otherwise + // crimson will complain that it cannot resolve relative entities + // because it grabs the base uri via lastIndexOf('/') without + // making sure it is really a /'ed path + protected String getSystemId( File file ) + { + String path = file.getAbsolutePath(); + path = path.replace( '\\', '/' ); + return FILE_PROTOCOL_PREFIX + path; + } + + private void logError( TransformerException e, String type ) + { + StringBuffer msg = new StringBuffer(); + if( e.getLocator() != null ) + { + if( e.getLocator().getSystemId() != null ) + { + String url = e.getLocator().getSystemId(); + if( url.startsWith( "file:///" ) ) + url = url.substring( 8 ); + msg.append( url ); + } + else + { + msg.append( "Unknown file" ); + } + if( e.getLocator().getLineNumber() != -1 ) + { + msg.append( ":" + e.getLocator().getLineNumber() ); + if( e.getLocator().getColumnNumber() != -1 ) + { + msg.append( ":" + e.getLocator().getColumnNumber() ); + } + } + } + msg.append( ": " + type + "! " ); + msg.append( e.getMessage() ); + if( e.getCause() != null ) + { + msg.append( " Cause: " + e.getCause() ); + } + + logger.log( msg.toString() ); + } + +}//-- TraXLiaison diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java new file mode 100644 index 000000000..02abefc30 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java @@ -0,0 +1,648 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Parser; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.ParserAdapter; + +/** + * The XMLValidateTask checks that an XML document is valid, with a + * SAX validating parser. + * + * @author Raphael Pierquin + * raphael.pierquin@agisphere.com + */ +public class XMLValidateTask extends Task +{ + + /** + * The default implementation parser classname used by the task to process + * validation. + */ + // The crimson implementation is shipped with ant. + public static String DEFAULT_XML_READER_CLASSNAME = "org.apache.crimson.parser.XMLReaderImpl"; + + protected static String INIT_FAILED_MSG = "Could not start xml validation: "; + + // ant task properties + // defaults + protected boolean failOnError = true; + protected boolean warn = true; + protected boolean lenient = false; + protected String readerClassName = DEFAULT_XML_READER_CLASSNAME; + + protected File file = null;// file to be validated + protected Vector filesets = new Vector(); + + /** + * the parser is viewed as a SAX2 XMLReader. If a SAX1 parser is specified, + * it's wrapped in an adapter that make it behave as a XMLReader. a more + * 'standard' way of doing this would be to use the JAXP1.1 SAXParser + * interface. + */ + protected XMLReader xmlReader = null;// XMLReader used to validation process + protected ValidatorErrorHandler errorHandler + = new ValidatorErrorHandler();// to report sax parsing errors + protected Hashtable features = new Hashtable(); + + /** + * The list of configured DTD locations + */ + public Vector dtdLocations = new Vector();// sets of file to be validated + protected Path classpath; + + /** + * Specify the class name of the SAX parser to be used. (optional) + * + * @param className should be an implementation of SAX2 org.xml.sax.XMLReader + * or SAX2 org.xml.sax.Parser.

      + * + * if className is an implementation of org.xml.sax.Parser + * , {@link #setLenient(boolean)}, will be ignored.

      + * + * if not set, the default {@link #DEFAULT_XML_READER_CLASSNAME} will + * be used. + * @see org.xml.sax.XMLReader + * @see org.xml.sax.Parser + */ + public void setClassName( String className ) + { + + readerClassName = className; + } + + + /** + * Specify the classpath to be searched to load the parser (optional) + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + /** + * @param r The new ClasspathRef value + * @see #setClasspath + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Specify how parser error are to be handled.

      + * + * If set to true (default), throw a buildException if the + * parser yields an error. + * + * @param fail The new FailOnError value + */ + public void setFailOnError( boolean fail ) + { + + failOnError = fail; + } + + /** + * specifify the file to be checked + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Specify whether the parser should be validating. Default is true + * .

      + * + * If set to false, the validation will fail only if the parsed document is + * not well formed XML.

      + * + * this option is ignored if the specified class with {@link + * #setClassName(String)} is not a SAX2 XMLReader. + * + * @param bool The new Lenient value + */ + public void setLenient( boolean bool ) + { + + lenient = bool; + } + + /** + * Specify how parser error are to be handled.

      + * + * If set to true + * + *(default), log a warn message for each SAX warn event. + * + * @param bool The new Warn value + */ + public void setWarn( boolean bool ) + { + + warn = bool; + } + + /** + * specifify a set of file to be checked + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * @return Description of the Returned Value + * @see #setClasspath + */ + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + /** + * Create a DTD location record. This stores the location of a DTD. The DTD + * is identified by its public Id. The location may either be a file + * location or a resource location. + * + * @return Description of the Returned Value + */ + public DTDLocation createDTD() + { + DTDLocation dtdLocation = new DTDLocation(); + dtdLocations.addElement( dtdLocation ); + + return dtdLocation; + } + + public void execute() + throws BuildException + { + + int fileProcessed = 0; + if( file == null && ( filesets.size() == 0 ) ) + { + throw new BuildException( "Specify at least one source - a file or a fileset." ); + } + + initValidator(); + + if( file != null ) + { + if( file.exists() && file.canRead() && file.isFile() ) + { + doValidate( file ); + fileProcessed++; + } + else + { + String errorMsg = "File " + file + " cannot be read"; + if( failOnError ) + throw new BuildException( errorMsg ); + else + log( errorMsg, Project.MSG_ERR ); + } + } + + for( int i = 0; i < filesets.size(); i++ ) + { + + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] files = ds.getIncludedFiles(); + + for( int j = 0; j < files.length; j++ ) + { + File srcFile = new File( fs.getDir( project ), files[j] ); + doValidate( srcFile ); + fileProcessed++; + } + } + log( fileProcessed + " file(s) have been successfully validated." ); + } + + protected EntityResolver getEntityResolver() + { + LocalResolver resolver = new LocalResolver(); + + for( Enumeration i = dtdLocations.elements(); i.hasMoreElements(); ) + { + DTDLocation location = ( DTDLocation )i.nextElement(); + resolver.registerDTD( location ); + } + return resolver; + } + + /* + * set a feature on the parser. + * TODO: find a way to set any feature from build.xml + */ + private boolean setFeature( String feature, boolean value, boolean warn ) + { + + boolean toReturn = false; + try + { + xmlReader.setFeature( feature, value ); + toReturn = true; + } + catch( SAXNotRecognizedException e ) + { + if( warn ) + log( "Could not set feature '" + + feature + + "' because the parser doesn't recognize it", + Project.MSG_WARN ); + } + catch( SAXNotSupportedException e ) + { + if( warn ) + log( "Could not set feature '" + + feature + + "' because the parser doesn't support it", + Project.MSG_WARN ); + } + return toReturn; + } + + /* + * parse the file + */ + private void doValidate( File afile ) + { + try + { + log( "Validating " + afile.getName() + "... ", Project.MSG_VERBOSE ); + errorHandler.init( afile ); + InputSource is = new InputSource( new FileReader( afile ) ); + String uri = "file:" + afile.getAbsolutePath().replace( '\\', '/' ); + for( int index = uri.indexOf( '#' ); index != -1; + index = uri.indexOf( '#' ) ) + { + uri = uri.substring( 0, index ) + "%23" + uri.substring( index + 1 ); + } + is.setSystemId( uri ); + xmlReader.parse( is ); + } + catch( SAXException ex ) + { + if( failOnError ) + throw new BuildException( "Could not validate document " + afile ); + } + catch( IOException ex ) + { + throw new BuildException( "Could not validate document " + afile, ex ); + } + + if( errorHandler.getFailure() ) + { + if( failOnError ) + throw new BuildException( afile + " is not a valid XML document." ); + else + log( afile + " is not a valid XML document", Project.MSG_ERR ); + } + } + + /** + * init the parser : load the parser class, and set features if necessary + */ + private void initValidator() + { + + try + { + // load the parser class + // with JAXP, we would use a SAXParser factory + Class readerClass = null; + //Class readerImpl = null; + //Class parserImpl = null; + if( classpath != null ) + { + AntClassLoader loader = new AntClassLoader( project, classpath ); +// loader.addSystemPackageRoot("org.xml"); // needed to avoid conflict + readerClass = loader.loadClass( readerClassName ); + AntClassLoader.initializeClass( readerClass ); + } + else + readerClass = Class.forName( readerClassName ); + + // then check it implements XMLReader + if( XMLReader.class.isAssignableFrom( readerClass ) ) + { + + xmlReader = ( XMLReader )readerClass.newInstance(); + log( "Using SAX2 reader " + readerClassName, Project.MSG_VERBOSE ); + } + else + { + + // see if it is a SAX1 Parser + if( Parser.class.isAssignableFrom( readerClass ) ) + { + Parser parser = ( Parser )readerClass.newInstance(); + xmlReader = new ParserAdapter( parser ); + log( "Using SAX1 parser " + readerClassName, Project.MSG_VERBOSE ); + } + else + { + throw new BuildException( INIT_FAILED_MSG + + readerClassName + + " implements nor SAX1 Parser nor SAX2 XMLReader." ); + } + } + } + catch( ClassNotFoundException e ) + { + throw new BuildException( INIT_FAILED_MSG + readerClassName, e ); + } + catch( InstantiationException e ) + { + throw new BuildException( INIT_FAILED_MSG + readerClassName, e ); + } + catch( IllegalAccessException e ) + { + throw new BuildException( INIT_FAILED_MSG + readerClassName, e ); + } + + xmlReader.setEntityResolver( getEntityResolver() ); + xmlReader.setErrorHandler( errorHandler ); + + if( !( xmlReader instanceof ParserAdapter ) ) + { + // turn validation on + if( !lenient ) + { + boolean ok = setFeature( "http://xml.org/sax/features/validation", true, true ); + if( !ok ) + { + throw new BuildException( INIT_FAILED_MSG + + readerClassName + + " doesn't provide validation" ); + } + } + // set other features + Enumeration enum = features.keys(); + while( enum.hasMoreElements() ) + { + String featureId = ( String )enum.nextElement(); + setFeature( featureId, ( ( Boolean )features.get( featureId ) ).booleanValue(), true ); + } + } + } + + public static class DTDLocation + { + private String publicId = null; + private String location = null; + + public void setLocation( String location ) + { + this.location = location; + } + + public void setPublicId( String publicId ) + { + this.publicId = publicId; + } + + public String getLocation() + { + return location; + } + + public String getPublicId() + { + return publicId; + } + } + + /* + * ValidatorErrorHandler role : + *

        + *
      • log SAX parse exceptions, + *
      • remember if an error occured + *
      + */ + protected class ValidatorErrorHandler implements ErrorHandler + { + + protected File currentFile = null; + protected String lastErrorMessage = null; + protected boolean failed = false; + + // did an error happen during last parsing ? + public boolean getFailure() + { + + return failed; + } + + public void error( SAXParseException exception ) + { + failed = true; + doLog( exception, Project.MSG_ERR ); + } + + public void fatalError( SAXParseException exception ) + { + failed = true; + doLog( exception, Project.MSG_ERR ); + } + + public void init( File file ) + { + currentFile = file; + failed = false; + } + + public void warning( SAXParseException exception ) + { + // depending on implementation, XMLReader can yield hips of warning, + // only output then if user explicitely asked for it + if( warn ) + doLog( exception, Project.MSG_WARN ); + } + + private String getMessage( SAXParseException e ) + { + String sysID = e.getSystemId(); + if( sysID != null ) + { + try + { + int line = e.getLineNumber(); + int col = e.getColumnNumber(); + return new URL( sysID ).getFile() + + ( line == -1 ? "" : ( ":" + line + + ( col == -1 ? "" : ( ":" + col ) ) ) ) + + ": " + e.getMessage(); + } + catch( MalformedURLException mfue ) + { + } + } + return e.getMessage(); + } + + private void doLog( SAXParseException e, int logLevel ) + { + + log( getMessage( e ), logLevel ); + } + } + + private class LocalResolver + implements EntityResolver + { + private Hashtable fileDTDs = new Hashtable(); + private Hashtable resourceDTDs = new Hashtable(); + private Hashtable urlDTDs = new Hashtable(); + + public LocalResolver() { } + + public void registerDTD( String publicId, String location ) + { + if( location == null ) + { + return; + } + + File fileDTD = new File( location ); + if( fileDTD.exists() ) + { + if( publicId != null ) + { + fileDTDs.put( publicId, fileDTD ); + log( "Mapped publicId " + publicId + " to file " + fileDTD, Project.MSG_VERBOSE ); + } + return; + } + + if( LocalResolver.this.getClass().getResource( location ) != null ) + { + if( publicId != null ) + { + resourceDTDs.put( publicId, location ); + log( "Mapped publicId " + publicId + " to resource " + location, Project.MSG_VERBOSE ); + } + } + + try + { + if( publicId != null ) + { + URL urldtd = new URL( location ); + urlDTDs.put( publicId, urldtd ); + } + } + catch( java.net.MalformedURLException e ) + { + //ignored + } + } + + public void registerDTD( DTDLocation location ) + { + registerDTD( location.getPublicId(), location.getLocation() ); + } + + public InputSource resolveEntity( String publicId, String systemId ) + throws SAXException + { + File dtdFile = ( File )fileDTDs.get( publicId ); + if( dtdFile != null ) + { + try + { + log( "Resolved " + publicId + " to local file " + dtdFile, Project.MSG_VERBOSE ); + return new InputSource( new FileInputStream( dtdFile ) ); + } + catch( FileNotFoundException ex ) + { + // ignore + } + } + + String dtdResourceName = ( String )resourceDTDs.get( publicId ); + if( dtdResourceName != null ) + { + InputStream is = this.getClass().getResourceAsStream( dtdResourceName ); + if( is != null ) + { + log( "Resolved " + publicId + " to local resource " + dtdResourceName, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + } + + URL dtdUrl = ( URL )urlDTDs.get( publicId ); + if( dtdUrl != null ) + { + try + { + InputStream is = dtdUrl.openStream(); + log( "Resolved " + publicId + " to url " + dtdUrl, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + catch( IOException ioe ) + { + //ignore + } + } + + log( "Could not resolve ( publicId: " + publicId + ", systemId: " + systemId + ") to a local entity", + Project.MSG_INFO ); + + return null; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java new file mode 100644 index 000000000..3798629fc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.XSLTLiaison; +import org.apache.xalan.xslt.XSLTInputSource; +import org.apache.xalan.xslt.XSLTProcessor; +import org.apache.xalan.xslt.XSLTProcessorFactory; +import org.apache.xalan.xslt.XSLTResultTarget; + +/** + * Concrete liaison for Xalan 1.x API. + * + * @author Sam Ruby + * @author Stephane Bailliez + */ +public class XalanLiaison implements XSLTLiaison +{ + + protected XSLTProcessor processor; + protected File stylesheet; + + public XalanLiaison() + throws Exception + { + processor = XSLTProcessorFactory.getProcessor(); + } + + public void setOutputtype( String type ) + throws Exception + { + if( !type.equals( "xml" ) ) + throw new BuildException( "Unsupported output type: " + type ); + } + + public void setStylesheet( File stylesheet ) + throws Exception + { + this.stylesheet = stylesheet; + } + + public void addParam( String name, String value ) + { + processor.setStylesheetParam( name, value ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileInputStream fis = null; + FileOutputStream fos = null; + FileInputStream xslStream = null; + try + { + xslStream = new FileInputStream( stylesheet ); + fis = new FileInputStream( infile ); + fos = new FileOutputStream( outfile ); + // systemid such as file:/// + getAbsolutePath() are considered + // invalid here... + XSLTInputSource xslSheet = new XSLTInputSource( xslStream ); + xslSheet.setSystemId( stylesheet.getAbsolutePath() ); + XSLTInputSource src = new XSLTInputSource( fis ); + src.setSystemId( infile.getAbsolutePath() ); + XSLTResultTarget res = new XSLTResultTarget( fos ); + processor.process( src, xslSheet, res ); + } + finally + { + // make sure to close all handles, otherwise the garbage + // collector will close them...whenever possible and + // Windows may complain about not being able to delete files. + try + { + if( xslStream != null ) + { + xslStream.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fis != null ) + { + fis.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fos != null ) + { + fos.close(); + } + } + catch( IOException ignored ) + {} + } + } +}//-- XalanLiaison diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java new file mode 100644 index 000000000..7ae71c808 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import com.kvisco.xsl.XSLProcessor; +import com.kvisco.xsl.XSLReader; +import com.kvisco.xsl.XSLStylesheet; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.XSLTLiaison; + + + +/** + * Concrete liaison for XSLP + * + * @author Sam Ruby + * @author Stephane Bailliez + */ +public class XslpLiaison implements XSLTLiaison +{ + + protected XSLProcessor processor; + protected XSLStylesheet xslSheet; + + public XslpLiaison() + { + processor = new XSLProcessor(); + // uh ?! I'm forced to do that otherwise a setProperty crashes with NPE ! + // I don't understand why the property map is static though... + // how can we do multithreading w/ multiple identical parameters ? + processor.getProperty( "dummy-to-init-properties-map" ); + } + + public void setOutputtype( String type ) + throws Exception + { + if( !type.equals( "xml" ) ) + throw new BuildException( "Unsupported output type: " + type ); + } + + public void setStylesheet( File fileName ) + throws Exception + { + XSLReader xslReader = new XSLReader(); + // a file:/// + getAbsolutePath() does not work here + // it is really the pathname + xslSheet = xslReader.read( fileName.getAbsolutePath() ); + } + + public void addParam( String name, String expression ) + { + processor.setProperty( name, expression ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileOutputStream fos = new FileOutputStream( outfile ); + // XSLP does not support encoding...we're in hot water. + OutputStreamWriter out = new OutputStreamWriter( fos, "UTF8" ); + processor.process( infile.getAbsolutePath(), xslSheet, out ); + } + +}//-- XSLPLiaison diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java new file mode 100644 index 000000000..974e06ce5 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + +/** + * Class common to all check commands (checkout, checkin,checkin default task); + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheck extends Continuus +{ + + /** + * -comment flag -- comment to attach to the file + */ + public final static String FLAG_COMMENT = "/comment"; + + /** + * -task flag -- associate checckout task with task + */ + public final static String FLAG_TASK = "/task"; + + private File _file = null; + private String _comment = null; + private String _task = null; + + public CCMCheck() + { + super(); + } + + /** + * Set the value of comment. + * + * @param v Value to assign to comment. + */ + public void setComment( String v ) + { + this._comment = v; + } + + /** + * Set the value of file. + * + * @param v Value to assign to file. + */ + public void setFile( File v ) + { + this._file = v; + } + + /** + * Set the value of task. + * + * @param v Value to assign to task. + */ + public void setTask( String v ) + { + this._task = v; + } + + /** + * Get the value of comment. + * + * @return value of comment. + */ + public String getComment() + { + return _comment; + } + + /** + * Get the value of file. + * + * @return value of file. + */ + public File getFile() + { + return _file; + } + + + /** + * Get the value of task. + * + * @return value of task. + */ + public String getTask() + { + return _task; + } + + + /** + * Executes the task.

      + * + * Builds a command line to execute ccm and then calls Exec's run method to + * execute the command line.

      + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // build the command line from what we got the format is + // ccm co /t .. files + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getCcmCommand() ); + commandLine.createArgument().setValue( getCcmAction() ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + if( getComment() != null ) + { + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( getComment() ); + } + + if( getTask() != null ) + { + cmd.createArgument().setValue( FLAG_TASK ); + cmd.createArgument().setValue( getTask() ); + }// end of if () + + if( getFile() != null ) + { + cmd.createArgument().setValue( _file.getAbsolutePath() ); + }// end of if () + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java new file mode 100644 index 000000000..ed7d1d1f1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import java.util.Date; + +/** + * Task to perform Checkin command to Continuus + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheckin extends CCMCheck +{ + + public CCMCheckin() + { + super(); + setCcmAction( COMMAND_CHECKIN ); + setComment( "Checkin " + new Date() ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java new file mode 100644 index 000000000..de5fb324d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; + +/** + * Task to perform Checkin Default task command to Continuus + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheckinDefault extends CCMCheck +{ + + public final static String DEFAULT_TASK = "default"; + + public CCMCheckinDefault() + { + super(); + setCcmAction( COMMAND_CHECKIN ); + setTask( DEFAULT_TASK ); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java new file mode 100644 index 000000000..a01d72e68 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; + +/** + * Task to perform Checkout command to Continuus + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheckout extends CCMCheck +{ + + public CCMCheckout() + { + super(); + setCcmAction( COMMAND_CHECKOUT ); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java new file mode 100644 index 000000000..f4ed8a11a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java @@ -0,0 +1,356 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * Task allows to create new ccm task and set it as the default + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCreateTask extends Continuus implements ExecuteStreamHandler +{ + + /** + * /comment -- comments associated to the task + */ + public final static String FLAG_COMMENT = "/synopsis"; + + /** + * /platform flag -- target platform + */ + public final static String FLAG_PLATFORM = "/plat"; + + /** + * /resolver flag + */ + public final static String FLAG_RESOLVER = "/resolver"; + + /** + * /release flag + */ + public final static String FLAG_RELEASE = "/release"; + + /** + * /release flag + */ + public final static String FLAG_SUBSYSTEM = "/subsystem"; + + /** + * -task flag -- associate checckout task with task + */ + public final static String FLAG_TASK = "/task"; + + private String comment = null; + private String platform = null; + private String resolver = null; + private String release = null; + private String subSystem = null; + private String task = null; + + public CCMCreateTask() + { + super(); + setCcmAction( COMMAND_CREATE_TASK ); + } + + /** + * Set the value of comment. + * + * @param v Value to assign to comment. + */ + public void setComment( String v ) + { + this.comment = v; + } + + /** + * Set the value of platform. + * + * @param v Value to assign to platform. + */ + public void setPlatform( String v ) + { + this.platform = v; + } + + /** + * @param is The new ProcessErrorStream value + * @exception IOException Description of Exception + */ + public void setProcessErrorStream( InputStream is ) + throws IOException + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + String s = reader.readLine(); + if( s != null ) + { + log( "err " + s, Project.MSG_DEBUG ); + }// end of if () + } + + /** + * @param param1 + * @exception IOException Description of Exception + */ + public void setProcessInputStream( OutputStream param1 ) + throws IOException { } + + /** + * read the output stream to retrieve the new task number. + * + * @param is InputStream + * @exception IOException Description of Exception + */ + public void setProcessOutputStream( InputStream is ) + throws IOException + { + + String buffer = ""; + try + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + buffer = reader.readLine(); + if( buffer != null ) + { + log( "buffer:" + buffer, Project.MSG_DEBUG ); + String taskstring = buffer.substring( buffer.indexOf( ' ' ) ).trim(); + taskstring = taskstring.substring( 0, taskstring.lastIndexOf( ' ' ) ).trim(); + setTask( taskstring ); + log( "task is " + getTask(), Project.MSG_DEBUG ); + }// end of if () + } + catch( NullPointerException npe ) + { + log( "error procession stream , null pointer exception", Project.MSG_ERR ); + npe.printStackTrace(); + throw new BuildException( npe.getClass().getName() ); + }// end of catch + catch( Exception e ) + { + log( "error procession stream " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( e.getMessage() ); + }// end of try-catch + + } + + /** + * Set the value of release. + * + * @param v Value to assign to release. + */ + public void setRelease( String v ) + { + this.release = v; + } + + /** + * Set the value of resolver. + * + * @param v Value to assign to resolver. + */ + public void setResolver( String v ) + { + this.resolver = v; + } + + /** + * Set the value of subSystem. + * + * @param v Value to assign to subSystem. + */ + public void setSubSystem( String v ) + { + this.subSystem = v; + } + + /** + * Set the value of task. + * + * @param v Value to assign to task. + */ + public void setTask( String v ) + { + this.task = v; + } + + + /** + * Get the value of comment. + * + * @return value of comment. + */ + public String getComment() + { + return comment; + } + + + /** + * Get the value of platform. + * + * @return value of platform. + */ + public String getPlatform() + { + return platform; + } + + + /** + * Get the value of release. + * + * @return value of release. + */ + public String getRelease() + { + return release; + } + + + /** + * Get the value of resolver. + * + * @return value of resolver. + */ + public String getResolver() + { + return resolver; + } + + /** + * Get the value of subSystem. + * + * @return value of subSystem. + */ + public String getSubSystem() + { + return subSystem; + } + + + /** + * Get the value of task. + * + * @return value of task. + */ + public String getTask() + { + return task; + } + + + /** + * Executes the task.

      + * + * Builds a command line to execute ccm and then calls Exec's run method to + * execute the command line.

      + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // build the command line from what we got the format + // as specified in the CCM.EXE help + commandLine.setExecutable( getCcmCommand() ); + commandLine.createArgument().setValue( getCcmAction() ); + + checkOptions( commandLine ); + + result = run( commandLine, this ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + + //create task ok, set this task as the default one + Commandline commandLine2 = new Commandline(); + commandLine2.setExecutable( getCcmCommand() ); + commandLine2.createArgument().setValue( COMMAND_DEFAULT_TASK ); + commandLine2.createArgument().setValue( getTask() ); + + log( commandLine.toString(), Project.MSG_DEBUG ); + + result = run( commandLine2 ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine2.toString(); + throw new BuildException( msg, location ); + } + + } + + + // implementation of org.apache.tools.ant.taskdefs.ExecuteStreamHandler interface + + /** + * @exception IOException Description of Exception + */ + public void start() + throws IOException { } + + /** + */ + public void stop() { } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + if( getComment() != null ) + { + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( "\"" + getComment() + "\"" ); + } + + if( getPlatform() != null ) + { + cmd.createArgument().setValue( FLAG_PLATFORM ); + cmd.createArgument().setValue( getPlatform() ); + }// end of if () + + if( getResolver() != null ) + { + cmd.createArgument().setValue( FLAG_RESOLVER ); + cmd.createArgument().setValue( getResolver() ); + }// end of if () + + if( getSubSystem() != null ) + { + cmd.createArgument().setValue( FLAG_SUBSYSTEM ); + cmd.createArgument().setValue( "\"" + getSubSystem() + "\"" ); + }// end of if () + + if( getRelease() != null ) + { + cmd.createArgument().setValue( FLAG_RELEASE ); + cmd.createArgument().setValue( getRelease() ); + }// end of if () + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java new file mode 100644 index 000000000..d079f2ae7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + +/** + * Task allows to reconfigure a project, recurcively or not + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMReconfigure extends Continuus +{ + + /** + * /recurse -- + */ + public final static String FLAG_RECURSE = "/recurse"; + + /** + * /recurse -- + */ + public final static String FLAG_VERBOSE = "/verbose"; + + /** + * /project flag -- target project + */ + public final static String FLAG_PROJECT = "/project"; + + private String project = null; + private boolean recurse = false; + private boolean verbose = false; + + public CCMReconfigure() + { + super(); + setCcmAction( COMMAND_RECONFIGURE ); + } + + /** + * Set the value of project. + * + * @param v Value to assign to project. + */ + public void setCcmProject( String v ) + { + this.project = v; + } + + /** + * Set the value of recurse. + * + * @param v Value to assign to recurse. + */ + public void setRecurse( boolean v ) + { + this.recurse = v; + } + + /** + * Set the value of verbose. + * + * @param v Value to assign to verbose. + */ + public void setVerbose( boolean v ) + { + this.verbose = v; + } + + /** + * Get the value of project. + * + * @return value of project. + */ + public String getCcmProject() + { + return project; + } + + + /** + * Get the value of recurse. + * + * @return value of recurse. + */ + public boolean isRecurse() + { + return recurse; + } + + + /** + * Get the value of verbose. + * + * @return value of verbose. + */ + public boolean isVerbose() + { + return verbose; + } + + + /** + * Executes the task.

      + * + * Builds a command line to execute ccm and then calls Exec's run method to + * execute the command line.

      + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // build the command line from what we got the format + // as specified in the CCM.EXE help + commandLine.setExecutable( getCcmCommand() ); + commandLine.createArgument().setValue( getCcmAction() ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + + if( isRecurse() == true ) + { + cmd.createArgument().setValue( FLAG_RECURSE ); + }// end of if () + + if( isVerbose() == true ) + { + cmd.createArgument().setValue( FLAG_VERBOSE ); + }// end of if () + + if( getCcmProject() != null ) + { + cmd.createArgument().setValue( FLAG_PROJECT ); + cmd.createArgument().setValue( getCcmProject() ); + } + + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java new file mode 100644 index 000000000..efc30650d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * A base class for creating tasks for executing commands on Continuus 5.1

      + * + * The class extends the task as it operates by executing the ccm.exe program + * supplied with Continuus/Synergy. By default the task expects the ccm + * executable to be in the path, you can override this be specifying the ccmdir + * attribute.

      + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public abstract class Continuus extends Task +{ + + /** + * Constant for the thing to execute + */ + private final static String CCM_EXE = "ccm"; + + /** + * The 'CreateTask' command + */ + public final static String COMMAND_CREATE_TASK = "create_task"; + /** + * The 'Checkout' command + */ + public final static String COMMAND_CHECKOUT = "co"; + /** + * The 'Checkin' command + */ + public final static String COMMAND_CHECKIN = "ci"; + /** + * The 'Reconfigure' command + */ + public final static String COMMAND_RECONFIGURE = "reconfigure"; + + /** + * The 'Reconfigure' command + */ + public final static String COMMAND_DEFAULT_TASK = "default_task"; + + private String _ccmDir = ""; + private String _ccmAction = ""; + + + /** + * Set the directory where the ccm executable is located + * + * @param dir the directory containing the ccm executable + */ + public final void setCcmDir( String dir ) + { + _ccmDir = project.translatePath( dir ); + } + + /** + * Set the value of ccmAction. + * + * @param v Value to assign to ccmAction. + */ + public void setCcmAction( String v ) + { + this._ccmAction = v; + } + + /** + * Get the value of ccmAction. + * + * @return value of ccmAction. + */ + public String getCcmAction() + { + return _ccmAction; + } + + /** + * Builds and returns the command string to execute ccm + * + * @return String containing path to the executable + */ + protected final String getCcmCommand() + { + String toReturn = _ccmDir; + if( !toReturn.equals( "" ) && !toReturn.endsWith( "/" ) ) + { + toReturn += "/"; + } + + toReturn += CCM_EXE; + + return toReturn; + } + + + protected int run( Commandline cmd, ExecuteStreamHandler handler ) + { + try + { + Execute exe = new Execute( handler ); + exe.setAntRun( getProject() ); + exe.setWorkingDirectory( getProject().getBaseDir() ); + exe.setCommandline( cmd.getCommandline() ); + return exe.execute(); + } + catch( java.io.IOException e ) + { + throw new BuildException( e ); + } + } + + protected int run( Commandline cmd ) + { + return run( cmd, new LogStreamHandler( this, Project.MSG_VERBOSE, Project.MSG_WARN ) ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java new file mode 100644 index 000000000..e4104295a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java @@ -0,0 +1,451 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform Checkin command to ClearCase.

      + * + * The following attributes are interpreted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * comment + * + * + * + * Specify a comment. Only one of comment or cfile may be used. + * + * + * + * No + * + * + * + * + * + * + * + * commentfile + * + * + * + * Specify a file containing a comment. Only one of comment or + * cfile may be used. + * + * + * + * No + * + * + * + * + * + * + * + * nowarn + * + * + * + * Suppress warning messages + * + * + * + * No + * + * + * + * + * + * + * + * preservetime + * + * + * + * Preserve the modification time + * + * + * + * No + * + * + * + * + * + * + * + * keepcopy + * + * + * + * Keeps a copy of the file with a .keep extension + * + * + * + * + * No + * + * + * + * + * + * + * + * identical + * + * + * + * Allows the file to be checked in even if it is + * identical to the original + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCCheckin extends ClearCase +{ + + /** + * -c flag -- comment to attach to the file + */ + public final static String FLAG_COMMENT = "-c"; + /** + * -cfile flag -- file containing a comment to attach to the file + */ + public final static String FLAG_COMMENTFILE = "-cfile"; + /** + * -nc flag -- no comment is specified + */ + public final static String FLAG_NOCOMMENT = "-nc"; + /** + * -nwarn flag -- suppresses warning messages + */ + public final static String FLAG_NOWARN = "-nwarn"; + /** + * -ptime flag -- preserves the modification time + */ + public final static String FLAG_PRESERVETIME = "-ptime"; + /** + * -keep flag -- keeps a copy of the file with a .keep extension + */ + public final static String FLAG_KEEPCOPY = "-keep"; + /** + * -identical flag -- allows the file to be checked in even if it is + * identical to the original + */ + public final static String FLAG_IDENTICAL = "-identical"; + private String m_Comment = null; + private String m_Cfile = null; + private boolean m_Nwarn = false; + private boolean m_Ptime = false; + private boolean m_Keep = false; + private boolean m_Identical = true; + + + /** + * Set comment string + * + * @param comment the comment string + */ + public void setComment( String comment ) + { + m_Comment = comment; + } + + /** + * Set comment file + * + * @param cfile the path to the comment file + */ + public void setCommentFile( String cfile ) + { + m_Cfile = cfile; + } + + /** + * Set the identical flag + * + * @param identical the status to set the flag to + */ + public void setIdentical( boolean identical ) + { + m_Identical = identical; + } + + /** + * Set the keepcopy flag + * + * @param keep the status to set the flag to + */ + public void setKeepCopy( boolean keep ) + { + m_Keep = keep; + } + + /** + * Set the nowarn flag + * + * @param nwarn the status to set the flag to + */ + public void setNoWarn( boolean nwarn ) + { + m_Nwarn = nwarn; + } + + /** + * Set preservetime flag + * + * @param ptime the status to set the flag to + */ + public void setPreserveTime( boolean ptime ) + { + m_Ptime = ptime; + } + + /** + * Get comment string + * + * @return String containing the comment + */ + public String getComment() + { + return m_Comment; + } + + /** + * Get comment file + * + * @return String containing the path to the comment file + */ + public String getCommentFile() + { + return m_Cfile; + } + + /** + * Get identical flag status + * + * @return boolean containing status of identical flag + */ + public boolean getIdentical() + { + return m_Identical; + } + + /** + * Get keepcopy flag status + * + * @return boolean containing status of keepcopy flag + */ + public boolean getKeepCopy() + { + return m_Keep; + } + + /** + * Get nowarn flag status + * + * @return boolean containing status of nwarn flag + */ + public boolean getNoWarn() + { + return m_Nwarn; + } + + /** + * Get preservetime flag status + * + * @return boolean containing status of preservetime flag + */ + public boolean getPreserveTime() + { + return m_Ptime; + } + + /** + * Executes the task.

      + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got. the format is + // cleartool checkin [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_CHECKIN ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Get the 'comment' command + * + * @param cmd Description of Parameter + */ + private void getCommentCommand( Commandline cmd ) + { + if( getComment() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( getComment() ); + } + } + + /** + * Get the 'commentfile' command + * + * @param cmd Description of Parameter + */ + private void getCommentFileCommand( Commandline cmd ) + { + if( getCommentFile() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENTFILE ); + cmd.createArgument().setValue( getCommentFile() ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + if( getComment() != null ) + { + // -c + getCommentCommand( cmd ); + } + else + { + if( getCommentFile() != null ) + { + // -cfile + getCommentFileCommand( cmd ); + } + else + { + cmd.createArgument().setValue( FLAG_NOCOMMENT ); + } + } + + if( getNoWarn() ) + { + // -nwarn + cmd.createArgument().setValue( FLAG_NOWARN ); + } + + if( getPreserveTime() ) + { + // -ptime + cmd.createArgument().setValue( FLAG_PRESERVETIME ); + } + + if( getKeepCopy() ) + { + // -keep + cmd.createArgument().setValue( FLAG_KEEPCOPY ); + } + + if( getIdentical() ) + { + // -identical + cmd.createArgument().setValue( FLAG_IDENTICAL ); + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java new file mode 100644 index 000000000..7c649bc5b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java @@ -0,0 +1,602 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform Checkout command to ClearCase.

      + * + * The following attributes are interpretted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * reserved + * + * + * + * Specifies whether to check out the file as reserved or not + * + * + * + * Yes + * + * + * + * + * + * + * + * out + * + * + * + * Creates a writable file under a different filename + * + * + * + * No + * + * + * + * + * + * + * + * nodata + * + * + * + * Checks out the file but does not create an editable file + * containing its data + * + * + * + * No + * + * + * + * + * + * + * + * branch + * + * + * + * Specify a branch to check out the file to + * + * + * + * No + * + * + * + * + * + * + * + * version + * + * + * + * Allows checkout of a version other than main latest + * + * + * + * + * No + * + * + * + * + * + * + * + * nowarn + * + * + * + * Suppress warning messages + * + * + * + * No + * + * + * + * + * + * + * + * comment + * + * + * + * Specify a comment. Only one of comment or + * cfile may be used. + * + * + * + * No + * + * + * + * + * + * + * + * commentfile + * + * + * + * Specify a file containing a comment. + * Only one of comment or cfile may be + * used. + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCCheckout extends ClearCase +{ + + /** + * -reserved flag -- check out the file as reserved + */ + public final static String FLAG_RESERVED = "-reserved"; + /** + * -reserved flag -- check out the file as unreserved + */ + public final static String FLAG_UNRESERVED = "-unreserved"; + /** + * -out flag -- create a writable file under a different filename + */ + public final static String FLAG_OUT = "-out"; + /** + * -ndata flag -- checks out the file but does not create an editable file + * containing its data + */ + public final static String FLAG_NODATA = "-ndata"; + /** + * -branch flag -- checks out the file on a specified branch + */ + public final static String FLAG_BRANCH = "-branch"; + /** + * -version flag -- allows checkout of a version that is not main latest + */ + public final static String FLAG_VERSION = "-version"; + /** + * -nwarn flag -- suppresses warning messages + */ + public final static String FLAG_NOWARN = "-nwarn"; + /** + * -c flag -- comment to attach to the file + */ + public final static String FLAG_COMMENT = "-c"; + /** + * -cfile flag -- file containing a comment to attach to the file + */ + public final static String FLAG_COMMENTFILE = "-cfile"; + /** + * -nc flag -- no comment is specified + */ + public final static String FLAG_NOCOMMENT = "-nc"; + private boolean m_Reserved = true; + private String m_Out = null; + private boolean m_Ndata = false; + private String m_Branch = null; + private boolean m_Version = false; + private boolean m_Nwarn = false; + private String m_Comment = null; + private String m_Cfile = null; + + /** + * Set branch name + * + * @param branch the name of the branch + */ + public void setBranch( String branch ) + { + m_Branch = branch; + } + + /** + * Set comment string + * + * @param comment the comment string + */ + public void setComment( String comment ) + { + m_Comment = comment; + } + + /** + * Set comment file + * + * @param cfile the path to the comment file + */ + public void setCommentFile( String cfile ) + { + m_Cfile = cfile; + } + + /** + * Set the nodata flag + * + * @param ndata the status to set the flag to + */ + public void setNoData( boolean ndata ) + { + m_Ndata = ndata; + } + + /** + * Set the nowarn flag + * + * @param nwarn the status to set the flag to + */ + public void setNoWarn( boolean nwarn ) + { + m_Nwarn = nwarn; + } + + /** + * Set out file + * + * @param outf the path to the out file + */ + public void setOut( String outf ) + { + m_Out = outf; + } + + /** + * Set reserved flag status + * + * @param reserved the status to set the flag to + */ + public void setReserved( boolean reserved ) + { + m_Reserved = reserved; + } + + /** + * Set the version flag + * + * @param version the status to set the flag to + */ + public void setVersion( boolean version ) + { + m_Version = version; + } + + /** + * Get branch name + * + * @return String containing the name of the branch + */ + public String getBranch() + { + return m_Branch; + } + + /** + * Get comment string + * + * @return String containing the comment + */ + public String getComment() + { + return m_Comment; + } + + /** + * Get comment file + * + * @return String containing the path to the comment file + */ + public String getCommentFile() + { + return m_Cfile; + } + + /** + * Get nodata flag status + * + * @return boolean containing status of ndata flag + */ + public boolean getNoData() + { + return m_Ndata; + } + + /** + * Get nowarn flag status + * + * @return boolean containing status of nwarn flag + */ + public boolean getNoWarn() + { + return m_Nwarn; + } + + /** + * Get out file + * + * @return String containing the path to the out file + */ + public String getOut() + { + return m_Out; + } + + /** + * Get reserved flag status + * + * @return boolean containing status of reserved flag + */ + public boolean getReserved() + { + return m_Reserved; + } + + /** + * Get version flag status + * + * @return boolean containing status of version flag + */ + public boolean getVersion() + { + return m_Version; + } + + /** + * Executes the task.

      + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got the format is + // cleartool checkout [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_CHECKOUT ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + /** + * Get the 'branch' command + * + * @param cmd Description of Parameter + */ + private void getBranchCommand( Commandline cmd ) + { + if( getBranch() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_BRANCH ); + cmd.createArgument().setValue( getBranch() ); + } + } + + + /** + * Get the 'comment' command + * + * @param cmd Description of Parameter + */ + private void getCommentCommand( Commandline cmd ) + { + if( getComment() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( getComment() ); + } + } + + /** + * Get the 'cfile' command + * + * @param cmd Description of Parameter + */ + private void getCommentFileCommand( Commandline cmd ) + { + if( getCommentFile() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENTFILE ); + cmd.createArgument().setValue( getCommentFile() ); + } + } + + /** + * Get the 'out' command + * + * @param cmd Description of Parameter + */ + private void getOutCommand( Commandline cmd ) + { + if( getOut() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_OUT ); + cmd.createArgument().setValue( getOut() ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + // ClearCase items + if( getReserved() ) + { + // -reserved + cmd.createArgument().setValue( FLAG_RESERVED ); + } + else + { + // -unreserved + cmd.createArgument().setValue( FLAG_UNRESERVED ); + } + + if( getOut() != null ) + { + // -out + getOutCommand( cmd ); + } + else + { + if( getNoData() ) + { + // -ndata + cmd.createArgument().setValue( FLAG_NODATA ); + } + + } + + if( getBranch() != null ) + { + // -branch + getBranchCommand( cmd ); + } + else + { + if( getVersion() ) + { + // -version + cmd.createArgument().setValue( FLAG_VERSION ); + } + + } + + if( getNoWarn() ) + { + // -nwarn + cmd.createArgument().setValue( FLAG_NOWARN ); + } + + if( getComment() != null ) + { + // -c + getCommentCommand( cmd ); + } + else + { + if( getCommentFile() != null ) + { + // -cfile + getCommentFileCommand( cmd ); + } + else + { + cmd.createArgument().setValue( FLAG_NOCOMMENT ); + } + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java new file mode 100644 index 000000000..02a442f2c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform UnCheckout command to ClearCase.

      + * + * The following attributes are interpretted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * keepcopy + * + * + * + * Specifies whether to keep a copy of the file with a .keep extension + * or not + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCUnCheckout extends ClearCase +{ + + /** + * -keep flag -- keep a copy of the file with .keep extension + */ + public final static String FLAG_KEEPCOPY = "-keep"; + /** + * -rm flag -- remove the copy of the file + */ + public final static String FLAG_RM = "-rm"; + private boolean m_Keep = false; + + /** + * Set keepcopy flag status + * + * @param keep the status to set the flag to + */ + public void setKeepCopy( boolean keep ) + { + m_Keep = keep; + } + + /** + * Get keepcopy flag status + * + * @return boolean containing status of keep flag + */ + public boolean getKeepCopy() + { + return m_Keep; + } + + /** + * Executes the task.

      + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got the format is + // cleartool uncheckout [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_UNCHECKOUT ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + // ClearCase items + if( getKeepCopy() ) + { + // -keep + cmd.createArgument().setValue( FLAG_KEEPCOPY ); + } + else + { + // -rm + cmd.createArgument().setValue( FLAG_RM ); + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java new file mode 100644 index 000000000..2d88f71c4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform an Update command to ClearCase.

      + * + * The following attributes are interpretted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * graphical + * + * + * + * Displays a graphical dialog during the update + * + * + * + * No + * + * + * + * + * + * + * + * log + * + * + * + * Specifies a log file for ClearCase to write to + * + * + * + * No + * + * + * + * + * + * + * + * overwrite + * + * + * + * Specifies whether to overwrite hijacked files or not + * + * + * + * No + * + * + * + * + * + * + * + * rename + * + * + * + * Specifies that hijacked files should be renamed with a + * .keep extension + * + * + * + * No + * + * + * + * + * + * + * + * currenttime + * + * + * + * Specifies that modification time should be written + * as the current time. Either currenttime or + * preservetime can be specified. + * + * + * + * No + * + * + * + * + * + * + * + * preservetime + * + * + * + * Specifies that modification time should + * preserved from the VOB time. Either currenttime + * or preservetime can be specified. + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCUpdate extends ClearCase +{ + + /** + * -graphical flag -- display graphical dialog during update operation + */ + public final static String FLAG_GRAPHICAL = "-graphical"; + /** + * -log flag -- file to log status to + */ + public final static String FLAG_LOG = "-log"; + /** + * -overwrite flag -- overwrite hijacked files + */ + public final static String FLAG_OVERWRITE = "-overwrite"; + /** + * -noverwrite flag -- do not overwrite hijacked files + */ + public final static String FLAG_NOVERWRITE = "-noverwrite"; + /** + * -rename flag -- rename hijacked files with .keep extension + */ + public final static String FLAG_RENAME = "-rename"; + /** + * -ctime flag -- modified time is written as the current time + */ + public final static String FLAG_CURRENTTIME = "-ctime"; + /** + * -ptime flag -- modified time is written as the VOB time + */ + public final static String FLAG_PRESERVETIME = "-ptime"; + private boolean m_Graphical = false; + private boolean m_Overwrite = false; + private boolean m_Rename = false; + private boolean m_Ctime = false; + private boolean m_Ptime = false; + private String m_Log = null; + + /** + * Set modified time based on current time + * + * @param ct the status to set the flag to + */ + public void setCurrentTime( boolean ct ) + { + m_Ctime = ct; + } + + /** + * Set graphical flag status + * + * @param graphical the status to set the flag to + */ + public void setGraphical( boolean graphical ) + { + m_Graphical = graphical; + } + + /** + * Set log file where cleartool can record the status of the command + * + * @param log the path to the log file + */ + public void setLog( String log ) + { + m_Log = log; + } + + /** + * Set overwrite hijacked files status + * + * @param ow the status to set the flag to + */ + public void setOverwrite( boolean ow ) + { + m_Overwrite = ow; + } + + /** + * Preserve modified time from the VOB time + * + * @param pt the status to set the flag to + */ + public void setPreserveTime( boolean pt ) + { + m_Ptime = pt; + } + + /** + * Set rename hijacked files status + * + * @param ren the status to set the flag to + */ + public void setRename( boolean ren ) + { + m_Rename = ren; + } + + /** + * Get current time status + * + * @return boolean containing status of current time flag + */ + public boolean getCurrentTime() + { + return m_Ctime; + } + + /** + * Get graphical flag status + * + * @return boolean containing status of graphical flag + */ + public boolean getGraphical() + { + return m_Graphical; + } + + /** + * Get log file + * + * @return String containing the path to the log file + */ + public String getLog() + { + return m_Log; + } + + /** + * Get overwrite hijacked files status + * + * @return boolean containing status of overwrite flag + */ + public boolean getOverwrite() + { + return m_Overwrite; + } + + /** + * Get preserve time status + * + * @return boolean containing status of preserve time flag + */ + public boolean getPreserveTime() + { + return m_Ptime; + } + + /** + * Get rename hijacked files status + * + * @return boolean containing status of rename flag + */ + public boolean getRename() + { + return m_Rename; + } + + /** + * Executes the task.

      + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got the format is + // cleartool update [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_UPDATE ); + + // Check the command line options + checkOptions( commandLine ); + + // For debugging + System.out.println( commandLine.toString() ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + /** + * Get the 'log' command + * + * @param cmd Description of Parameter + */ + private void getLogCommand( Commandline cmd ) + { + if( getLog() == null ) + { + return; + } + else + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_LOG ); + cmd.createArgument().setValue( getLog() ); + } + } + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + // ClearCase items + if( getGraphical() ) + { + // -graphical + cmd.createArgument().setValue( FLAG_GRAPHICAL ); + } + else + { + if( getOverwrite() ) + { + // -overwrite + cmd.createArgument().setValue( FLAG_OVERWRITE ); + } + else + { + if( getRename() ) + { + // -rename + cmd.createArgument().setValue( FLAG_RENAME ); + } + else + { + // -noverwrite + cmd.createArgument().setValue( FLAG_NOVERWRITE ); + } + } + + if( getCurrentTime() ) + { + // -ctime + cmd.createArgument().setValue( FLAG_CURRENTTIME ); + } + else + { + if( getPreserveTime() ) + { + // -ptime + cmd.createArgument().setValue( FLAG_PRESERVETIME ); + } + } + + // -log logname + getLogCommand( cmd ); + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java new file mode 100644 index 000000000..917152f39 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * A base class for creating tasks for executing commands on ClearCase.

      + * + * The class extends the 'exec' task as it operates by executing the cleartool + * program supplied with ClearCase. By default the task expects the cleartool + * executable to be in the path, * you can override this be specifying the + * cleartooldir attribute.

      + * + * This class provides set and get methods for the 'viewpath' attribute. It also + * contains constants for the flags that can be passed to cleartool.

      + * + * @author Curtis White + */ +public abstract class ClearCase extends Task +{ + + /** + * Constant for the thing to execute + */ + private final static String CLEARTOOL_EXE = "cleartool"; + + /** + * The 'Update' command + */ + public final static String COMMAND_UPDATE = "update"; + /** + * The 'Checkout' command + */ + public final static String COMMAND_CHECKOUT = "checkout"; + /** + * The 'Checkin' command + */ + public final static String COMMAND_CHECKIN = "checkin"; + /** + * The 'UndoCheckout' command + */ + public final static String COMMAND_UNCHECKOUT = "uncheckout"; + private String m_ClearToolDir = ""; + private String m_viewPath = null; + + /** + * Set the directory where the cleartool executable is located + * + * @param dir the directory containing the cleartool executable + */ + public final void setClearToolDir( String dir ) + { + m_ClearToolDir = project.translatePath( dir ); + } + + /** + * Set the path to the item in a clearcase view to operate on + * + * @param viewPath Path to the view directory or file + */ + public final void setViewPath( String viewPath ) + { + m_viewPath = viewPath; + } + + /** + * Get the path to the item in a clearcase view + * + * @return m_viewPath + */ + public String getViewPath() + { + return m_viewPath; + } + + /** + * Builds and returns the command string to execute cleartool + * + * @return String containing path to the executable + */ + protected final String getClearToolCommand() + { + String toReturn = m_ClearToolDir; + if( !toReturn.equals( "" ) && !toReturn.endsWith( "/" ) ) + { + toReturn += "/"; + } + + toReturn += CLEARTOOL_EXE; + + return toReturn; + } + + + protected int run( Commandline cmd ) + { + try + { + Project aProj = getProject(); + Execute exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ) ); + exe.setAntRun( aProj ); + exe.setWorkingDirectory( aProj.getBaseDir() ); + exe.setCommandline( cmd.getCommandline() ); + return exe.execute(); + } + catch( java.io.IOException e ) + { + throw new BuildException( e ); + } + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java new file mode 100644 index 000000000..f0479ae8c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; +import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ClassCPInfo; +import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ConstantPool; +import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ConstantPoolEntry; + +/** + * A ClassFile object stores information about a Java class. The class may be + * read from a DataInputStream.and written to a DataOutputStream. These are + * usually streams from a Java class file or a class file component of a Jar + * file. + * + * @author Conor MacNeill + */ +public class ClassFile +{ + + /** + * The Magic Value that marks the start of a Java class file + */ + private final static int CLASS_MAGIC = 0xCAFEBABE; + + /** + * The class name for this class. + */ + private String className; + + /** + * This class' constant pool. + */ + private ConstantPool constantPool; + + + /** + * Get the classes which this class references. + * + * @return The ClassRefs value + */ + public Vector getClassRefs() + { + + Vector classRefs = new Vector(); + + for( int i = 0; i < constantPool.size(); ++i ) + { + ConstantPoolEntry entry = constantPool.getEntry( i ); + + if( entry != null && entry.getTag() == ConstantPoolEntry.CONSTANT_Class ) + { + ClassCPInfo classEntry = ( ClassCPInfo )entry; + + if( !classEntry.getClassName().equals( className ) ) + { + classRefs.addElement( ClassFileUtils.convertSlashName( classEntry.getClassName() ) ); + } + } + } + + return classRefs; + } + + /** + * Get the class' fully qualified name in dot format. + * + * @return the class name in dot format (eg. java.lang.Object) + */ + public String getFullClassName() + { + return ClassFileUtils.convertSlashName( className ); + } + + /** + * Read the class from a data stream. This method takes an InputStream as + * input and parses the class from the stream.

      + * + * + * + * @param stream an InputStream from which the class will be read + * @throws IOException if there is a problem reading from the given stream. + * @throws ClassFormatError if the class cannot be parsed correctly + */ + public void read( InputStream stream ) + throws IOException, ClassFormatError + { + DataInputStream classStream = new DataInputStream( stream ); + + if( classStream.readInt() != CLASS_MAGIC ) + { + throw new ClassFormatError( "No Magic Code Found - probably not a Java class file." ); + } + + // right we have a good looking class file. + int minorVersion = classStream.readUnsignedShort(); + int majorVersion = classStream.readUnsignedShort(); + + // read the constant pool in and resolve it + constantPool = new ConstantPool(); + + constantPool.read( classStream ); + constantPool.resolve(); + + int accessFlags = classStream.readUnsignedShort(); + int thisClassIndex = classStream.readUnsignedShort(); + int superClassIndex = classStream.readUnsignedShort(); + className = ( ( ClassCPInfo )constantPool.getEntry( thisClassIndex ) ).getClassName(); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java new file mode 100644 index 000000000..de4ad9499 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; + + +public interface ClassFileIterator +{ + + ClassFile getNextClassFile(); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java new file mode 100644 index 000000000..054ef68f8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; + +/** + * Utility class file routines. This class porovides a number of static utility + * methods to convert between the formats used in the Java class file format and + * those commonly used in Java programming. + * + * @author Conor MacNeill + */ +public class ClassFileUtils +{ + + /** + * Convert a class name from java source file dot notation to class file + * slash notation.. + * + * @param dotName the class name in dot notation (eg. java.lang.Object). + * @return the class name in slash notation (eg. java/lang/Object). + */ + public static String convertDotName( String dotName ) + { + return dotName.replace( '.', '/' ); + } + + /** + * Convert a class name from class file slash notation to java source file + * dot notation. + * + * @param name Description of Parameter + * @return the class name in dot notation (eg. java.lang.Object). + */ + public static String convertSlashName( String name ) + { + return name.replace( '\\', '.' ).replace( '/', '.' ); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/Depend.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/Depend.java new file mode 100644 index 000000000..62ec1812e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/Depend.java @@ -0,0 +1,764 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + + +/** + * Generate a dependency file for a given set of classes + * + * @author Conor MacNeill + */ +public class Depend extends MatchingTask +{ + + /** + * constants used with the cache file + */ + private final static String CACHE_FILE_NAME = "dependencies.txt"; + private final static String CLASSNAME_PREPEND = "||:"; + + /** + * indicates that the dependency relationships should be extended beyond + * direct dependencies to include all classes. So if A directly affects B + * abd B directly affects C, then A indirectly affects C. + */ + private boolean closure = false; + + /** + * Flag which controls whether the reversed dependencies should be dumped to + * the log + */ + private boolean dump = false; + + /** + * A map which gives for every class a list of te class which it affects. + */ + private Hashtable affectedClassMap; + + /** + * The directory which contains the dependency cache. + */ + private File cache; + + /** + * A map which gives information about a class + */ + private Hashtable classFileInfoMap; + + /** + * A map which gives the list of jars and classes from the classpath that a + * class depends upon + */ + private Hashtable classpathDependencies; + + /** + * The classpath to look for additional dependencies + */ + private Path dependClasspath; + + /** + * The path where compiled class files exist. + */ + private Path destPath; + + /** + * The list of classes which are out of date. + */ + private Hashtable outOfDateClasses; + + /** + * The path where source files exist + */ + private Path srcPath; + + public void setCache( File cache ) + { + this.cache = cache; + } + + /** + * Set the classpath to be used for this dependency check. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( dependClasspath == null ) + { + dependClasspath = classpath; + } + else + { + dependClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setClosure( boolean closure ) + { + this.closure = closure; + } + + /** + * Set the destination directory where the compiled java files exist. + * + * @param destPath The new DestDir value + */ + public void setDestDir( Path destPath ) + { + this.destPath = destPath; + } + + /** + * Flag to indicate whether the reverse dependency list should be dumped to + * debug + * + * @param dump The new Dump value + */ + public void setDump( boolean dump ) + { + this.dump = dump; + } + + + /** + * Set the source dirs to find the source Java files. + * + * @param srcPath The new Srcdir value + */ + public void setSrcdir( Path srcPath ) + { + this.srcPath = srcPath; + } + + /** + * Gets the classpath to be used for this dependency check. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return dependClasspath; + } + + /** + * Creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( dependClasspath == null ) + { + dependClasspath = new Path( project ); + } + return dependClasspath.createPath(); + } + + /** + * Does the work. + * + * @exception BuildException Thrown in unrecovrable error. + */ + public void execute() + throws BuildException + { + try + { + long start = System.currentTimeMillis(); + String[] srcPathList = srcPath.list(); + if( srcPathList.length == 0 ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + + if( destPath == null ) + { + destPath = srcPath; + } + + if( cache != null && cache.exists() && !cache.isDirectory() ) + { + throw new BuildException( "The cache, if specified, must point to a directory" ); + } + + if( cache != null && !cache.exists() ) + { + cache.mkdirs(); + } + + determineDependencies(); + + if( dump ) + { + log( "Reverse Dependency Dump for " + affectedClassMap.size() + + " classes:", Project.MSG_DEBUG ); + for( Enumeration e = affectedClassMap.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + log( " Class " + className + " affects:", Project.MSG_DEBUG ); + Hashtable affectedClasses = ( Hashtable )affectedClassMap.get( className ); + for( Enumeration e2 = affectedClasses.keys(); e2.hasMoreElements(); ) + { + String affectedClass = ( String )e2.nextElement(); + ClassFileInfo info = ( ClassFileInfo )affectedClasses.get( affectedClass ); + log( " " + affectedClass + " in " + info.absoluteFile.getPath(), Project.MSG_DEBUG ); + } + } + + if( classpathDependencies != null ) + { + log( "Classpath file dependencies (Forward):", Project.MSG_DEBUG ); + for( Enumeration e = classpathDependencies.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + log( " Class " + className + " depends on:", Project.MSG_DEBUG ); + Hashtable dependencies = ( Hashtable )classpathDependencies.get( className ); + for( Enumeration e2 = dependencies.elements(); e2.hasMoreElements(); ) + { + File classpathFile = ( File )e2.nextElement(); + log( " " + classpathFile.getPath(), Project.MSG_DEBUG ); + } + } + } + + } + + // we now need to scan for out of date files. When we have the list + // we go through and delete all class files which are affected by these files. + outOfDateClasses = new Hashtable(); + for( int i = 0; i < srcPathList.length; i++ ) + { + File srcDir = ( File )project.resolveFile( srcPathList[i] ); + if( srcDir.exists() ) + { + DirectoryScanner ds = this.getDirectoryScanner( srcDir ); + String[] files = ds.getIncludedFiles(); + scanDir( srcDir, files ); + } + } + + // now check classpath file dependencies + if( classpathDependencies != null ) + { + for( Enumeration e = classpathDependencies.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + if( !outOfDateClasses.containsKey( className ) ) + { + ClassFileInfo info = ( ClassFileInfo )classFileInfoMap.get( className ); + + // if we have no info about the class - it may have been deleted already and we + // are using cached info. + if( info != null ) + { + Hashtable dependencies = ( Hashtable )classpathDependencies.get( className ); + for( Enumeration e2 = dependencies.elements(); e2.hasMoreElements(); ) + { + File classpathFile = ( File )e2.nextElement(); + if( classpathFile.lastModified() > info.absoluteFile.lastModified() ) + { + log( "Class " + className + + " is out of date with respect to " + classpathFile, Project.MSG_DEBUG ); + outOfDateClasses.put( className, className ); + break; + } + } + } + } + } + } + + // we now have a complete list of classes which are out of date + // We scan through the affected classes, deleting any affected classes. + int count = deleteAllAffectedFiles(); + + long duration = ( System.currentTimeMillis() - start ) / 1000; + log( "Deleted " + count + " out of date files in " + duration + " seconds" ); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + /** + * Scans the directory looking for source files that are newer than their + * class files. The results are returned in the class variable compileList + * + * @param srcDir Description of Parameter + * @param files Description of Parameter + */ + protected void scanDir( File srcDir, String files[] ) + { + + long now = System.currentTimeMillis(); + + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".java" ) ) + { + String filePath = srcFile.getPath(); + String className = filePath.substring( srcDir.getPath().length() + 1, + filePath.length() - ".java".length() ); + className = ClassFileUtils.convertSlashName( className ); + ClassFileInfo info = ( ClassFileInfo )classFileInfoMap.get( className ); + if( info == null ) + { + // there was no class file. add this class to the list + outOfDateClasses.put( className, className ); + } + else + { + if( srcFile.lastModified() > info.absoluteFile.lastModified() ) + { + outOfDateClasses.put( className, className ); + } + } + } + } + } + + + /** + * Get the list of class files we are going to analyse. + * + * @param classLocations a path structure containing all the directories + * where classes can be found. + * @return a vector containing the classes to analyse. + */ + private Vector getClassFiles( Path classLocations ) + { + // break the classLocations into its components. + String[] classLocationsList = classLocations.list(); + + Vector classFileList = new Vector(); + + for( int i = 0; i < classLocationsList.length; ++i ) + { + File dir = new File( classLocationsList[i] ); + if( dir.isDirectory() ) + { + addClassFiles( classFileList, dir, dir ); + } + } + + return classFileList; + } + + /** + * Add the list of class files from the given directory to the class file + * vector, including any subdirectories. + * + * @param classFileList The feature to be added to the ClassFiles attribute + * @param dir The feature to be added to the ClassFiles attribute + * @param root The feature to be added to the ClassFiles attribute + */ + private void addClassFiles( Vector classFileList, File dir, File root ) + { + String[] filesInDir = dir.list(); + + if( filesInDir != null ) + { + int length = filesInDir.length; + + for( int i = 0; i < length; ++i ) + { + File file = new File( dir, filesInDir[i] ); + if( file.isDirectory() ) + { + addClassFiles( classFileList, file, root ); + } + else if( file.getName().endsWith( ".class" ) ) + { + ClassFileInfo info = new ClassFileInfo(); + info.absoluteFile = file; + info.relativeName = file.getPath().substring( root.getPath().length() + 1, + file.getPath().length() - 6 ); + info.className = ClassFileUtils.convertSlashName( info.relativeName ); + classFileList.addElement( info ); + } + } + } + } + + private int deleteAffectedFiles( String className ) + { + int count = 0; + + Hashtable affectedClasses = ( Hashtable )affectedClassMap.get( className ); + if( affectedClasses != null ) + { + for( Enumeration e = affectedClasses.keys(); e.hasMoreElements(); ) + { + String affectedClassName = ( String )e.nextElement(); + ClassFileInfo affectedClassInfo = ( ClassFileInfo )affectedClasses.get( affectedClassName ); + if( affectedClassInfo.absoluteFile.exists() ) + { + log( "Deleting file " + affectedClassInfo.absoluteFile.getPath() + " since " + + className + " out of date", Project.MSG_VERBOSE ); + affectedClassInfo.absoluteFile.delete(); + count++; + if( closure ) + { + count += deleteAffectedFiles( affectedClassName ); + } + else + { + // without closure we may delete an inner class but not the + // top level class which would not trigger a recompile. + + if( affectedClassName.indexOf( "$" ) != -1 ) + { + // need to delete the main class + String topLevelClassName + = affectedClassName.substring( 0, affectedClassName.indexOf( "$" ) ); + log( "Top level class = " + topLevelClassName, Project.MSG_VERBOSE ); + ClassFileInfo topLevelClassInfo + = ( ClassFileInfo )classFileInfoMap.get( topLevelClassName ); + if( topLevelClassInfo != null && + topLevelClassInfo.absoluteFile.exists() ) + { + log( "Deleting file " + topLevelClassInfo.absoluteFile.getPath() + " since " + + "one of its inner classes was removed", Project.MSG_VERBOSE ); + topLevelClassInfo.absoluteFile.delete(); + count++; + if( closure ) + { + count += deleteAffectedFiles( topLevelClassName ); + } + } + } + } + } + } + } + return count; + } + + private int deleteAllAffectedFiles() + { + int count = 0; + for( Enumeration e = outOfDateClasses.elements(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + count += deleteAffectedFiles( className ); + ClassFileInfo classInfo = ( ClassFileInfo )classFileInfoMap.get( className ); + if( classInfo != null && classInfo.absoluteFile.exists() ) + { + classInfo.absoluteFile.delete(); + count++; + } + } + return count; + } + + + /** + * Determine the dependencies between classes. Class dependencies are + * determined by examining the class references in a class file to other + * classes + * + * @exception IOException Description of Exception + */ + private void determineDependencies() + throws IOException + { + affectedClassMap = new Hashtable(); + classFileInfoMap = new Hashtable(); + boolean cacheDirty = false; + + Hashtable dependencyMap = new Hashtable(); + File depCacheFile = null; + boolean depCacheFileExists = true; + long depCacheFileLastModified = Long.MAX_VALUE; + + // read the dependency cache from the disk + if( cache != null ) + { + dependencyMap = readCachedDependencies(); + depCacheFile = new File( cache, CACHE_FILE_NAME ); + depCacheFileExists = depCacheFile.exists(); + depCacheFileLastModified = depCacheFile.lastModified(); + } + for( Enumeration e = getClassFiles( destPath ).elements(); e.hasMoreElements(); ) + { + ClassFileInfo info = ( ClassFileInfo )e.nextElement(); + log( "Adding class info for " + info.className, Project.MSG_DEBUG ); + classFileInfoMap.put( info.className, info ); + + Vector dependencyList = null; + + if( cache != null ) + { + // try to read the dependency info from the map if it is not out of date + if( depCacheFileExists && depCacheFileLastModified > info.absoluteFile.lastModified() ) + { + // depFile exists and is newer than the class file + // need to get dependency list from the map. + dependencyList = ( Vector )dependencyMap.get( info.className ); + } + } + + if( dependencyList == null ) + { + // not cached - so need to read directly from the class file + FileInputStream inFileStream = null; + try + { + inFileStream = new FileInputStream( info.absoluteFile ); + ClassFile classFile = new ClassFile(); + classFile.read( inFileStream ); + + dependencyList = classFile.getClassRefs(); + if( dependencyList != null ) + { + cacheDirty = true; + dependencyMap.put( info.className, dependencyList ); + } + + } + finally + { + if( inFileStream != null ) + { + inFileStream.close(); + } + } + } + + // This class depends on each class in the dependency list. For each + // one of those, add this class into their affected classes list + for( Enumeration depEnum = dependencyList.elements(); depEnum.hasMoreElements(); ) + { + String dependentClass = ( String )depEnum.nextElement(); + + Hashtable affectedClasses = ( Hashtable )affectedClassMap.get( dependentClass ); + if( affectedClasses == null ) + { + affectedClasses = new Hashtable(); + affectedClassMap.put( dependentClass, affectedClasses ); + } + + affectedClasses.put( info.className, info ); + } + } + + classpathDependencies = null; + if( dependClasspath != null ) + { + // now determine which jars each class depends upon + classpathDependencies = new Hashtable(); + AntClassLoader loader = new AntClassLoader( getProject(), dependClasspath ); + + Hashtable classpathFileCache = new Hashtable(); + Object nullFileMarker = new Object(); + for( Enumeration e = dependencyMap.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + Vector dependencyList = ( Vector )dependencyMap.get( className ); + Hashtable dependencies = new Hashtable(); + classpathDependencies.put( className, dependencies ); + for( Enumeration e2 = dependencyList.elements(); e2.hasMoreElements(); ) + { + String dependency = ( String )e2.nextElement(); + Object classpathFileObject = classpathFileCache.get( dependency ); + if( classpathFileObject == null ) + { + classpathFileObject = nullFileMarker; + + if( !dependency.startsWith( "java." ) && !dependency.startsWith( "javax." ) ) + { + URL classURL = loader.getResource( dependency.replace( '.', '/' ) + ".class" ); + if( classURL != null ) + { + if( classURL.getProtocol().equals( "jar" ) ) + { + String jarFilePath = classURL.getFile(); + if( jarFilePath.startsWith( "file:" ) ) + { + int classMarker = jarFilePath.indexOf( '!' ); + jarFilePath = jarFilePath.substring( 5, classMarker ); + } + classpathFileObject = new File( jarFilePath ); + } + else if( classURL.getProtocol().equals( "file" ) ) + { + String classFilePath = classURL.getFile(); + classpathFileObject = new File( classFilePath ); + } + log( "Class " + className + + " depends on " + classpathFileObject + + " due to " + dependency, Project.MSG_DEBUG ); + } + } + classpathFileCache.put( dependency, classpathFileObject ); + } + if( classpathFileObject != null && classpathFileObject != nullFileMarker ) + { + // we need to add this jar to the list for this class. + File jarFile = ( File )classpathFileObject; + dependencies.put( jarFile, jarFile ); + } + } + } + } + + // write the dependency cache to the disk + if( cache != null && cacheDirty ) + { + writeCachedDependencies( dependencyMap ); + } + } + + /** + * Read the dependencies from cache file + * + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + private Hashtable readCachedDependencies() + throws IOException + { + Hashtable dependencyMap = new Hashtable(); + + if( cache != null ) + { + File depFile = new File( cache, CACHE_FILE_NAME ); + BufferedReader in = null; + if( depFile.exists() ) + { + try + { + in = new BufferedReader( new FileReader( depFile ) ); + String line = null; + Vector dependencyList = null; + String className = null; + int prependLength = CLASSNAME_PREPEND.length(); + while( ( line = in.readLine() ) != null ) + { + if( line.startsWith( CLASSNAME_PREPEND ) ) + { + dependencyList = new Vector(); + className = line.substring( prependLength ); + dependencyMap.put( className, dependencyList ); + } + else + { + dependencyList.addElement( line ); + } + } + } + finally + { + if( in != null ) + { + in.close(); + } + } + } + } + + return dependencyMap; + } + + /** + * Write the dependencies to cache file + * + * @param dependencyMap Description of Parameter + * @exception IOException Description of Exception + */ + private void writeCachedDependencies( Hashtable dependencyMap ) + throws IOException + { + if( cache != null ) + { + PrintWriter pw = null; + try + { + cache.mkdirs(); + File depFile = new File( cache, CACHE_FILE_NAME ); + + pw = new PrintWriter( new FileWriter( depFile ) ); + for( Enumeration deps = dependencyMap.keys(); deps.hasMoreElements(); ) + { + String className = ( String )deps.nextElement(); + + pw.println( CLASSNAME_PREPEND + className ); + + Vector dependencyList = ( Vector )dependencyMap.get( className ); + int size = dependencyList.size(); + for( int x = 0; x < size; x++ ) + { + pw.println( dependencyList.elementAt( x ) ); + } + } + } + finally + { + if( pw != null ) + { + pw.close(); + } + } + } + } + + /** + * A class (struct) user to manage information about a class + * + * @author RT + */ + private static class ClassFileInfo + { + /** + * The file where the class file is stored in the file system + */ + public File absoluteFile; + + /** + * The Java class name of this class + */ + public String className; + + /** + * The location of the file relative to its base directory - the root of + * the package namespace + */ + public String relativeName; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java new file mode 100644 index 000000000..e003411d4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Stack; +import java.util.Vector; + +/** + * An iterator which iterates through the contents of a java directory. The + * iterator should be created with the directory at the root of the Java + * namespace. + * + * @author Conor MacNeill + */ +public class DirectoryIterator implements ClassFileIterator +{ + + /** + * The length of the root directory. This is used to remove the root + * directory from full paths. + */ + int rootLength; + + /** + * The current directory iterator. As directories encounter lower level + * directories, the current iterator is pushed onto the iterator stack and a + * new iterator over the sub directory becomes the current directory. This + * implements a depth first traversal of the directory namespace. + */ + private Enumeration currentEnum; + + /** + * This is a stack of current iterators supporting the depth first traversal + * of the directory tree. + */ + private Stack enumStack; + + /** + * Creates a directory iterator. The directory iterator is created to scan + * the root directory. If the changeInto flag is given, then the entries + * returned will be relative to this directory and not the current + * directory. + * + * @param rootDirectory the root if the directory namespace which is to be + * iterated over + * @param changeInto if true then the returned entries will be relative to + * the rootDirectory and not the current directory. + * @exception IOException Description of Exception + * @throws IOException if there is a problem reading the directory + * information. + */ + public DirectoryIterator( File rootDirectory, boolean changeInto ) + throws IOException + { + super(); + + enumStack = new Stack(); + + if( rootDirectory.isAbsolute() || changeInto ) + { + rootLength = rootDirectory.getPath().length() + 1; + } + else + { + rootLength = 0; + } + + Vector filesInRoot = getDirectoryEntries( rootDirectory ); + + currentEnum = filesInRoot.elements(); + } + + /** + * Template method to allow subclasses to supply elements for the iteration. + * The directory iterator maintains a stack of iterators covering each level + * in the directory hierarchy. The current iterator covers the current + * directory being scanned. If the next entry in that directory is a + * subdirectory, the current iterator is pushed onto the stack and a new + * iterator is created for the subdirectory. If the entry is a file, it is + * returned as the next element and the iterator remains valid. If there are + * no more entries in the current directory, the topmost iterator on the + * statck is popped off to become the current iterator. + * + * @return the next ClassFile in the iteration. + */ + public ClassFile getNextClassFile() + { + ClassFile nextElement = null; + + try + { + while( nextElement == null ) + { + if( currentEnum.hasMoreElements() ) + { + File element = ( File )currentEnum.nextElement(); + + if( element.isDirectory() ) + { + + // push the current iterator onto the stack and then + // iterate through this directory. + enumStack.push( currentEnum ); + + Vector files = getDirectoryEntries( element ); + + currentEnum = files.elements(); + } + else + { + + // we have a file. create a stream for it + FileInputStream inFileStream = new FileInputStream( element ); + + if( element.getName().endsWith( ".class" ) ) + { + + // create a data input stream from the jar input stream + ClassFile javaClass = new ClassFile(); + + javaClass.read( inFileStream ); + + nextElement = javaClass; + } + } + } + else + { + // this iterator is exhausted. Can we pop one off the stack + if( enumStack.empty() ) + { + break; + } + else + { + currentEnum = ( Enumeration )enumStack.pop(); + } + } + } + } + catch( IOException e ) + { + nextElement = null; + } + + return nextElement; + } + + /** + * Get a vector covering all the entries (files and subdirectories in a + * directory). + * + * @param directory the directory to be scanned. + * @return a vector containing File objects for each entry in the directory. + */ + private Vector getDirectoryEntries( File directory ) + { + Vector files = new Vector(); + + // File[] filesInDir = directory.listFiles(); + String[] filesInDir = directory.list(); + + if( filesInDir != null ) + { + int length = filesInDir.length; + + for( int i = 0; i < length; ++i ) + { + files.addElement( new File( directory, filesInDir[i] ) ); + } + } + + return files; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java new file mode 100644 index 000000000..1715255bf --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * A class file iterator which iterates through the contents of a Java jar file. + * + * @author Conor MacNeill + */ +public class JarFileIterator implements ClassFileIterator +{ + private ZipInputStream jarStream; + + public JarFileIterator( InputStream stream ) + throws IOException + { + super(); + + jarStream = new ZipInputStream( stream ); + } + + public ClassFile getNextClassFile() + { + ZipEntry jarEntry; + ClassFile nextElement = null; + + try + { + jarEntry = jarStream.getNextEntry(); + + while( nextElement == null && jarEntry != null ) + { + String entryName = jarEntry.getName(); + + if( !jarEntry.isDirectory() && entryName.endsWith( ".class" ) ) + { + + // create a data input stream from the jar input stream + ClassFile javaClass = new ClassFile(); + + javaClass.read( jarStream ); + + nextElement = javaClass; + } + else + { + + jarEntry = jarStream.getNextEntry(); + } + } + } + catch( IOException e ) + { + String message = e.getMessage(); + String text = e.getClass().getName(); + + if( message != null ) + { + text += ": " + message; + } + + throw new RuntimeException( "Problem reading JAR file: " + text ); + } + + return nextElement; + } + + private byte[] getEntryBytes( InputStream stream ) + throws IOException + { + byte[] buffer = new byte[8192]; + ByteArrayOutputStream baos = new ByteArrayOutputStream( 2048 ); + int n; + + while( ( n = stream.read( buffer, 0, buffer.length ) ) != -1 ) + { + baos.write( buffer, 0, n ); + } + + return baos.toByteArray(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java new file mode 100644 index 000000000..919486a22 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * The constant pool entry which stores class information. + * + * @author Conor MacNeill + */ +public class ClassCPInfo extends ConstantPoolEntry +{ + + /** + * The class' name. This will be only valid if the entry has been resolved + * against the constant pool. + */ + private String className; + + /** + * The index into the constant pool where this class' name is stored. If the + * class name is changed, this entry is invalid until this entry is + * connected to a constant pool. + */ + private int index; + + /** + * Constructor. Sets the tag value for this entry to type Class + */ + public ClassCPInfo() + { + super( CONSTANT_Class, 1 ); + } + + /** + * Get the class name of this entry. + * + * @return the class' name. + */ + public String getClassName() + { + return className; + } + + /** + * Read the entry from a stream. + * + * @param cpStream the stream containing the constant pool entry to be read. + * @exception IOException thrown if there is a problem reading the entry + * from the stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + index = cpStream.readUnsignedShort(); + className = "unresolved"; + } + + /** + * Resolve this class info against the given constant pool. + * + * @param constantPool the constant pool with which to resolve the class. + */ + public void resolve( ConstantPool constantPool ) + { + className = ( ( Utf8CPInfo )constantPool.getEntry( index ) ).getValue(); + + super.resolve( constantPool ); + } + + /** + * Generate a string readable version of this entry + * + * @return Description of the Returned Value + */ + public String toString() + { + return "Class Constant Pool Entry for " + className + "[" + index + "]"; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java new file mode 100644 index 000000000..f8ba55828 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; + + +/** + * A Constant Pool entry which represents a constant value. + * + * @author Conor MacNeill + */ +public abstract class ConstantCPInfo extends ConstantPoolEntry +{ + + /** + * The entry's untyped value. Each subclass interprets the constant value + * based on the subclass's type. The value here must be compatible. + */ + private Object value; + + /** + * Initialise the constant entry. + * + * @param tagValue the constant pool entry type to be used. + * @param entries the number of constant pool entry slots occupied by this + * entry. + */ + protected ConstantCPInfo( int tagValue, int entries ) + { + super( tagValue, entries ); + } + + /** + * Set the constant value. + * + * @param newValue the new untyped value of this constant. + */ + public void setValue( Object newValue ) + { + value = newValue; + } + + /** + * Get the value of the constant. + * + * @return the value of the constant (untyped). + */ + public Object getValue() + { + return value; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java new file mode 100644 index 000000000..ee3131ec3 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +/** + * The constant pool of a Java class. The constant pool is a collection of + * constants used in a Java class file. It stores strings, constant values, + * class names, method names, field names etc. + * + * @author Conor MacNeill + * @see The Java Virtual + * Machine Specification + */ +public class ConstantPool +{ + + /** + * The entries in the constant pool. + */ + private Vector entries; + + /** + * A Hashtable of UTF8 entries - used to get constant pool indexes of the + * UTF8 values quickly + */ + private Hashtable utf8Indexes; + + /** + * Initialise the constant pool. + */ + public ConstantPool() + { + entries = new Vector(); + + // The zero index is never present in the constant pool itself so + // we add a null entry for it + entries.addElement( null ); + + utf8Indexes = new Hashtable(); + } + + /** + * Get the index of a given CONSTANT_Class entry in the constant pool. + * + * @param className the name of the class for which the class entry index is + * required. + * @return the index at which the given class entry occurs in the constant + * pool or -1 if the value does not occur. + */ + public int getClassEntry( String className ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof ClassCPInfo ) + { + ClassCPInfo classinfo = ( ClassCPInfo )element; + + if( classinfo.getClassName().equals( className ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given constant value entry in the constant pool. + * + * @param constantValue the constant value for which the index is required. + * @return the index at which the given value entry occurs in the constant + * pool or -1 if the value does not occur. + */ + public int getConstantEntry( Object constantValue ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof ConstantCPInfo ) + { + ConstantCPInfo constantEntry = ( ConstantCPInfo )element; + + if( constantEntry.getValue().equals( constantValue ) ) + { + index = i; + } + } + } + + return index; + } + + + /** + * Get an constant pool entry at a particular index. + * + * @param index the index into the constant pool. + * @return the constant pool entry at that index. + */ + public ConstantPoolEntry getEntry( int index ) + { + return ( ConstantPoolEntry )entries.elementAt( index ); + } + + /** + * Get the index of a given CONSTANT_FieldRef entry in the constant pool. + * + * @param fieldClassName the name of the class which contains the field + * being referenced. + * @param fieldName the name of the field being referenced. + * @param fieldType the type descriptor of the field being referenced. + * @return the index at which the given field ref entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getFieldRefEntry( String fieldClassName, String fieldName, String fieldType ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof FieldRefCPInfo ) + { + FieldRefCPInfo fieldRefEntry = ( FieldRefCPInfo )element; + + if( fieldRefEntry.getFieldClassName().equals( fieldClassName ) && fieldRefEntry.getFieldName().equals( fieldName ) + && fieldRefEntry.getFieldType().equals( fieldType ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given CONSTANT_InterfaceMethodRef entry in the + * constant pool. + * + * @param interfaceMethodClassName the name of the interface which contains + * the method being referenced. + * @param interfaceMethodName the name of the method being referenced. + * @param interfaceMethodType the type descriptor of the metho dbeing + * referenced. + * @return the index at which the given method ref entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getInterfaceMethodRefEntry( String interfaceMethodClassName, String interfaceMethodName, String interfaceMethodType ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof InterfaceMethodRefCPInfo ) + { + InterfaceMethodRefCPInfo interfaceMethodRefEntry = ( InterfaceMethodRefCPInfo )element; + + if( interfaceMethodRefEntry.getInterfaceMethodClassName().equals( interfaceMethodClassName ) + && interfaceMethodRefEntry.getInterfaceMethodName().equals( interfaceMethodName ) + && interfaceMethodRefEntry.getInterfaceMethodType().equals( interfaceMethodType ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given CONSTANT_MethodRef entry in the constant pool. + * + * @param methodClassName the name of the class which contains the method + * being referenced. + * @param methodName the name of the method being referenced. + * @param methodType the type descriptor of the metho dbeing referenced. + * @return the index at which the given method ref entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getMethodRefEntry( String methodClassName, String methodName, String methodType ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof MethodRefCPInfo ) + { + MethodRefCPInfo methodRefEntry = ( MethodRefCPInfo )element; + + if( methodRefEntry.getMethodClassName().equals( methodClassName ) + && methodRefEntry.getMethodName().equals( methodName ) && methodRefEntry.getMethodType().equals( methodType ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given CONSTANT_NameAndType entry in the constant pool. + * + * @param name the name + * @param type the type + * @return the index at which the given NameAndType entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getNameAndTypeEntry( String name, String type ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof NameAndTypeCPInfo ) + { + NameAndTypeCPInfo nameAndTypeEntry = ( NameAndTypeCPInfo )element; + + if( nameAndTypeEntry.getName().equals( name ) && nameAndTypeEntry.getType().equals( type ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given UTF8 constant pool entry. + * + * @param value the string value of the UTF8 entry. + * @return the index at which the given string occurs in the constant pool + * or -1 if the value does not occur. + */ + public int getUTF8Entry( String value ) + { + int index = -1; + Integer indexInteger = ( Integer )utf8Indexes.get( value ); + + if( indexInteger != null ) + { + index = indexInteger.intValue(); + } + + return index; + } + + /** + * Add an entry to the constant pool. + * + * @param entry the new entry to be added to the constant pool. + * @return the index into the constant pool at which the entry is stored. + */ + public int addEntry( ConstantPoolEntry entry ) + { + int index = entries.size(); + + entries.addElement( entry ); + + int numSlots = entry.getNumEntries(); + + // add null entries for any additional slots required. + for( int j = 0; j < numSlots - 1; ++j ) + { + entries.addElement( null ); + } + + if( entry instanceof Utf8CPInfo ) + { + Utf8CPInfo utf8Info = ( Utf8CPInfo )entry; + + utf8Indexes.put( utf8Info.getValue(), new Integer( index ) ); + } + + return index; + } + + /** + * Read the constant pool from a class input stream. + * + * @param classStream the DataInputStream of a class file. + * @throws IOException if there is a problem reading the constant pool from + * the stream + */ + public void read( DataInputStream classStream ) + throws IOException + { + int numEntries = classStream.readUnsignedShort(); + + for( int i = 1; i < numEntries; ) + { + ConstantPoolEntry nextEntry = ConstantPoolEntry.readEntry( classStream ); + + i += nextEntry.getNumEntries(); + + addEntry( nextEntry ); + } + } + + /** + * Resolve the entries in the constant pool. Resolution of the constant pool + * involves transforming indexes to other constant pool entries into the + * actual data for that entry. + */ + public void resolve() + { + for( Enumeration i = entries.elements(); i.hasMoreElements(); ) + { + ConstantPoolEntry poolInfo = ( ConstantPoolEntry )i.nextElement(); + + if( poolInfo != null && !poolInfo.isResolved() ) + { + poolInfo.resolve( this ); + } + } + } + + /** + * Get the size of the constant pool. + * + * @return Description of the Returned Value + */ + public int size() + { + return entries.size(); + } + + /** + * Dump the constant pool to a string. + * + * @return the constant pool entries as strings + */ + public String toString() + { + StringBuffer sb = new StringBuffer( "\n" ); + int size = entries.size(); + + for( int i = 0; i < size; ++i ) + { + sb.append( "[" + i + "] = " + getEntry( i ) + "\n" ); + } + + return sb.toString(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java new file mode 100644 index 000000000..7e1a89c31 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * An entry in the constant pool. This class contains a represenation of the + * constant pool entries. It is an abstract base class for all the different + * forms of constant pool entry. + * + * @author Conor MacNeill + * @see ConstantPool + */ +public abstract class ConstantPoolEntry +{ + + /** + * Tag value for UTF8 entries. + */ + public final static int CONSTANT_Utf8 = 1; + + /** + * Tag value for Integer entries. + */ + public final static int CONSTANT_Integer = 3; + + /** + * Tag value for Float entries. + */ + public final static int CONSTANT_Float = 4; + + /** + * Tag value for Long entries. + */ + public final static int CONSTANT_Long = 5; + + /** + * Tag value for Double entries. + */ + public final static int CONSTANT_Double = 6; + + /** + * Tag value for Class entries. + */ + public final static int CONSTANT_Class = 7; + + /** + * Tag value for String entries. + */ + public final static int CONSTANT_String = 8; + + /** + * Tag value for Field Reference entries. + */ + public final static int CONSTANT_FieldRef = 9; + + /** + * Tag value for Method Reference entries. + */ + public final static int CONSTANT_MethodRef = 10; + + /** + * Tag value for Interface Method Reference entries. + */ + public final static int CONSTANT_InterfaceMethodRef = 11; + + /** + * Tag value for Name and Type entries. + */ + public final static int CONSTANT_NameAndType = 12; + + /** + * The number of slots in the constant pool, occupied by this entry. + */ + private int numEntries; + + /** + * A flag which indiciates if this entry has been resolved or not. + */ + private boolean resolved; + + /** + * This entry's tag which identifies the type of this constant pool entry. + */ + private int tag; + + /** + * Initialse the constant pool entry. + * + * @param tagValue the tag value which identifies which type of constant + * pool entry this is. + * @param entries the number of constant pool entry slots this entry + * occupies. + */ + public ConstantPoolEntry( int tagValue, int entries ) + { + tag = tagValue; + numEntries = entries; + resolved = false; + } + + /** + * Read a constant pool entry from a stream. This is a factory method which + * reads a constant pool entry form a stream and returns the appropriate + * subclass for the entry. + * + * @param cpStream the stream from which the constant pool entry is to be + * read. + * @return Description of the Returned Value + * @exception IOException Description of Exception + * @returns the appropriate ConstantPoolEntry subclass representing the + * constant pool entry from the stream. + * @throws IOExcception if there is a problem reading the entry from the + * stream. + */ + public static ConstantPoolEntry readEntry( DataInputStream cpStream ) + throws IOException + { + ConstantPoolEntry cpInfo = null; + int cpTag = cpStream.readUnsignedByte(); + + switch ( cpTag ) + { + + case CONSTANT_Utf8: + cpInfo = new Utf8CPInfo(); + + break; + case CONSTANT_Integer: + cpInfo = new IntegerCPInfo(); + + break; + case CONSTANT_Float: + cpInfo = new FloatCPInfo(); + + break; + case CONSTANT_Long: + cpInfo = new LongCPInfo(); + + break; + case CONSTANT_Double: + cpInfo = new DoubleCPInfo(); + + break; + case CONSTANT_Class: + cpInfo = new ClassCPInfo(); + + break; + case CONSTANT_String: + cpInfo = new StringCPInfo(); + + break; + case CONSTANT_FieldRef: + cpInfo = new FieldRefCPInfo(); + + break; + case CONSTANT_MethodRef: + cpInfo = new MethodRefCPInfo(); + + break; + case CONSTANT_InterfaceMethodRef: + cpInfo = new InterfaceMethodRefCPInfo(); + + break; + case CONSTANT_NameAndType: + cpInfo = new NameAndTypeCPInfo(); + + break; + default: + throw new ClassFormatError( "Invalid Constant Pool entry Type " + cpTag ); + } + + cpInfo.read( cpStream ); + + return cpInfo; + } + + /** + * Get the number of Constant Pool Entry slots within the constant pool + * occupied by this entry. + * + * @return the number of slots used. + */ + public final int getNumEntries() + { + return numEntries; + } + + /** + * Get the Entry's type tag. + * + * @return The Tag value of this entry + */ + public int getTag() + { + return tag; + } + + /** + * Indicates whether this entry has been resolved. In general a constant + * pool entry can reference another constant pool entry by its index value. + * Resolution involves replacing this index value with the constant pool + * entry at that index. + * + * @return true if this entry has been resolved. + */ + public boolean isResolved() + { + return resolved; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public abstract void read( DataInputStream cpStream ) + throws IOException; + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + resolved = true; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java new file mode 100644 index 000000000..94122a060 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * The constant pool entry subclass used to represent double constant values. + * + * @author Conor MacNeill + */ +public class DoubleCPInfo extends ConstantCPInfo +{ + public DoubleCPInfo() + { + super( CONSTANT_Double, 2 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Double( cpStream.readDouble() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Double Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java new file mode 100644 index 000000000..bf56aee85 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A FieldRef CP Info + * + * @author Conor MacNeill + */ +public class FieldRefCPInfo extends ConstantPoolEntry +{ + private int classIndex; + private String fieldClassName; + private String fieldName; + private String fieldType; + private int nameAndTypeIndex; + + /** + * Constructor. + */ + public FieldRefCPInfo() + { + super( CONSTANT_FieldRef, 1 ); + } + + public String getFieldClassName() + { + return fieldClassName; + } + + public String getFieldName() + { + return fieldName; + } + + public String getFieldType() + { + return fieldType; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + classIndex = cpStream.readUnsignedShort(); + nameAndTypeIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + ClassCPInfo fieldClass = ( ClassCPInfo )constantPool.getEntry( classIndex ); + + fieldClass.resolve( constantPool ); + + fieldClassName = fieldClass.getClassName(); + + NameAndTypeCPInfo nt = ( NameAndTypeCPInfo )constantPool.getEntry( nameAndTypeIndex ); + + nt.resolve( constantPool ); + + fieldName = nt.getName(); + fieldType = nt.getType(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "Field : Class = " + fieldClassName + ", name = " + fieldName + ", type = " + fieldType; + } + else + { + value = "Field : Class index = " + classIndex + ", name and type index = " + nameAndTypeIndex; + } + + return value; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java new file mode 100644 index 000000000..affdeefc1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * A Float CP Info + * + * @author Conor MacNeill + */ +public class FloatCPInfo extends ConstantCPInfo +{ + + /** + * Constructor. + */ + public FloatCPInfo() + { + super( CONSTANT_Float, 1 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Float( cpStream.readFloat() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Float Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java new file mode 100644 index 000000000..ce7acbc52 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * An Integer CP Info + * + * @author Conor MacNeill + */ +public class IntegerCPInfo extends ConstantCPInfo +{ + + /** + * Constructor. + */ + public IntegerCPInfo() + { + super( CONSTANT_Integer, 1 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Integer( cpStream.readInt() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Integer Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java new file mode 100644 index 000000000..bc2077fed --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A InterfaceMethodRef CP Info + * + * @author Conor MacNeill + */ +public class InterfaceMethodRefCPInfo extends ConstantPoolEntry +{ + private int classIndex; + private String interfaceMethodClassName; + private String interfaceMethodName; + private String interfaceMethodType; + private int nameAndTypeIndex; + + /** + * Constructor. + */ + public InterfaceMethodRefCPInfo() + { + super( CONSTANT_InterfaceMethodRef, 1 ); + } + + public String getInterfaceMethodClassName() + { + return interfaceMethodClassName; + } + + public String getInterfaceMethodName() + { + return interfaceMethodName; + } + + public String getInterfaceMethodType() + { + return interfaceMethodType; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + classIndex = cpStream.readUnsignedShort(); + nameAndTypeIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + ClassCPInfo interfaceMethodClass = ( ClassCPInfo )constantPool.getEntry( classIndex ); + + interfaceMethodClass.resolve( constantPool ); + + interfaceMethodClassName = interfaceMethodClass.getClassName(); + + NameAndTypeCPInfo nt = ( NameAndTypeCPInfo )constantPool.getEntry( nameAndTypeIndex ); + + nt.resolve( constantPool ); + + interfaceMethodName = nt.getName(); + interfaceMethodType = nt.getType(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "InterfaceMethod : Class = " + interfaceMethodClassName + ", name = " + interfaceMethodName + ", type = " + + interfaceMethodType; + } + else + { + value = "InterfaceMethod : Class index = " + classIndex + ", name and type index = " + nameAndTypeIndex; + } + + return value; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java new file mode 100644 index 000000000..12f981faf --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * A Long CP Info + * + * @author Conor MacNeill + */ +public class LongCPInfo extends ConstantCPInfo +{ + + /** + * Constructor. + */ + public LongCPInfo() + { + super( CONSTANT_Long, 2 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Long( cpStream.readLong() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Long Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java new file mode 100644 index 000000000..a284adcf9 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A MethodRef CP Info + * + * @author Conor MacNeill + */ +public class MethodRefCPInfo extends ConstantPoolEntry +{ + private int classIndex; + private String methodClassName; + private String methodName; + private String methodType; + private int nameAndTypeIndex; + + /** + * Constructor. + */ + public MethodRefCPInfo() + { + super( CONSTANT_MethodRef, 1 ); + } + + public String getMethodClassName() + { + return methodClassName; + } + + public String getMethodName() + { + return methodName; + } + + public String getMethodType() + { + return methodType; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + classIndex = cpStream.readUnsignedShort(); + nameAndTypeIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + ClassCPInfo methodClass = ( ClassCPInfo )constantPool.getEntry( classIndex ); + + methodClass.resolve( constantPool ); + + methodClassName = methodClass.getClassName(); + + NameAndTypeCPInfo nt = ( NameAndTypeCPInfo )constantPool.getEntry( nameAndTypeIndex ); + + nt.resolve( constantPool ); + + methodName = nt.getName(); + methodType = nt.getType(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "Method : Class = " + methodClassName + ", name = " + methodName + ", type = " + methodType; + } + else + { + value = "Method : Class index = " + classIndex + ", name and type index = " + nameAndTypeIndex; + } + + return value; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java new file mode 100644 index 000000000..8b769629b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A NameAndType CP Info + * + * @author Conor MacNeill + */ +public class NameAndTypeCPInfo extends ConstantPoolEntry +{ + private int descriptorIndex; + + private String name; + private int nameIndex; + private String type; + + /** + * Constructor. + */ + public NameAndTypeCPInfo() + { + super( CONSTANT_NameAndType, 1 ); + } + + public String getName() + { + return name; + } + + public String getType() + { + return type; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + nameIndex = cpStream.readUnsignedShort(); + descriptorIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + name = ( ( Utf8CPInfo )constantPool.getEntry( nameIndex ) ).getValue(); + type = ( ( Utf8CPInfo )constantPool.getEntry( descriptorIndex ) ).getValue(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "Name = " + name + ", type = " + type; + } + else + { + value = "Name index = " + nameIndex + ", descriptor index = " + descriptorIndex; + } + + return value; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java new file mode 100644 index 000000000..f65ad291b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A String Constant Pool Entry. The String info contains an index into the + * constant pool where a UTF8 string is stored. + * + * @author Conor MacNeill + */ +public class StringCPInfo extends ConstantCPInfo +{ + + private int index; + + /** + * Constructor. + */ + public StringCPInfo() + { + super( CONSTANT_String, 1 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + index = cpStream.readUnsignedShort(); + + setValue( "unresolved" ); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + setValue( ( ( Utf8CPInfo )constantPool.getEntry( index ) ).getValue() ); + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "String Constant Pool Entry for " + getValue() + "[" + index + "]"; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java new file mode 100644 index 000000000..dc42d1984 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A UTF8 Constant Pool Entry. + * + * @author Conor MacNeill + */ +public class Utf8CPInfo extends ConstantPoolEntry +{ + private String value; + + /** + * Constructor. + */ + public Utf8CPInfo() + { + super( CONSTANT_Utf8, 1 ); + } + + public String getValue() + { + return value; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + value = cpStream.readUTF(); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "UTF8 Value = " + value; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java new file mode 100644 index 000000000..371264a4a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java @@ -0,0 +1,965 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.dotnet; +import java.io.File;// ==================================================================== +// imports +// ==================================================================== +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.types.Path; + + +// ==================================================================== +/** + * This task compiles CSharp source into executables or modules. The task will + * only work on win2K until other platforms support csc.exe or an equivalent. + * CSC.exe must be on the execute path too.

      + * + * All parameters are optional: <csc/> should suffice to produce a debug + * build of all *.cs files. References to external files do require explicit + * enumeration, so are one of the first attributes to consider adding.

      + * + * The task is a directory based task, so attributes like includes="*.cs" + * and excludes="broken.cs" can be used to control the files pulled in. + * By default, all *.cs files from the project folder down are included in the + * command. When this happens the output file -if not specified- is taken as the + * first file in the list, which may be somewhat hard to control. Specifying the + * output file with 'outfile' seems prudent.

      + * + *

      + * + * TODO + *

        + *
      1. is incremental build still broken in beta-1? + *
      2. is Win32Icon broken? + *
      3. all the missing options + *
      + *

      + * + * History + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      + * 0.3 + * + * Beta 1 edition + * + * To avoid having to remember which assemblies to include, the task + * automatically refers to the main dotnet libraries in Beta1. + *
      + * 0.2 + * + * Slightly different + * + * Split command execution to a separate class; + *
      + * 0.1 + * + * "I can't believe it's so rudimentary" + * + * First pass; minimal builds only support; + *
      + * + * + * @author Steve Loughran steve_l@iseran.com + * @version 0.3 + */ + +public class CSharp + extends org.apache.tools.ant.taskdefs.MatchingTask +{ + + /** + * name of the executable. the .exe suffix is deliberately not included in + * anticipation of the unix version + */ + protected final static String csc_exe_name = "csc"; + + /** + * what is the file extension we search on? + */ + protected final static String csc_file_ext = "cs"; + + /** + * derive the search pattern from the extension + */ + protected final static String csc_file_pattern = "**/*." + csc_file_ext; + + /** + * Fix C# reference inclusion. C# is really dumb in how it handles + * inclusion. You have to list every 'assembly' -read DLL that is imported. + * So already you are making a platform assumption -shared libraries have a + * .dll;"+ extension and the poor developer has to know every library which + * is included why the compiler cant find classes on the path or in a + * directory, is a mystery. To reduce the need to be explicit, here is a + * long list of the core libraries used in Beta-1 of .NET ommitting the + * blatantly non portable (MS.win32.interop) and the .designer libraries. + * (ripping out Com was tempting) Casing is chosen to match that of the file + * system exactly so may work on a unix box too. + */ + + protected final static String DEFAULT_REFERENCE_LIST = + "Accessibility.dll;" + + "cscompmgd.dll;" + + "CustomMarshalers.dll;" + + "IEExecRemote.dll;" + + "IEHost.dll;" + + "IIEHost.dll;" + + "ISymWrapper.dll;" + + "Microsoft.JScript.dll;" + + "Microsoft.VisualBasic.dll;" + + "Microsoft.VisualC.dll;" + + "Microsoft.Vsa.dll;" + + "Mscorcfg.dll;" + + "RegCode.dll;" + + "System.Configuration.Install.dll;" + + "System.Data.dll;" + + "System.Design.dll;" + + "System.DirectoryServices.dll;" + + "System.EnterpriseServices.dll;" + + "System.dll;" + + "System.Drawing.Design.dll;" + + "System.Drawing.dll;" + + "System.Management.dll;" + + "System.Messaging.dll;" + + "System.Runtime.Remoting.dll;" + + "System.Runtime.Serialization.Formatters.Soap.dll;" + + "System.Security.dll;" + + "System.ServiceProcess.dll;" + + "System.Web.dll;" + + "System.Web.RegularExpressions.dll;" + + "System.Web.Services.dll;" + + "System.Windows.Forms.dll;" + + "System.XML.dll;"; + + /** + * utf out flag + */ + + protected boolean _utf8output = false; + + protected boolean _noconfig = false; + + // /fullpaths + protected boolean _fullpaths = false; + + /** + * debug flag. Controls generation of debug information. + */ + protected boolean _debug; + + /** + * output XML documentation flag + */ + protected File _docFile; + + /** + * any extra command options? + */ + protected String _extraOptions; + + /** + * flag to control action on execution trouble + */ + protected boolean _failOnError; + + /** + * flag to enable automatic reference inclusion + */ + protected boolean _includeDefaultReferences; + + /** + * incremental build flag + */ + protected boolean _incremental; + + /** + * main class (or null for automatic choice) + */ + protected String _mainClass; + + /** + * optimise flag + */ + protected boolean _optimize; + + /** + * output file. If not supplied this is derived from the source file + */ + protected File _outputFile; + + /** + * using the path approach didnt work as it could not handle the implicit + * execution path. Perhaps that could be extracted from the runtime and then + * the path approach would be viable + */ + protected Path _referenceFiles; + + /** + * list of reference classes. (pretty much a classpath equivalent) + */ + protected String _references; + + /** + * type of target. Should be one of exe|library|module|winexe|(null) default + * is exe; the actual value (if not null) is fed to the command line.
      + * See /target + */ + protected String _targetType; + + /** + * enable unsafe code flag. Clearly set to false by default + */ + protected boolean _unsafe; + + /** + * icon for incorporation into apps + */ + protected File _win32icon; + /** + * icon for incorporation into apps + */ + protected File _win32res; + + /** + * list of extra modules to refer to + */ + String _additionalModules; + + /** + * defines list something like 'RELEASE;WIN32;NO_SANITY_CHECKS;;SOMETHING_ELSE' + */ + String _definitions; + + /** + * destination directory (null means use the source directory) NB: this is + * currently not used + */ + private File _destDir; + + /** + * source directory upon which the search pattern is applied + */ + private File _srcDir; + + /** + * warning level: 0-4, with 4 being most verbose + */ + private int _warnLevel; + + /** + * constructor inits everything and set up the search pattern + */ + + public CSharp() + { + Clear(); + setIncludes( csc_file_pattern ); + } + + /** + * Set the definitions + * + * @param params The new AdditionalModules value + */ + public void setAdditionalModules( String params ) + { + _additionalModules = params; + } + + /** + * set the debug flag on or off + * + * @param f on/off flag + */ + public void setDebug( boolean f ) + { + _debug = f; + } + + /** + * Set the definitions + * + * @param params The new Definitions value + */ + public void setDefinitions( String params ) + { + _definitions = params; + } + + /** + * Set the destination dir to find the files to be compiled + * + * @param dirName The new DestDir value + */ + public void setDestDir( File dirName ) + { + _destDir = dirName; + } + + /** + * file for generated XML documentation + * + * @param f output file + */ + public void setDocFile( File f ) + { + _docFile = f; + } + + /** + * Sets the ExtraOptions attribute + * + * @param extraOptions The new ExtraOptions value + */ + public void setExtraOptions( String extraOptions ) + { + this._extraOptions = extraOptions; + } + + /** + * set fail on error flag + * + * @param b The new FailOnError value + */ + public void setFailOnError( boolean b ) + { + _failOnError = b; + } + + public void setFullPaths( boolean enabled ) + { + _fullpaths = enabled; + } + + /** + * set the automatic reference inclusion flag on or off this flag controls + * the string of references and the /nostdlib option in CSC + * + * @param f on/off flag + */ + public void setIncludeDefaultReferences( boolean f ) + { + _includeDefaultReferences = f; + } + + /** + * set the incremental compilation flag on or off + * + * @param f on/off flag + */ + public void setIncremental( boolean f ) + { + _incremental = f; + } + + /** + * Sets the MainClass attribute + * + * @param mainClass The new MainClass value + */ + public void setMainClass( String mainClass ) + { + this._mainClass = mainClass; + } + + /** + * set the optimise flag on or off + * + * @param f on/off flag + */ + public void setOptimize( boolean f ) + { + _optimize = f; + } + + /** + * Set the definitions + * + * @param params The new OutputFile value + */ + public void setOutputFile( File params ) + { + _outputFile = params; + } + + /** + * add another path to the reference file path list + * + * @param path another path to append + */ + public void setReferenceFiles( Path path ) + { + //demand create pathlist + if( _referenceFiles == null ) + _referenceFiles = new Path( this.project ); + _referenceFiles.append( path ); + } + + /** + * Set the reference list to be used for this compilation. + * + * @param s The new References value + */ + public void setReferences( String s ) + { + _references = s; + } + + /** + * Set the source dir to find the files to be compiled + * + * @param srcDirName The new SrcDir value + */ + public void setSrcDir( File srcDirName ) + { + _srcDir = srcDirName; + } + + /** + * define the target + * + * @param targetType The new TargetType value + * @exception BuildException if target is not one of + * exe|library|module|winexe + */ + public void setTargetType( String targetType ) + throws BuildException + { + targetType = targetType.toLowerCase(); + if( targetType.equals( "exe" ) || targetType.equals( "library" ) || + targetType.equals( "module" ) || targetType.equals( "winexe" ) ) + { + _targetType = targetType; + } + else + throw new BuildException( "targetType " + targetType + " is not a valid type" ); + } + + /** + * Sets the Unsafe attribute + * + * @param unsafe The new Unsafe value + */ + public void setUnsafe( boolean unsafe ) + { + this._unsafe = unsafe; + } + + /** + * enable generation of utf8 output from the compiler. + * + * @param enabled The new Utf8Output value + */ + public void setUtf8Output( boolean enabled ) + { + _utf8output = enabled; + } + + /** + * set warn level (no range checking) + * + * @param warnLevel warn level -see .net docs for valid range (probably 0-4) + */ + public void setWarnLevel( int warnLevel ) + { + this._warnLevel = warnLevel; + } + + /** + * Set the win32 icon + * + * @param fileName path to the file. Can be relative, absolute, whatever. + */ + public void setWin32Icon( File fileName ) + { + _win32icon = fileName; + } + + /** + * Set the win32 icon + * + * @param fileName path to the file. Can be relative, absolute, whatever. + */ + public void setWin32Res( File fileName ) + { + _win32res = fileName; + } + + /** + * query the debug flag + * + * @return true if debug is turned on + */ + public boolean getDebug() + { + return _debug; + } + + /** + * Gets the ExtraOptions attribute + * + * @return The ExtraOptions value + */ + public String getExtraOptions() + { + return this._extraOptions; + } + + /** + * query fail on error flag + * + * @return The FailFailOnError value + */ + public boolean getFailFailOnError() + { + return _failOnError; + } + + /** + * query the optimise flag + * + * @return true if optimise is turned on + */ + public boolean getIncludeDefaultReferences() + { + return _includeDefaultReferences; + } + + /** + * query the incrementalflag + * + * @return true iff incremental compilation is turned on + */ + public boolean getIncremental() + { + return _incremental; + } + + /** + * Gets the MainClass attribute + * + * @return The MainClass value + */ + public String getMainClass() + { + return this._mainClass; + } + + /** + * query the optimise flag + * + * @return true if optimise is turned on + */ + public boolean getOptimize() + { + return _optimize; + } + + /** + * Gets the TargetType attribute + * + * @return The TargetType value + */ + public String getTargetType() + { + return _targetType; + } + + /** + * query the Unsafe attribute + * + * @return The Unsafe value + */ + public boolean getUnsafe() + { + return this._unsafe; + } + + /** + * query warn level + * + * @return current value + */ + public int getWarnLevel() + { + return _warnLevel; + } + + /** + * reset all contents. + */ + public void Clear() + { + _targetType = null; + _win32icon = null; + _srcDir = null; + _destDir = null; + _mainClass = null; + _unsafe = false; + _warnLevel = 3; + _docFile = null; + _incremental = false; + _optimize = false; + _debug = true; + _references = null; + _failOnError = true; + _definitions = null; + _additionalModules = null; + _includeDefaultReferences = true; + _extraOptions = null; + _fullpaths = true; + } + + /** + * do the work by building the command line and then calling it + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( _srcDir == null ) + _srcDir = project.resolveFile( "." ); + + NetCommand command = new NetCommand( this, "CSC", csc_exe_name ); + command.setFailOnError( getFailFailOnError() ); + //DEBUG helper + command.setTraceCommandLine( true ); + //fill in args + command.addArgument( "/nologo" ); + command.addArgument( getAdditionalModulesParameter() ); + command.addArgument( getDefinitionsParameter() ); + command.addArgument( getDebugParameter() ); + command.addArgument( getDocFileParameter() ); + command.addArgument( getIncrementalParameter() ); + command.addArgument( getMainClassParameter() ); + command.addArgument( getOptimizeParameter() ); + command.addArgument( getReferencesParameter() ); + command.addArgument( getTargetTypeParameter() ); + command.addArgument( getUnsafeParameter() ); + command.addArgument( getWarnLevelParameter() ); + command.addArgument( getWin32IconParameter() ); + command.addArgument( getOutputFileParameter() ); + command.addArgument( getIncludeDefaultReferencesParameter() ); + command.addArgument( getDefaultReferenceParameter() ); + command.addArgument( getWin32ResParameter() ); + command.addArgument( getUtf8OutpuParameter() ); + command.addArgument( getNoConfigParameter() ); + command.addArgument( getFullPathsParameter() ); + command.addArgument( getExtraOptionsParameter() ); + + //get dependencies list. + DirectoryScanner scanner = super.getDirectoryScanner( _srcDir ); + String[] dependencies = scanner.getIncludedFiles(); + log( "compiling " + dependencies.length + " file" + ( ( dependencies.length == 1 ) ? "" : "s" ) ); + String baseDir = scanner.getBasedir().toString(); + //add to the command + for( int i = 0; i < dependencies.length; i++ ) + { + String targetFile = dependencies[i]; + targetFile = baseDir + File.separator + targetFile; + command.addArgument( targetFile ); + } + + //now run the command of exe + settings + files + command.runCommand(); + } + + protected void setNoConfig( boolean enabled ) + { + _noconfig = enabled; + } + + /** + * get the argument or null for no argument needed + * + * @return The AdditionalModules Parameter to CSC + */ + protected String getAdditionalModulesParameter() + { + if( notEmpty( _additionalModules ) ) + return "/addmodule:" + _additionalModules; + else + return null; + } + + /** + * get the debug switch argument + * + * @return The Debug Parameter to CSC + */ + protected String getDebugParameter() + { + return "/debug" + ( _debug ? "+" : "-" ); + } + + + /** + * get default reference list + * + * @return null or a string of references. + */ + protected String getDefaultReferenceParameter() + { + if( _includeDefaultReferences ) + { + StringBuffer s = new StringBuffer( "/reference:" ); + s.append( DEFAULT_REFERENCE_LIST ); + return new String( s ); + } + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The Definitions Parameter to CSC + */ + protected String getDefinitionsParameter() + { + if( notEmpty( _definitions ) ) + return "/define:" + _definitions; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The DocFile Parameter to CSC + */ + protected String getDocFileParameter() + { + if( _docFile != null ) + return "/doc:" + _docFile.toString(); + else + return null; + } + + /** + * get any extra options or null for no argument needed + * + * @return The ExtraOptions Parameter to CSC + */ + protected String getExtraOptionsParameter() + { + if( _extraOptions != null && _extraOptions.length() != 0 ) + return _extraOptions; + else + return null; + } + + protected String getFullPathsParameter() + { + return _fullpaths ? "/fullpaths" : null; + } + + /** + * get the include default references flag or null for no argument needed + * + * @return The Parameter to CSC + */ + protected String getIncludeDefaultReferencesParameter() + { + return "/nostdlib" + ( _includeDefaultReferences ? "-" : "+" ); + } + + /** + * get the incremental build argument + * + * @return The Incremental Parameter to CSC + */ + protected String getIncrementalParameter() + { + return "/incremental" + ( _incremental ? "+" : "-" ); + } + + /** + * get the /main argument or null for no argument needed + * + * @return The MainClass Parameter to CSC + */ + protected String getMainClassParameter() + { + if( _mainClass != null && _mainClass.length() != 0 ) + return "/main:" + _mainClass; + else + return null; + } + + protected String getNoConfigParameter() + { + return _noconfig ? "/noconfig" : null; + } + + /** + * get the optimise flag or null for no argument needed + * + * @return The Optimize Parameter to CSC + */ + protected String getOptimizeParameter() + { + return "/optimize" + ( _optimize ? "+" : "-" ); + } + + /** + * get the argument or null for no argument needed + * + * @return The OutputFile Parameter to CSC + */ + protected String getOutputFileParameter() + { + if( _outputFile != null ) + { + File f = _outputFile; + return "/out:" + f.toString(); + } + else + return null; + } + + /** + * turn the path list into a list of files and a /references argument + * + * @return null or a string of references. + */ + protected String getReferenceFilesParameter() + { + //bail on no references + if( _references == null ) + return null; + //iterate through the ref list & generate an entry for each + //or just rely on the fact that the toString operator does this, but + //noting that the separator is ';' on windows, ':' on unix + String refpath = _references.toString(); + + //bail on no references listed + if( refpath.length() == 0 ) + return null; + + StringBuffer s = new StringBuffer( "/reference:" ); + s.append( refpath ); + return new String( s ); + } + + /** + * get the reference string or null for no argument needed + * + * @return The References Parameter to CSC + */ + protected String getReferencesParameter() + { + //bail on no references + if( notEmpty( _references ) ) + return "/reference:" + _references; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The TargetType Parameter to CSC + */ + protected String getTargetTypeParameter() + { + if( notEmpty( _targetType ) ) + return "/target:" + _targetType; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The Unsafe Parameter to CSC + */ + protected String getUnsafeParameter() + { + return _unsafe ? "/unsafe" : null; + } + + protected String getUtf8OutpuParameter() + { + return _utf8output ? "/utf8output" : null; + } + + /** + * get the warn level switch + * + * @return The WarnLevel Parameter to CSC + */ + protected String getWarnLevelParameter() + { + return "/warn:" + _warnLevel; + } + + /** + * get the argument or null for no argument needed + * + * @return The Win32Icon Parameter to CSC + */ + protected String getWin32IconParameter() + { + if( _win32icon != null ) + return "/win32icon:" + _win32icon.toString(); + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The Win32Icon Parameter to CSC + */ + protected String getWin32ResParameter() + { + if( _win32res != null ) + return "/win32res:" + _win32res.toString(); + else + return null; + } + + /** + * test for a string containing something useful + * + * @param s string in + * @return true if the argument is not null or empty + */ + protected boolean notEmpty( String s ) + { + return s != null && s.length() != 0; + }// end execute + +}//end class diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java new file mode 100644 index 000000000..b1440d32f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.dotnet;// ==================================================================== +// imports +// ==================================================================== +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; + + +/** + * Task to assemble .net 'Intermediate Language' files. The task will only work + * on win2K until other platforms support csc.exe or an equivalent. ilasm.exe + * must be on the execute path too.

      + * + *

      + * + * All parameters are optional: <il/> should suffice to produce a debug + * build of all *.il files. The option set is roughly compatible with the CSharp + * class; even though the command line options are only vaguely equivalent. [The + * low level commands take things like /OUT=file, csc wants /out:file ... + * /verbose is used some places; /quiet here in ildasm... etc.] It would be nice + * if someone made all the command line tools consistent (and not as brittle as + * the java cmdline tools)

      + * + * The task is a directory based task, so attributes like includes="*.il" + * and excludes="broken.il" can be used to control the files pulled in. + * Each file is built on its own, producing an appropriately named output file + * unless manually specified with outfile + * + * @author Steve Loughran steve_l@iseran.com + * @version 0.2 + */ + +public class Ilasm + extends org.apache.tools.ant.taskdefs.MatchingTask +{ + + /** + * name of the executable. the .exe suffix is deliberately not included in + * anticipation of the unix version + */ + protected final static String exe_name = "ilasm"; + + /** + * what is the file extension we search on? + */ + protected final static String file_ext = "il"; + + /** + * and now derive the search pattern from the extension + */ + protected final static String file_pattern = "**/*." + file_ext; + + /** + * title of task for external presentation + */ + protected final static String exe_title = "ilasm"; + + /** + * debug flag. Controls generation of debug information. + */ + protected boolean _debug; + + /** + * any extra command options? + */ + protected String _extraOptions; + + /** + * flag to control action on execution trouble + */ + protected boolean _failOnError; + + /** + * listing flag + */ + + protected boolean _listing; + + /** + * output file. If not supplied this is derived from the source file + */ + protected File _outputFile; + + /** + * resource file (.res format) to include in the app. + */ + protected File _resourceFile; + + /** + * type of target. Should be one of exe|library|module|winexe|(null) default + * is exe; the actual value (if not null) is fed to the command line.
      + * See /target + */ + protected String _targetType; + + /** + * verbose flag + */ + protected boolean _verbose; + + /** + * file containing private key + */ + + private File _keyfile; + + /** + * source directory upon which the search pattern is applied + */ + private File _srcDir; + + /** + * constructor inits everything and set up the search pattern + */ + public Ilasm() + { + Clear(); + setIncludes( file_pattern ); + } + + /** + * set the debug flag on or off + * + * @param f on/off flag + */ + public void setDebug( boolean f ) + { + _debug = f; + } + + /** + * Sets the ExtraOptions attribute + * + * @param extraOptions The new ExtraOptions value + */ + public void setExtraOptions( String extraOptions ) + { + this._extraOptions = extraOptions; + } + + /** + * set fail on error flag + * + * @param b The new FailOnError value + */ + public void setFailOnError( boolean b ) + { + _failOnError = b; + } + + public void setKeyfile( File keyfile ) + { + this._keyfile = keyfile; + } + + /** + * enable/disable listing + * + * @param b flag set to true for listing on + */ + public void setListing( boolean b ) + { + _listing = b; + } + + /** + * Set the definitions + * + * @param params The new OutputFile value + */ + public void setOutputFile( File params ) + { + _outputFile = params; + } + + + /** + * Sets the Owner attribute + * + * @param s The new Owner value + */ + + public void setOwner( String s ) + { + log( "This option is not supported by ILASM as of Beta-2, and will be ignored", Project.MSG_WARN ); + } + + /** + * Set the resource file + * + * @param fileName path to the file. Can be relative, absolute, whatever. + */ + public void setResourceFile( File fileName ) + { + _resourceFile = fileName; + } + + /** + * Set the source dir to find the files to be compiled + * + * @param srcDirName The new SrcDir value + */ + public void setSrcDir( File srcDirName ) + { + _srcDir = srcDirName; + } + + /** + * define the target + * + * @param targetType one of exe|library| + * @exception BuildException if target is not one of + * exe|library|module|winexe + */ + + public void setTargetType( String targetType ) + throws BuildException + { + targetType = targetType.toLowerCase(); + if( targetType.equals( "exe" ) || targetType.equals( "library" ) ) + { + _targetType = targetType; + } + else + throw new BuildException( "targetType " + targetType + " is not a valid type" ); + } + + /** + * enable/disable verbose ILASM output + * + * @param b flag set to true for verbose on + */ + public void setVerbose( boolean b ) + { + _verbose = b; + } + + /** + * query the debug flag + * + * @return true if debug is turned on + */ + public boolean getDebug() + { + return _debug; + } + + /** + * Gets the ExtraOptions attribute + * + * @return The ExtraOptions value + */ + public String getExtraOptions() + { + return this._extraOptions; + } + + /** + * query fail on error flag + * + * @return The FailFailOnError value + */ + public boolean getFailFailOnError() + { + return _failOnError; + } + + /** + * accessor method for target type + * + * @return the current target option + */ + public String getTargetType() + { + return _targetType; + } + + /** + * reset all contents. + */ + public void Clear() + { + _targetType = null; + _srcDir = null; + _listing = false; + _verbose = false; + _debug = true; + _outputFile = null; + _failOnError = true; + _resourceFile = null; + _extraOptions = null; + } + + + /** + * This is the execution entry point. Build a list of files and call ilasm + * on each of them. + * + * @throws BuildException if the assembly failed and FailOnError is true + */ + public void execute() + throws BuildException + { + if( _srcDir == null ) + _srcDir = project.resolveFile( "." ); + + //get dependencies list. + DirectoryScanner scanner = super.getDirectoryScanner( _srcDir ); + String[] dependencies = scanner.getIncludedFiles(); + log( "assembling " + dependencies.length + " file" + ( ( dependencies.length == 1 ) ? "" : "s" ) ); + String baseDir = scanner.getBasedir().toString(); + //add to the command + for( int i = 0; i < dependencies.length; i++ ) + { + String targetFile = dependencies[i]; + targetFile = baseDir + File.separator + targetFile; + executeOneFile( targetFile ); + } + + }// end execute + + + /** + * do the work for one file by building the command line then calling it + * + * @param targetFile name of the the file to assemble + * @throws BuildException if the assembly failed and FailOnError is true + */ + public void executeOneFile( String targetFile ) + throws BuildException + { + NetCommand command = new NetCommand( this, exe_title, exe_name ); + command.setFailOnError( getFailFailOnError() ); + //DEBUG helper + command.setTraceCommandLine( true ); + //fill in args + command.addArgument( getDebugParameter() ); + command.addArgument( getTargetTypeParameter() ); + command.addArgument( getListingParameter() ); + command.addArgument( getOutputFileParameter() ); + command.addArgument( getResourceFileParameter() ); + command.addArgument( getVerboseParameter() ); + command.addArgument( getKeyfileParameter() ); + command.addArgument( getExtraOptionsParameter() ); + + /* + * space for more argumentativeness + * command.addArgument(); + * command.addArgument(); + */ + command.addArgument( targetFile ); + //now run the command of exe + settings + file + command.runCommand(); + } + + /** + * get the argument or null for no argument needed + * + * @return The DebugParameter value + */ + protected String getDebugParameter() + { + return _debug ? "/debug" : null; + } + + /** + * get any extra options or null for no argument needed + * + * @return The ExtraOptions Parameter to CSC + */ + protected String getExtraOptionsParameter() + { + if( _extraOptions != null && _extraOptions.length() != 0 ) + return _extraOptions; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The KeyfileParameter value + */ + protected String getKeyfileParameter() + { + if( _keyfile != null ) + return "/keyfile:" + _keyfile.toString(); + else + return null; + } + + /** + * turn the listing flag into a parameter for ILASM + * + * @return the appropriate string from the state of the listing flag + */ + protected String getListingParameter() + { + return _listing ? "/listing" : "/nolisting"; + } + + /** + * get the output file + * + * @return the argument string or null for no argument + */ + protected String getOutputFileParameter() + { + if( _outputFile == null || _outputFile.length() == 0 ) + return null; + File f = _outputFile; + return "/output=" + f.toString(); + } + + protected String getResourceFileParameter() + { + if( _resourceFile != null ) + { + return "/resource=" + _resourceFile.toString(); + } + else + { + return null; + } + } + + /** + * g get the target type or null for no argument needed + * + * @return The TargetTypeParameter value + */ + + protected String getTargetTypeParameter() + { + if( !notEmpty( _targetType ) ) + return null; + if( _targetType.equals( "exe" ) ) + return "/exe"; + else + if( _targetType.equals( "library" ) ) + return "/dll"; + else + return null; + } + + /** + * turn the verbose flag into a parameter for ILASM + * + * @return null or the appropriate command line string + */ + protected String getVerboseParameter() + { + return _verbose ? null : "/quiet"; + } + + /** + * test for a string containing something useful + * + * @param s Description of Parameter + * @return Description of the Returned Value + * @returns true if the argument is not null or empty + */ + protected boolean notEmpty( String s ) + { + return s != null && s.length() != 0; + }// end executeOneFile +}//class diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java new file mode 100644 index 000000000..4f28dffe7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.dotnet;// imports +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * This is a helper class to spawn net commands out. In its initial form it + * contains no .net specifics, just contains all the command line/exe + * construction stuff. However, it may be handy in future to have a means of + * setting the path to point to the dotnet bin directory; in which case the + * shared code should go in here. + * + * @author Steve Loughran steve_l@iseran.com + * @version 0.3 + * @created 2000-11-01 + */ + +public class NetCommand +{ + + /** + * trace flag + */ + protected boolean _traceCommandLine = false; + + /** + * what is the command line + */ + protected Commandline _commandLine; + + /** + * executabe + */ + protected Execute _exe; + + /** + * flag to control action on execution trouble + */ + protected boolean _failOnError; + + /** + * owner project + */ + protected Task _owner; + + /** + * actual program to invoke + */ + protected String _program; + + /** + * title of the command + */ + protected String _title; + + /** + * constructor + * + * @param title (for logging/errors) + * @param owner Description of Parameter + * @param program Description of Parameter + */ + + public NetCommand( Task owner, String title, String program ) + { + _owner = owner; + _title = title; + _program = program; + _commandLine = new Commandline(); + _commandLine.setExecutable( _program ); + prepareExecutor(); + } + + /** + * set fail on error flag + * + * @param b fail flag -set to true to cause an exception to be raised if the + * return value != 0 + */ + public void setFailOnError( boolean b ) + { + _failOnError = b; + } + + /** + * turn tracing on or off + * + * @param b trace flag + */ + public void setTraceCommandLine( boolean b ) + { + _traceCommandLine = b; + } + + /** + * query fail on error flag + * + * @return The FailFailOnError value + */ + public boolean getFailFailOnError() + { + return _failOnError; + } + + /** + * add an argument to a command line; do nothing if the arg is null or empty + * string + * + * @param argument The feature to be added to the Argument attribute + */ + public void addArgument( String argument ) + { + if( argument != null && argument.length() != 0 ) + { + _commandLine.createArgument().setValue( argument ); + } + } + + /** + * Run the command using the given Execute instance. + * + * @exception BuildException Description of Exception + * @throws an exception of something goes wrong and the failOnError flag is + * true + */ + public void runCommand() + throws BuildException + { + int err = -1;// assume the worst + try + { + if( _traceCommandLine ) + { + _owner.log( _commandLine.toString() ); + } + else + { + //in verbose mode we always log stuff + logVerbose( _commandLine.toString() ); + } + _exe.setCommandline( _commandLine.getCommandline() ); + err = _exe.execute(); + if( err != 0 ) + { + if( _failOnError ) + { + throw new BuildException( _title + " returned: " + err, _owner.getLocation() ); + } + else + { + _owner.log( _title + " Result: " + err, Project.MSG_ERR ); + } + } + } + catch( IOException e ) + { + throw new BuildException( _title + " failed: " + e, e, _owner.getLocation() ); + } + } + + + /** + * error text log + * + * @param msg message to display as an error + */ + protected void logError( String msg ) + { + _owner.getProject().log( msg, Project.MSG_ERR ); + } + + /** + * verbose text log + * + * @param msg string to add to log iff verbose is defined for the build + */ + protected void logVerbose( String msg ) + { + _owner.getProject().log( msg, Project.MSG_VERBOSE ); + } + + /** + * set up the command sequence.. + */ + protected void prepareExecutor() + { + // default directory to the project's base directory + File dir = _owner.getProject().getBaseDir(); + ExecuteStreamHandler handler = new LogStreamHandler( _owner, + Project.MSG_INFO, Project.MSG_WARN ); + _exe = new Execute( handler, null ); + _exe.setAntRun( _owner.getProject() ); + _exe.setWorkingDirectory( dir ); + } +}//class diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java new file mode 100644 index 000000000..ff7a72719 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + + +/** + * BorlandDeploymentTool is dedicated to the Borland Application Server 4.5 and + * 4.5.1 This task generates and compiles the stubs and skeletons for all ejb + * described into the Deployement Descriptor, builds the jar file including the + * support files and verify whether the produced jar is valid or not. The + * supported options are: + *

        + *
      • debug (boolean) : turn on the debug mode for generation of stubs and + * skeletons (default:false)
      • + *
      • verify (boolean) : turn on the verification at the end of the jar + * production (default:true)
      • + *
      • verifyargs (String) : add optional argument to verify command (see vbj + * com.inprise.ejb.util.Verify)
      • + *
      • basdtd (String) : location of the BAS DTD
      • + *
      • generateclient (boolean) : turn on the client jar file generation + *
      • + *
      + *
      + *
      + *      <ejbjar srcdir="${build.classes}"  basejarname="vsmp"  descriptordir="${rsc.dir}/hrmanager">
      + *        <borland destdir="tstlib">
      + *          <classpath refid="classpath" />
      + *        </borland>
      + *        <include name="**\ejb-jar.xml"/>
      + *        <support dir="${build.classes}">
      + *          <include name="demo\smp\*.class"/>
      + *          <include name="demo\helper\*.class"/>
      + *         </support>
      + *     </ejbjar>
      + *
      + * + * @author Benoit Moussaud + */ +public class BorlandDeploymentTool extends GenericDeploymentTool implements ExecuteStreamHandler +{ + public final static String PUBLICID_BORLAND_EJB + = "-//Inprise Corporation//DTD Enterprise JavaBeans 1.1//EN"; + + protected final static String DEFAULT_BAS45_EJB11_DTD_LOCATION + = "/com/inprise/j2ee/xml/dtds/ejb-jar.dtd"; + + protected final static String DEFAULT_BAS_DTD_LOCATION + = "/com/inprise/j2ee/xml/dtds/ejb-inprise.dtd"; + + protected final static String BAS_DD = "ejb-inprise.xml"; + + /** + * Java2iiop executable * + */ + protected final static String JAVA2IIOP = "java2iiop"; + + /** + * Verify class + */ + protected final static String VERIFY = "com.inprise.ejb.util.Verify"; + + /** + * Instance variable that stores the suffix for the borland jarfile. + */ + private String jarSuffix = "-ejb.jar"; + + /** + * Instance variable that determines whether the debug mode is on + */ + private boolean java2iiopdebug = false; + + /** + * Instance variable that determines whetger the client jar file is + * generated + */ + private boolean generateclient = false; + /** + * Instance variable that determines whether it is necessary to verify the + * produced jar + */ + private boolean verify = true; + private String verifyArgs = ""; + + private Hashtable _genfiles = new Hashtable(); + + /** + * Instance variable that stores the location of the borland DTD file. + */ + private String borlandDTD; + + /** + * Setter used to store the location of the borland DTD. This can be a file + * on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setBASdtd( String inString ) + { + this.borlandDTD = inString; + } + + /** + * set the debug mode for java2iiop (default false) + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.java2iiopdebug = debug; + } + + + /** + * setter used to store whether the task will include the generate client + * task. (see : BorlandGenerateClient task) + * + * @param b The new Generateclient value + */ + public void setGenerateclient( boolean b ) + { + this.generateclient = b; + } + + /** + * @param is The new ProcessErrorStream value + * @exception IOException Description of Exception + */ + public void setProcessErrorStream( InputStream is ) + throws IOException + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + String s = reader.readLine(); + if( s != null ) + { + log( "[java2iiop] " + s, Project.MSG_DEBUG ); + }// end of if () + } + + public void setProcessInputStream( OutputStream param1 ) + throws IOException { } + + /** + * @param is + * @exception IOException Description of Exception + */ + public void setProcessOutputStream( InputStream is ) + throws IOException + { + try + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + String javafile; + while( ( javafile = reader.readLine() ) != null ) + { + log( "buffer:" + javafile, Project.MSG_DEBUG ); + if( javafile.endsWith( ".java" ) ) + { + String classfile = toClassFile( javafile ); + String key = classfile.substring( getConfig().srcDir.getAbsolutePath().length() + 1 ); + log( " generated : " + classfile, Project.MSG_DEBUG ); + log( " key : " + key, Project.MSG_DEBUG ); + _genfiles.put( key, new File( classfile ) ); + }// end of if () + }// end of while () + reader.close(); + } + catch( Exception e ) + { + String msg = "Exception while parsing java2iiop output. Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } + + + /** + * Setter used to store the suffix for the generated borland jar file. + * + * @param inString the string to use as the suffix. + */ + public void setSuffix( String inString ) + { + this.jarSuffix = inString; + } + + /** + * set the verify mode for the produced jar (default true) + * + * @param verify The new Verify value + */ + public void setVerify( boolean verify ) + { + this.verify = verify; + } + + + /** + * sets some additional args to send to verify command + * + * @param args addtions command line parameters + */ + public void setVerifyArgs( String args ) + { + this.verifyArgs = args; + } + + // implementation of org.apache.tools.ant.taskdefs.ExecuteStreamHandler interface + + public void start() + throws IOException { } + + public void stop() { } + + + protected DescriptorHandler getBorlandDescriptorHandler( final File srcDir ) + { + DescriptorHandler handler = + new DescriptorHandler( getTask(), srcDir ) + { + protected void processElement() + { + if( currentElement.equals( "type-storage" ) ) + { + // Get the filename of vendor specific descriptor + String fileNameWithMETA = currentText; + //trim the META_INF\ off of the file name + String fileName = fileNameWithMETA.substring( META_DIR.length(), + fileNameWithMETA.length() ); + File descriptorFile = new File( srcDir, fileName ); + + ejbFiles.put( fileNameWithMETA, descriptorFile ); + } + } + }; + handler.registerDTD( PUBLICID_BORLAND_EJB, + borlandDTD == null ? DEFAULT_BAS_DTD_LOCATION : borlandDTD ); + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + } + return handler; + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + + File borlandDD = new File( getConfig().descriptorDir, ddPrefix + BAS_DD ); + if( borlandDD.exists() ) + { + log( "Borland specific file found " + borlandDD, Project.MSG_VERBOSE ); + ejbFiles.put( META_DIR + BAS_DD, borlandDD ); + } + else + { + log( "Unable to locate borland deployment descriptor. It was expected to be in " + + borlandDD.getPath(), Project.MSG_WARN ); + return; + } + } + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarFile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + protected void writeJar( String baseName, File jarFile, Hashtable files, String publicId ) + throws BuildException + { + //build the home classes list. + Vector homes = new Vector(); + Iterator it = files.keySet().iterator(); + while( it.hasNext() ) + { + String clazz = ( String )it.next(); + if( clazz.endsWith( "Home.class" ) ) + { + //remove .class extension + String home = toClass( clazz ); + homes.add( home ); + log( " Home " + home, Project.MSG_VERBOSE ); + }// end of if () + }// end of while () + + buildBorlandStubs( homes.iterator(), files ); + + //add the gen files to the collection + files.putAll( _genfiles ); + + super.writeJar( baseName, jarFile, files, publicId ); + + if( verify ) + { + verifyBorlandJar( jarFile ); + }// end of if () + + if( generateclient ) + { + generateClient( jarFile ); + }// end of if () + } + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( getDestDir(), baseName + jarSuffix ); + } + + /** + * Generate stubs & sketelton for each home found into the DD Add all the + * generate class file into the ejb files + * + * @param ithomes : iterator on home class + * @param files : file list , updated by the adding generated files + */ + private void buildBorlandStubs( Iterator ithomes, Hashtable files ) + { + Execute execTask = null; + + execTask = new Execute( this ); + Project project = getTask().getProject(); + execTask.setAntRun( project ); + execTask.setWorkingDirectory( project.getBaseDir() ); + + Commandline commandline = new Commandline(); + commandline.setExecutable( JAVA2IIOP ); + //debug ? + if( java2iiopdebug ) + { + commandline.createArgument().setValue( "-VBJdebug" ); + }// end of if () + //set the classpath + commandline.createArgument().setValue( "-VBJclasspath" ); + commandline.createArgument().setPath( getCombinedClasspath() ); + //list file + commandline.createArgument().setValue( "-list_files" ); + //no TIE classes + commandline.createArgument().setValue( "-no_tie" ); + //root dir + commandline.createArgument().setValue( "-root_dir" ); + commandline.createArgument().setValue( getConfig().srcDir.getAbsolutePath() ); + //compiling order + commandline.createArgument().setValue( "-compile" ); + //add the home class + while( ithomes.hasNext() ) + { + commandline.createArgument().setValue( ithomes.next().toString() ); + }// end of while () + + try + { + log( "Calling java2iiop", Project.MSG_VERBOSE ); + log( commandline.toString(), Project.MSG_DEBUG ); + execTask.setCommandline( commandline.getCommandline() ); + int result = execTask.execute(); + if( result != 0 ) + { + String msg = "Failed executing java2iiop (ret code is " + result + ")"; + throw new BuildException( msg, getTask().getLocation() ); + } + } + catch( java.io.IOException e ) + { + log( "java2iiop exception :" + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( e ); + } + } + + /** + * Generate the client jar corresponding to the jar file passed as paremeter + * the method uses the BorlandGenerateClient task. + * + * @param sourceJar java.io.File representing the produced jar file + */ + private void generateClient( File sourceJar ) + { + getTask().getProject().addTaskDefinition( "internal_bas_generateclient", + org.apache.tools.ant.taskdefs.optional.ejb.BorlandGenerateClient.class ); + + org.apache.tools.ant.taskdefs.optional.ejb.BorlandGenerateClient gentask = null; + log( "generate client for " + sourceJar, Project.MSG_INFO ); + try + { + String args = verifyArgs; + args += " " + sourceJar.getPath(); + + gentask = ( BorlandGenerateClient )getTask().getProject().createTask( "internal_bas_generateclient" ); + gentask.setEjbjar( sourceJar ); + gentask.setDebug( java2iiopdebug ); + Path classpath = getCombinedClasspath(); + if( classpath != null ) + { + gentask.setClasspath( classpath ); + } + gentask.setTaskName( "generate client" ); + gentask.execute(); + } + catch( Exception e ) + { + //TO DO : delete the file if it is not a valid file. + String msg = "Exception while calling " + VERIFY + " Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } + + /** + * convert a class file name : A/B/C/toto.class into a class name: + * A.B.C.toto + * + * @param filename Description of Parameter + * @return Description of the Returned Value + */ + private String toClass( String filename ) + { + //remove the .class + String classname = filename.substring( 0, filename.lastIndexOf( ".class" ) ); + classname = classname.replace( '\\', '.' ); + return classname; + } + + /** + * convert a file name : A/B/C/toto.java into a class name: A/B/C/toto.class + * + * @param filename Description of Parameter + * @return Description of the Returned Value + */ + private String toClassFile( String filename ) + { + //remove the .class + String classfile = filename.substring( 0, filename.lastIndexOf( ".java" ) ); + classfile = classfile + ".class"; + return classfile; + } + + /** + * Verify the produced jar file by invoking the Borland verify tool + * + * @param sourceJar java.io.File representing the produced jar file + */ + private void verifyBorlandJar( File sourceJar ) + { + org.apache.tools.ant.taskdefs.Java javaTask = null; + log( "verify " + sourceJar, Project.MSG_INFO ); + try + { + + String args = verifyArgs; + args += " " + sourceJar.getPath(); + + javaTask = ( Java )getTask().getProject().createTask( "java" ); + javaTask.setTaskName( "verify" ); + javaTask.setClassname( VERIFY ); + Commandline.Argument arguments = javaTask.createArg(); + arguments.setLine( args ); + Path classpath = getCombinedClasspath(); + if( classpath != null ) + { + javaTask.setClasspath( classpath ); + javaTask.setFork( true ); + } + + log( "Calling " + VERIFY + " for " + sourceJar.toString(), Project.MSG_VERBOSE ); + javaTask.execute(); + } + catch( Exception e ) + { + //TO DO : delete the file if it is not a valid file. + String msg = "Exception while calling " + VERIFY + " Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java new file mode 100644 index 000000000..1dab7dd20 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.ExecTask; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + + +/** + * BorlandGenerateClient is dedicated to the Borland Application Server 4.5 This + * task generates the client jar using as input the ejb jar file. Two mode are + * available: java mode (default) and fork mode. With the fork mode, it is + * impossible to add classpath to the commmand line. + * + * @author Benoit Moussaud + */ +public class BorlandGenerateClient extends Task +{ + final static String JAVA_MODE = "java"; + final static String FORK_MODE = "fork"; + + /** + * debug the generateclient task + */ + boolean debug = false; + + /** + * hold the ejbjar file name + */ + File ejbjarfile = null; + + /** + * hold the client jar file name + */ + File clientjarfile = null; + + /** + * hold the mode (java|fork) + */ + String mode = JAVA_MODE; + + /** + * hold the classpath + */ + Path classpath; + + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setClientjar( File clientjar ) + { + clientjarfile = clientjar; + } + + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + public void setEjbjar( File ejbfile ) + { + ejbjarfile = ejbfile; + } + + public void setMode( String s ) + { + mode = s; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * a java task. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( ejbjarfile == null || + ejbjarfile.isDirectory() ) + { + throw new BuildException( "invalid ejb jar file." ); + }// end of if () + + if( clientjarfile == null || + clientjarfile.isDirectory() ) + { + log( "invalid or missing client jar file.", Project.MSG_VERBOSE ); + String ejbjarname = ejbjarfile.getAbsolutePath(); + //clientname = ejbjarfile+client.jar + String clientname = ejbjarname.substring( 0, ejbjarname.lastIndexOf( "." ) ); + clientname = clientname + "client.jar"; + clientjarfile = new File( clientname ); + + }// end of if () + + if( mode == null ) + { + log( "mode is null default mode is java" ); + setMode( JAVA_MODE ); + }// end of if () + + log( "client jar file is " + clientjarfile ); + + if( mode.equalsIgnoreCase( FORK_MODE ) ) + { + executeFork(); + }// end of if () + else + { + executeJava(); + }// end of else + } + + /** + * launch the generate client using system api + * + * @exception BuildException Description of Exception + */ + protected void executeFork() + throws BuildException + { + try + { + log( "mode : fork" ); + + org.apache.tools.ant.taskdefs.ExecTask execTask = null; + execTask = ( ExecTask )getProject().createTask( "exec" ); + + execTask.setDir( new File( "." ) ); + execTask.setExecutable( "iastool" ); + execTask.createArg().setValue( "generateclient" ); + if( debug ) + { + execTask.createArg().setValue( "-trace" ); + }// end of if () + + // + execTask.createArg().setValue( "-short" ); + execTask.createArg().setValue( "-jarfile" ); + // ejb jar file + execTask.createArg().setValue( ejbjarfile.getAbsolutePath() ); + //client jar file + execTask.createArg().setValue( "-single" ); + execTask.createArg().setValue( "-clientjarfile" ); + execTask.createArg().setValue( clientjarfile.getAbsolutePath() ); + + log( "Calling java2iiop", Project.MSG_VERBOSE ); + execTask.execute(); + } + catch( Exception e ) + { + // Have to catch this because of the semantics of calling main() + String msg = "Exception while calling generateclient Details: " + e.toString(); + throw new BuildException( msg, e ); + } + + } + + /** + * launch the generate client using java api + * + * @exception BuildException Description of Exception + */ + protected void executeJava() + throws BuildException + { + try + { + log( "mode : java" ); + + org.apache.tools.ant.taskdefs.Java execTask = null; + execTask = ( Java )getProject().createTask( "java" ); + + execTask.setDir( new File( "." ) ); + execTask.setClassname( "com.inprise.server.commandline.EJBUtilities" ); + //classpath + //add at the end of the classpath + //the system classpath in order to find the tools.jar file + execTask.setClasspath( classpath.concatSystemClasspath() ); + + execTask.setFork( true ); + execTask.createArg().setValue( "generateclient" ); + if( debug ) + { + execTask.createArg().setValue( "-trace" ); + }// end of if () + + // + execTask.createArg().setValue( "-short" ); + execTask.createArg().setValue( "-jarfile" ); + // ejb jar file + execTask.createArg().setValue( ejbjarfile.getAbsolutePath() ); + //client jar file + execTask.createArg().setValue( "-single" ); + execTask.createArg().setValue( "-clientjarfile" ); + execTask.createArg().setValue( clientjarfile.getAbsolutePath() ); + + log( "Calling EJBUtilities", Project.MSG_VERBOSE ); + execTask.execute(); + + } + catch( Exception e ) + { + // Have to catch this because of the semantics of calling main() + String msg = "Exception while calling generateclient Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java new file mode 100644 index 000000000..25218e180 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * Build a serialised deployment descriptor given a text file description of the + * descriptor in the format supported by WebLogic. This ant task is a front end + * for the weblogic DDCreator tool. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class DDCreator extends MatchingTask +{ + + /** + * The classpath to be used in the weblogic ejbc calls. It must contain the + * weblogic classes necessary fro DDCreator and the implementation + * classes of the home and remote interfaces. + */ + private String classpath; + /** + * The root directory of the tree containing the textual deployment + * desciptors. The actual deployment descriptor files are selected using + * include and exclude constructs on the EJBC task, as supported by the + * MatchingTask superclass. + */ + private File descriptorDirectory; + + /** + * The directory where generated serialised deployment descriptors are + * placed. + */ + private File generatedFilesDirectory; + + /** + * Set the classpath to be used for this compilation. + * + * @param s the classpath to use for the ddcreator tool. + */ + public void setClasspath( String s ) + { + this.classpath = project.translatePath( s ); + } + + /** + * Set the directory from where the text descriptions of the deployment + * descriptors are to be read. + * + * @param dirName the name of the directory containing the text deployment + * descriptor files. + */ + public void setDescriptors( String dirName ) + { + descriptorDirectory = new File( dirName ); + } + + /** + * Set the directory into which the serialised deployment descriptors are to + * be written. + * + * @param dirName the name of the directory into which the serialised + * deployment descriptors are written. + */ + public void setDest( String dirName ) + { + generatedFilesDirectory = new File( dirName ); + } + + /** + * Do the work. The work is actually done by creating a helper task. This + * approach allows the classpath of the helper task to be set. Since the + * weblogic tools require the class files of the project's home and remote + * interfaces to be available in the classpath, this also avoids having to + * start ant with the class path of the project it is building. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( descriptorDirectory == null || + !descriptorDirectory.isDirectory() ) + { + throw new BuildException( "descriptors directory " + descriptorDirectory.getPath() + + " is not valid" ); + } + if( generatedFilesDirectory == null || + !generatedFilesDirectory.isDirectory() ) + { + throw new BuildException( "dest directory " + generatedFilesDirectory.getPath() + + " is not valid" ); + } + + String args = descriptorDirectory + " " + generatedFilesDirectory; + + // get all the files in the descriptor directory + DirectoryScanner ds = super.getDirectoryScanner( descriptorDirectory ); + + String[] files = ds.getIncludedFiles(); + + for( int i = 0; i < files.length; ++i ) + { + args += " " + files[i]; + } + + String systemClassPath = System.getProperty( "java.class.path" ); + String execClassPath = project.translatePath( systemClassPath + ":" + classpath ); + Java ddCreatorTask = ( Java )project.createTask( "java" ); + ddCreatorTask.setTaskName( getTaskName() ); + ddCreatorTask.setFork( true ); + ddCreatorTask.setClassname( "org.apache.tools.ant.taskdefs.optional.ejb.DDCreatorHelper" ); + Commandline.Argument arguments = ddCreatorTask.createArg(); + arguments.setLine( args ); + ddCreatorTask.setClasspath( new Path( project, execClassPath ) ); + if( ddCreatorTask.executeJava() != 0 ) + { + throw new BuildException( "Execution of ddcreator helper failed" ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java new file mode 100644 index 000000000..f59a3288c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import javax.ejb.deployment.DeploymentDescriptor; + +/** + * A helper class which performs the actual work of the ddcreator task. This + * class is run with a classpath which includes the weblogic tools and the home + * and remote interface class files referenced in the deployment descriptors + * being built. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class DDCreatorHelper +{ + + /** + * The descriptor text files for which a serialised descriptor is to be + * created. + */ + String[] descriptors; + /** + * The root directory of the tree containing the textual deployment + * desciptors. + */ + private File descriptorDirectory; + + /** + * The directory where generated serialised desployment descriptors are + * written. + */ + private File generatedFilesDirectory; + + /** + * Initialise the helper with the command arguments. + * + * @param args Description of Parameter + */ + private DDCreatorHelper( String[] args ) + { + int index = 0; + descriptorDirectory = new File( args[index++] ); + generatedFilesDirectory = new File( args[index++] ); + + descriptors = new String[args.length - index]; + for( int i = 0; index < args.length; ++i ) + { + descriptors[i] = args[index++]; + } + } + + /** + * The main method. The main method creates an instance of the + * DDCreatorHelper, passing it the args which it then processes. + * + * @param args The command line arguments + * @exception Exception Description of Exception + */ + public static void main( String[] args ) + throws Exception + { + DDCreatorHelper helper = new DDCreatorHelper( args ); + helper.process(); + } + + /** + * Do the actual work. The work proceeds by examining each descriptor given. + * If the serialised file does not exist or is older than the text + * description, the weblogic DDCreator tool is invoked directly to build the + * serialised descriptor. + * + * @exception Exception Description of Exception + */ + private void process() + throws Exception + { + for( int i = 0; i < descriptors.length; ++i ) + { + String descriptorName = descriptors[i]; + File descriptorFile = new File( descriptorDirectory, descriptorName ); + + int extIndex = descriptorName.lastIndexOf( "." ); + String serName = null; + if( extIndex != -1 ) + { + serName = descriptorName.substring( 0, extIndex ) + ".ser"; + } + else + { + serName = descriptorName + ".ser"; + } + File serFile = new File( generatedFilesDirectory, serName ); + + // do we need to regenerate the file + if( !serFile.exists() || serFile.lastModified() < descriptorFile.lastModified() + || regenerateSerializedFile( serFile ) ) + { + + String[] args = {"-noexit", + "-d", serFile.getParent(), + "-outputfile", serFile.getName(), + descriptorFile.getPath()}; + try + { + weblogic.ejb.utils.DDCreator.main( args ); + } + catch( Exception e ) + { + // there was an exception - run with no exit to get proper error + String[] newArgs = {"-d", generatedFilesDirectory.getPath(), + "-outputfile", serFile.getName(), + descriptorFile.getPath()}; + weblogic.ejb.utils.DDCreator.main( newArgs ); + } + } + } + } + + /** + * EJBC will fail if the serialized descriptor file does not match the bean + * classes. You can test for this by trying to load the deployment + * descriptor. If it fails, the serialized file needs to be regenerated + * because the associated class files don't match. + * + * @param serFile Description of Parameter + * @return Description of the Returned Value + */ + private boolean regenerateSerializedFile( File serFile ) + { + try + { + + FileInputStream fis = new FileInputStream( serFile ); + ObjectInputStream ois = new ObjectInputStream( fis ); + DeploymentDescriptor dd = ( DeploymentDescriptor )ois.readObject(); + fis.close(); + + // Since the descriptor read properly, everything should be o.k. + return false; + } + catch( Exception e ) + { + + // Weblogic will throw an error if the deployment descriptor does + // not match the class files. + return true; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java new file mode 100644 index 000000000..4f0a226cc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Hashtable; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.xml.sax.AttributeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Inner class used by EjbJar to facilitate the parsing of deployment + * descriptors and the capture of appropriate information. Extends HandlerBase + * so it only implements the methods needed. During parsing creates a hashtable + * consisting of entries mapping the name it should be inserted into an EJB jar + * as to a File representing the file on disk. This list can then be accessed + * through the getFiles() method. + * + * @author RT + */ +public class DescriptorHandler extends org.xml.sax.HandlerBase +{ + private final static int STATE_LOOKING_EJBJAR = 1; + private final static int STATE_IN_EJBJAR = 2; + private final static int STATE_IN_BEANS = 3; + private final static int STATE_IN_SESSION = 4; + private final static int STATE_IN_ENTITY = 5; + private final static int STATE_IN_MESSAGE = 6; + + /** + * Bunch of constants used for storing entries in a hashtable, and for + * constructing the filenames of various parts of the ejb jar. + */ + private final static String EJB_REF = "ejb-ref"; + private final static String HOME_INTERFACE = "home"; + private final static String REMOTE_INTERFACE = "remote"; + private final static String LOCAL_HOME_INTERFACE = "local-home"; + private final static String LOCAL_INTERFACE = "local"; + private final static String BEAN_CLASS = "ejb-class"; + private final static String PK_CLASS = "prim-key-class"; + private final static String EJB_NAME = "ejb-name"; + private final static String EJB_JAR = "ejb-jar"; + private final static String ENTERPRISE_BEANS = "enterprise-beans"; + private final static String ENTITY_BEAN = "entity"; + private final static String SESSION_BEAN = "session"; + private final static String MESSAGE_BEAN = "message-driven"; + + private String publicId = null; + + /** + * The state of the parsing + */ + private int parseState = STATE_LOOKING_EJBJAR; + + /** + * Instance variable used to store the name of the current element being + * processed by the SAX parser. Accessed by the SAX parser call-back methods + * startElement() and endElement(). + */ + protected String currentElement = null; + + /** + * The text of the current element + */ + protected String currentText = null; + + /** + * Instance variable that stores the names of the files as they will be put + * into the jar file, mapped to File objects Accessed by the SAX parser + * call-back method characters(). + */ + protected Hashtable ejbFiles = null; + + /** + * Instance variable that stores the value found in the <ejb-name> + * element + */ + protected String ejbName = null; + + private Hashtable fileDTDs = new Hashtable(); + + private Hashtable resourceDTDs = new Hashtable(); + + private boolean inEJBRef = false; + + private Hashtable urlDTDs = new Hashtable(); + + private Task owningTask; + + /** + * The directory containing the bean classes and interfaces. This is used + * for performing dependency file lookups. + */ + private File srcDir; + + public DescriptorHandler( Task task, File srcDir ) + { + this.owningTask = task; + this.srcDir = srcDir; + } + + /** + * Getter method that returns the value of the <ejb-name> element. + * + * @return The EjbName value + */ + public String getEjbName() + { + return ejbName; + } + + /** + * Getter method that returns the set of files to include in the EJB jar. + * + * @return The Files value + */ + public Hashtable getFiles() + { + return ( ejbFiles == null ) ? new Hashtable() : ejbFiles; + } + + /** + * Get the publicId of the DTD + * + * @return The PublicId value + */ + public String getPublicId() + { + return publicId; + } + + /** + * SAX parser call-back method invoked whenever characters are located + * within an element. currentAttribute (modified by startElement and + * endElement) tells us whether we are in an interesting element (one of the + * up to four classes of an EJB). If so then converts the classname from the + * format org.apache.tools.ant.Parser to the convention for storing such a + * class, org/apache/tools/ant/Parser.class. This is then resolved into a + * file object under the srcdir which is stored in a Hashtable. + * + * @param ch A character array containing all the characters in the element, + * and maybe others that should be ignored. + * @param start An integer marking the position in the char array to start + * reading from. + * @param length An integer representing an offset into the char array where + * the current data terminates. + * @exception SAXException Description of Exception + */ + public void characters( char[] ch, int start, int length ) + throws SAXException + { + + currentText += new String( ch, start, length ); + } + + + /** + * SAX parser call-back method that is invoked when an element is exited. + * Used to blank out (set to the empty string, not nullify) the name of the + * currentAttribute. A better method would be to use a stack as an instance + * variable, however since we are only interested in leaf-node data this is + * a simpler and workable solution. + * + * @param name The name of the attribute being exited. Ignored in this + * implementation. + * @exception SAXException Description of Exception + */ + public void endElement( String name ) + throws SAXException + { + processElement(); + currentText = ""; + this.currentElement = ""; + if( name.equals( EJB_REF ) ) + { + inEJBRef = false; + } + else if( parseState == STATE_IN_ENTITY && name.equals( ENTITY_BEAN ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_SESSION && name.equals( SESSION_BEAN ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_MESSAGE && name.equals( MESSAGE_BEAN ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_BEANS && name.equals( ENTERPRISE_BEANS ) ) + { + parseState = STATE_IN_EJBJAR; + } + else if( parseState == STATE_IN_EJBJAR && name.equals( EJB_JAR ) ) + { + parseState = STATE_LOOKING_EJBJAR; + } + } + + public void registerDTD( String publicId, String location ) + { + if( location == null ) + { + return; + } + + File fileDTD = new File( location ); + if( fileDTD.exists() ) + { + if( publicId != null ) + { + fileDTDs.put( publicId, fileDTD ); + owningTask.log( "Mapped publicId " + publicId + " to file " + fileDTD, Project.MSG_VERBOSE ); + } + return; + } + + if( getClass().getResource( location ) != null ) + { + if( publicId != null ) + { + resourceDTDs.put( publicId, location ); + owningTask.log( "Mapped publicId " + publicId + " to resource " + location, Project.MSG_VERBOSE ); + } + } + + try + { + if( publicId != null ) + { + URL urldtd = new URL( location ); + urlDTDs.put( publicId, urldtd ); + } + } + catch( java.net.MalformedURLException e ) + { + //ignored + } + + } + + public InputSource resolveEntity( String publicId, String systemId ) + throws SAXException + { + this.publicId = publicId; + + File dtdFile = ( File )fileDTDs.get( publicId ); + if( dtdFile != null ) + { + try + { + owningTask.log( "Resolved " + publicId + " to local file " + dtdFile, Project.MSG_VERBOSE ); + return new InputSource( new FileInputStream( dtdFile ) ); + } + catch( FileNotFoundException ex ) + { + // ignore + } + } + + String dtdResourceName = ( String )resourceDTDs.get( publicId ); + if( dtdResourceName != null ) + { + InputStream is = this.getClass().getResourceAsStream( dtdResourceName ); + if( is != null ) + { + owningTask.log( "Resolved " + publicId + " to local resource " + dtdResourceName, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + } + + URL dtdUrl = ( URL )urlDTDs.get( publicId ); + if( dtdUrl != null ) + { + try + { + InputStream is = dtdUrl.openStream(); + owningTask.log( "Resolved " + publicId + " to url " + dtdUrl, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + catch( IOException ioe ) + { + //ignore + } + } + + owningTask.log( "Could not resolve ( publicId: " + publicId + ", systemId: " + systemId + ") to a local entity", + Project.MSG_INFO ); + + return null; + } + + /** + * SAX parser call-back method that is used to initialize the values of some + * instance variables to ensure safe operation. + * + * @exception SAXException Description of Exception + */ + public void startDocument() + throws SAXException + { + this.ejbFiles = new Hashtable( 10, 1 ); + this.currentElement = null; + inEJBRef = false; + } + + + /** + * SAX parser call-back method that is invoked when a new element is entered + * into. Used to store the context (attribute name) in the currentAttribute + * instance variable. + * + * @param name The name of the element being entered. + * @param attrs Attributes associated to the element. + * @exception SAXException Description of Exception + */ + public void startElement( String name, AttributeList attrs ) + throws SAXException + { + this.currentElement = name; + currentText = ""; + if( name.equals( EJB_REF ) ) + { + inEJBRef = true; + } + else if( parseState == STATE_LOOKING_EJBJAR && name.equals( EJB_JAR ) ) + { + parseState = STATE_IN_EJBJAR; + } + else if( parseState == STATE_IN_EJBJAR && name.equals( ENTERPRISE_BEANS ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_BEANS && name.equals( SESSION_BEAN ) ) + { + parseState = STATE_IN_SESSION; + } + else if( parseState == STATE_IN_BEANS && name.equals( ENTITY_BEAN ) ) + { + parseState = STATE_IN_ENTITY; + } + else if( parseState == STATE_IN_BEANS && name.equals( MESSAGE_BEAN ) ) + { + parseState = STATE_IN_MESSAGE; + } + } + + + protected void processElement() + { + if( inEJBRef || + ( parseState != STATE_IN_ENTITY && parseState != STATE_IN_SESSION && parseState != STATE_IN_MESSAGE ) ) + { + return; + } + + if( currentElement.equals( HOME_INTERFACE ) || + currentElement.equals( REMOTE_INTERFACE ) || + currentElement.equals( LOCAL_INTERFACE ) || + currentElement.equals( LOCAL_HOME_INTERFACE ) || + currentElement.equals( BEAN_CLASS ) || + currentElement.equals( PK_CLASS ) ) + { + + // Get the filename into a String object + File classFile = null; + String className = currentText.trim(); + + // If it's a primitive wrapper then we shouldn't try and put + // it into the jar, so ignore it. + if( !className.startsWith( "java." ) && + !className.startsWith( "javax." ) ) + { + // Translate periods into path separators, add .class to the + // name, create the File object and add it to the Hashtable. + className = className.replace( '.', File.separatorChar ); + className += ".class"; + classFile = new File( srcDir, className ); + ejbFiles.put( className, classFile ); + } + } + + // Get the value of the tag. Only the first occurence. + if( currentElement.equals( EJB_NAME ) ) + { + if( ejbName == null ) + { + ejbName = currentText.trim(); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java new file mode 100644 index 000000000..147faed55 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import javax.xml.parsers.SAXParser; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +public interface EJBDeploymentTool +{ + /** + * Process a deployment descriptor, generating the necessary vendor specific + * deployment files. + * + * @param descriptorFilename the name of the deployment descriptor + * @param saxParser a SAX parser which can be used to parse the deployment + * descriptor. + * @exception BuildException Description of Exception + */ + void processDescriptor( String descriptorFilename, SAXParser saxParser ) + throws BuildException; + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + void validateConfigured() + throws BuildException; + + /** + * Set the task which owns this tool + * + * @param task The new Task value + */ + void setTask( Task task ); + + /** + * Configure this tool for use in the ejbjar task. + * + * @param config Description of Parameter + */ + void configure( EjbJar.Config config ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java new file mode 100644 index 000000000..df7aa90bb --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java @@ -0,0 +1,567 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb;// Standard java imports +import java.io.File; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.xml.parsers.ParserConfigurationException;// XML imports +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory;// Apache/Ant imports +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.xml.sax.SAXException; + +/** + *

      + * + * Provides automated ejb jar file creation for ant. Extends the MatchingTask + * class provided in the default ant distribution to provide a directory + * scanning EJB jarfile generator.

      + * + * The task works by taking the deployment descriptors one at a time and parsing + * them to locate the names of the classes which should be placed in the jar. + * The classnames are translated to java.io.Files by replacing periods with + * File.separatorChar and resolving the generated filename as a relative path + * under the srcDir attribute. All necessary files are then assembled into a + * jarfile. One jarfile is constructed for each deployment descriptor found. + *

      + * + * Functionality is currently provided for standard EJB1.1 jars and Weblogic 5.1 + * jars. The weblogic deployment descriptors, used in constructing the Weblogic + * jar, are located based on a simple naming convention. The name of the + * standard deployment descriptor is taken upto the first instance of a String, + * specified by the attribute baseNameTerminator, and then the regular Weblogic + * descriptor name is appended. For example if baseNameTerminator is set to '-', + * its default value, and a standard descriptor is called Foo-ejb-jar.xml then + * the files Foo-weblogic-ejb-jar.xml and Foo-weblogic-cmp-rdbms-jar.xml will be + * looked for, and if found, included in the jarfile.

      + * + * Attributes and setter methods are provided to support optional generation of + * Weblogic5.1 jars, optional deletion of generic jar files, setting alternate + * values for baseNameTerminator, and setting the strings to append to the names + * of the generated jarfiles.

      + * + * @author Tim Fennell + */ +public class EjbJar extends MatchingTask +{ + + private Config config = new Config(); + + /** + * Instance variable that stores the suffix for the generated jarfile. + */ + private String genericJarSuffix = "-generic.jar"; + + /** + * The list of deployment tools we are going to run. + */ + private ArrayList deploymentTools = new ArrayList(); + + /** + * Stores a handle to the directory to put the Jar files in. This is only + * used by the generic deployment descriptor tool which is created if no + * other deployment descriptor tools are provided. Normally each deployment + * tool will specify the desitination dir itself. + */ + private File destDir; + + /** + * Set the base name of the EJB jar that is to be created if it is not to be + * determined from the name of the deployment descriptor files. + * + * @param inValue the basename that will be used when writing the jar file + * containing the EJB + */ + public void setBasejarname( String inValue ) + { + config.baseJarName = inValue; + if( config.namingScheme == null ) + { + config.namingScheme = new NamingScheme(); + config.namingScheme.setValue( NamingScheme.BASEJARNAME ); + } + else if( !config.namingScheme.getValue().equals( NamingScheme.BASEJARNAME ) ) + { + throw new BuildException( "The basejarname attribute is not compatible with the " + + config.namingScheme.getValue() + " naming scheme" ); + } + } + + /** + * Set the baseNameTerminator. The basename terminator is the string which + * terminates the bean name. The convention used by this task is that bean + * descriptors are named as the BeanName with some suffix. The + * baseNameTerminator string separates the bean name and the suffix and is + * used to determine the bean name. + * + * @param inValue a string which marks the end of the basename. + */ + public void setBasenameterminator( String inValue ) + { + config.baseNameTerminator = inValue; + } + + /** + * Set the classpath to use when resolving classes for inclusion in the jar. + * + * @param classpath the classpath to use. + */ + public void setClasspath( Path classpath ) + { + config.classpath = classpath; + } + + /** + * Set the descriptor directory. The descriptor directory contains the EJB + * deployment descriptors. These are XML files that declare the properties + * of a bean in a particular deployment scenario. Such properties include, + * for example, the transactional nature of the bean and the security access + * control to the bean's methods. + * + * @param inDir the directory containing the deployment descriptors. + */ + public void setDescriptordir( File inDir ) + { + config.descriptorDir = inDir; + } + + + /** + * Set the destination directory. The EJB jar files will be written into + * this directory. The jar files that exist in this directory are also used + * when determining if the contents of the jar file have changed. Note that + * this parameter is only used if no deployment tools are specified. + * Typically each deployment tool will specify its own destination + * directory. + * + * @param inDir The new Destdir value + */ + public void setDestdir( File inDir ) + { + this.destDir = inDir; + } + + /** + * Set the flat dest dir flag. This flag controls whether the destination + * jars are written out in the destination directory with the same + * hierarchal structure from which the deployment descriptors have been + * read. If this is set to true the generated EJB jars are written into the + * root of the destination directory, otherwise they are written out in the + * same relative position as the deployment descriptors in the descriptor + * directory. + * + * @param inValue the new value of the flatdestdir flag. + */ + public void setFlatdestdir( boolean inValue ) + { + config.flatDestDir = inValue; + } + + /** + * Set the suffix for the generated jar file. When generic jars are + * generated, they have a suffix which is appended to the the bean name to + * create the name of the jar file. Note that this suffix includes the + * extension fo te jar file and should therefore end with an appropriate + * extension such as .jar or .ear + * + * @param inString the string to use as the suffix. + */ + public void setGenericjarsuffix( String inString ) + { + this.genericJarSuffix = inString; + } + + + /** + * Set the Manifest file to use when jarring. As of EJB 1.1, manifest files + * are no longer used to configure the EJB. However, they still have a vital + * importance if the EJB is intended to be packaged in an EAR file. By + * adding "Class-Path" settings to a Manifest file, the EJB can look for + * classes inside the EAR file itself, allowing for easier deployment. This + * is outlined in the J2EE specification, and all J2EE components are meant + * to support it. + * + * @param manifest The new Manifest value + */ + public void setManifest( File manifest ) + { + config.manifest = manifest; + } + + /** + * Set the naming scheme used to determine the name of the generated jars + * from the deployment descriptor + * + * @param namingScheme The new Naming value + */ + public void setNaming( NamingScheme namingScheme ) + { + config.namingScheme = namingScheme; + if( !config.namingScheme.getValue().equals( NamingScheme.BASEJARNAME ) && + config.baseJarName != null ) + { + throw new BuildException( "The basejarname attribute is not compatible with the " + + config.namingScheme.getValue() + " naming scheme" ); + } + } + + /** + * Set the srcdir attribute. The source directory is the directory that + * contains the classes that will be added to the EJB jar. Typically this + * will include the home and remote interfaces and the bean class. + * + * @param inDir the source directory. + */ + public void setSrcdir( File inDir ) + { + config.srcDir = inDir; + } + + /** + * Create a Borland nested element used to configure a deployment tool for + * Borland server. + * + * @return the deployment tool instance to be configured. + */ + public BorlandDeploymentTool createBorland() + { + log( "Borland deployment tools", Project.MSG_VERBOSE ); + + BorlandDeploymentTool tool = new BorlandDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * creates a nested classpath element. This classpath is used to locate the + * super classes and interfaces of the classes that will make up the EJB + * jar. + * + * @return the path to be configured. + */ + public Path createClasspath() + { + if( config.classpath == null ) + { + config.classpath = new Path( project ); + } + return config.classpath.createPath(); + } + + /** + * Create a DTD location record. This stores the location of a DTD. The DTD + * is identified by its public Id. The location may either be a file + * location or a resource location. + * + * @return Description of the Returned Value + */ + public DTDLocation createDTD() + { + DTDLocation dtdLocation = new DTDLocation(); + config.dtdLocations.add( dtdLocation ); + + return dtdLocation; + } + + /** + * Create a nested element used to configure a deployment tool for iPlanet + * Application Server. + * + * @return the deployment tool instance to be configured. + */ + public IPlanetDeploymentTool createIplanet() + { + log( "iPlanet Application Server deployment tools", Project.MSG_VERBOSE ); + + IPlanetDeploymentTool tool = new IPlanetDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a jboss nested element used to configure a deployment tool for + * Jboss server. + * + * @return the deployment tool instance to be configured. + */ + public JbossDeploymentTool createJboss() + { + JbossDeploymentTool tool = new JbossDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a file set for support elements + * + * @return a fileset which can be populated with support files. + */ + public FileSet createSupport() + { + FileSet supportFileSet = new FileSet(); + config.supportFileSets.add( supportFileSet ); + return supportFileSet; + } + + /** + * Create a weblogic nested element used to configure a deployment tool for + * Weblogic server. + * + * @return the deployment tool instance to be configured. + */ + public WeblogicDeploymentTool createWeblogic() + { + WeblogicDeploymentTool tool = new WeblogicDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a nested element for weblogic when using the Toplink Object- + * Relational mapping. + * + * @return the deployment tool instance to be configured. + */ + public WeblogicTOPLinkDeploymentTool createWeblogictoplink() + { + WeblogicTOPLinkDeploymentTool tool = new WeblogicTOPLinkDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a websphere nested element used to configure a deployment tool for + * Websphere 4.0 server. + * + * @return the deployment tool instance to be configured. + */ + public WebsphereDeploymentTool createWebsphere() + { + WebsphereDeploymentTool tool = new WebsphereDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Invoked by Ant after the task is prepared, when it is ready to execute + * this task. This will configure all of the nested deployment tools to + * allow them to process the jar. If no deployment tools have been + * configured a generic tool is created to handle the jar. A parser is + * configured and then each descriptor found is passed to all the deployment + * tool elements for processing. + * + * @exception BuildException thrown whenever a problem is encountered that + * cannot be recovered from, to signal to ant that a major problem + * occurred within this task. + */ + public void execute() + throws BuildException + { + validateConfig(); + + if( deploymentTools.size() == 0 ) + { + GenericDeploymentTool genericTool = new GenericDeploymentTool(); + genericTool.setTask( this ); + genericTool.setDestdir( destDir ); + genericTool.setGenericJarSuffix( genericJarSuffix ); + deploymentTools.add( genericTool ); + } + + for( Iterator i = deploymentTools.iterator(); i.hasNext(); ) + { + EJBDeploymentTool tool = ( EJBDeploymentTool )i.next(); + tool.configure( config ); + tool.validateConfigured(); + } + + try + { + // Create the parser using whatever parser the system dictates + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setValidating( true ); + SAXParser saxParser = saxParserFactory.newSAXParser(); + + DirectoryScanner ds = getDirectoryScanner( config.descriptorDir ); + ds.scan(); + String[] files = ds.getIncludedFiles(); + + log( files.length + " deployment descriptors located.", + Project.MSG_VERBOSE ); + + // Loop through the files. Each file represents one deployment + // descriptor, and hence one bean in our model. + for( int index = 0; index < files.length; ++index ) + { + // process the deployment descriptor in each tool + for( Iterator i = deploymentTools.iterator(); i.hasNext(); ) + { + EJBDeploymentTool tool = ( EJBDeploymentTool )i.next(); + tool.processDescriptor( files[index], saxParser ); + } + } + } + catch( SAXException se ) + { + String msg = "SAXException while creating parser." + + " Details: " + + se.getMessage(); + throw new BuildException( msg, se ); + } + catch( ParserConfigurationException pce ) + { + String msg = "ParserConfigurationException while creating parser. " + + "Details: " + pce.getMessage(); + throw new BuildException( msg, pce ); + } + } + + private void validateConfig() + { + if( config.srcDir == null ) + { + throw new BuildException( "The srcDir attribute must be specified" ); + } + + if( config.descriptorDir == null ) + { + config.descriptorDir = config.srcDir; + } + + if( config.namingScheme == null ) + { + config.namingScheme = new NamingScheme(); + config.namingScheme.setValue( NamingScheme.DESCRIPTOR ); + } + else if( config.namingScheme.getValue().equals( NamingScheme.BASEJARNAME ) && + config.baseJarName == null ) + { + throw new BuildException( "The basejarname attribute must be specified " + + "with the basejarname naming scheme" ); + } + } + + public static class DTDLocation + { + private String publicId = null; + private String location = null; + + public void setLocation( String location ) + { + this.location = location; + } + + public void setPublicId( String publicId ) + { + this.publicId = publicId; + } + + public String getLocation() + { + return location; + } + + public String getPublicId() + { + return publicId; + } + } + + public static class NamingScheme extends EnumeratedAttribute + { + public final static String EJB_NAME = "ejb-name"; + public final static String DIRECTORY = "directory"; + public final static String DESCRIPTOR = "descriptor"; + public final static String BASEJARNAME = "basejarname"; + + public String[] getValues() + { + return new String[]{EJB_NAME, DIRECTORY, DESCRIPTOR, BASEJARNAME}; + } + } + + /** + * A class which contains the configuration state of the ejbjar task. This + * state is passed to the deployment tools for configuration + * + * @author RT + */ + static class Config + { + + /** + * Instance variable that marks the end of the 'basename' + */ + public String baseNameTerminator = "-"; + + /** + * Instance variable that determines whether to use a package structure + * of a flat directory as the destination for the jar files. + */ + public boolean flatDestDir = false; + + /** + * A Fileset of support classes + */ + public List supportFileSets = new ArrayList(); + + /** + * The list of configured DTD locations + */ + public ArrayList dtdLocations = new ArrayList(); + + /** + * Stores a handle to the destination EJB Jar file + */ + public String baseJarName; + + /** + * The classpath to use when loading classes + */ + public Path classpath; + + /** + * Stores a handle to the directory under which to search for deployment + * descriptors + */ + public File descriptorDir; + + /** + * The Manifest file + */ + public File manifest; + + /** + * The naming scheme used to determine the generated jar name from the + * descriptor information + */ + public NamingScheme namingScheme; + /** + * Stores a handle to the directory under which to search for class + * files + */ + public File srcDir; + }// end of execute() +} + + + + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java new file mode 100644 index 000000000..0041c3756 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * Build EJB support classes using Weblogic's ejbc tool from a directory + * containing a set of deployment descriptors. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class Ejbc extends MatchingTask +{ + + public boolean keepgenerated; + + /** + * The classpath to be used in the weblogic ejbc calls. It must contain the + * weblogic classes and the implementation classes of the home and + * remote interfaces. + */ + private String classpath; + /** + * The root directory of the tree containing the serialised deployment + * desciptors. The actual deployment descriptor files are selected using + * include and exclude constructs on the ejbc task provided by the + * MatchingTask superclass. + */ + private File descriptorDirectory; + + /** + * The directory where generated files are placed. + */ + private File generatedFilesDirectory; + + /** + * The name of the manifest file generated for the EJB jar. + */ + private File generatedManifestFile; + + /** + * The source directory for the home and remote interfaces. This is used to + * determine if the generated deployment classes are out of date. + */ + private File sourceDirectory; + + /** + * Set the classpath to be used for this compilation. + * + * @param s The new Classpath value + */ + public void setClasspath( String s ) + { + this.classpath = project.translatePath( s ); + } + + /** + * Set the directory from where the serialised deployment descriptors are to + * be read. + * + * @param dirName the name of the directory containing the serialised + * deployment descriptors. + */ + public void setDescriptors( String dirName ) + { + descriptorDirectory = new File( dirName ); + } + + /** + * Set the directory into which the support classes, RMI stubs, etc are to + * be written + * + * @param dirName the name of the directory into which code is generated + */ + public void setDest( String dirName ) + { + generatedFilesDirectory = new File( dirName ); + } + + public void setKeepgenerated( String newKeepgenerated ) + { + keepgenerated = Boolean.valueOf( newKeepgenerated.trim() ).booleanValue(); + + } + + /** + * Set the generated manifest file. For each EJB that is processed an entry + * is created in this file. This can then be used to create a jar file for + * dploying the beans. + * + * @param manifestFilename The new Manifest value + */ + public void setManifest( String manifestFilename ) + { + generatedManifestFile = new File( manifestFilename ); + } + + /** + * Set the directory containing the source code for the home interface, + * remote interface and public key class definitions. + * + * @param dirName the directory containg the source tree for the EJB's + * interface classes. + */ + public void setSrc( String dirName ) + { + sourceDirectory = new File( dirName ); + } + + public boolean getKeepgenerated() + { + return keepgenerated; + } + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * a helper task. This approach allows the classpath of the helper task to + * be set. Since the weblogic tools require the class files of the project's + * home and remote interfaces to be available in the classpath, this also + * avoids having to start ant with the class path of the project it is + * building. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( descriptorDirectory == null || + !descriptorDirectory.isDirectory() ) + { + throw new BuildException( "descriptors directory " + descriptorDirectory.getPath() + + " is not valid" ); + } + if( generatedFilesDirectory == null || + !generatedFilesDirectory.isDirectory() ) + { + throw new BuildException( "dest directory " + generatedFilesDirectory.getPath() + + " is not valid" ); + } + + if( sourceDirectory == null || + !sourceDirectory.isDirectory() ) + { + throw new BuildException( "src directory " + sourceDirectory.getPath() + + " is not valid" ); + } + + String systemClassPath = System.getProperty( "java.class.path" ); + String execClassPath = project.translatePath( systemClassPath + ":" + classpath + + ":" + generatedFilesDirectory ); + // get all the files in the descriptor directory + DirectoryScanner ds = super.getDirectoryScanner( descriptorDirectory ); + + String[] files = ds.getIncludedFiles(); + + Java helperTask = ( Java )project.createTask( "java" ); + helperTask.setTaskName( getTaskName() ); + helperTask.setFork( true ); + helperTask.setClassname( "org.apache.tools.ant.taskdefs.optional.ejb.EjbcHelper" ); + String args = ""; + args += " " + descriptorDirectory; + args += " " + generatedFilesDirectory; + args += " " + sourceDirectory; + args += " " + generatedManifestFile; + args += " " + keepgenerated; + + for( int i = 0; i < files.length; ++i ) + { + args += " " + files[i]; + } + + Commandline.Argument arguments = helperTask.createArg(); + arguments.setLine( args ); + helperTask.setClasspath( new Path( project, execClassPath ) ); + if( helperTask.executeJava() != 0 ) + { + throw new BuildException( "Execution of ejbc helper failed" ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java new file mode 100644 index 000000000..73b2ed414 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.PrintWriter; +import java.util.Vector; +import javax.ejb.deployment.DeploymentDescriptor; +import javax.ejb.deployment.EntityDescriptor; + + +/** + * A helper class which performs the actual work of the ejbc task. This class is + * run with a classpath which includes the weblogic tools and the home and + * remote interface class files referenced in the deployment descriptors being + * processed. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class EjbcHelper +{ + + /** + * The names of the serialised deployment descriptors + */ + String[] descriptors; + + /** + * The classpath to be used in the weblogic ejbc calls. It must contain the + * weblogic classes and the implementation classes of the home and + * remote interfaces. + */ + private String classpath; + /** + * The root directory of the tree containing the serialised deployment + * desciptors. + */ + private File descriptorDirectory; + + /** + * The directory where generated files are placed. + */ + private File generatedFilesDirectory; + + private boolean keepGenerated; + + /** + * The name of the manifest file generated for the EJB jar. + */ + private File manifestFile; + + /** + * The source directory for the home and remote interfaces. This is used to + * determine if the generated deployment classes are out of date. + */ + private File sourceDirectory; + + /** + * Initialise the EjbcHelper by reading the command arguments. + * + * @param args Description of Parameter + */ + private EjbcHelper( String[] args ) + { + int index = 0; + descriptorDirectory = new File( args[index++] ); + generatedFilesDirectory = new File( args[index++] ); + sourceDirectory = new File( args[index++] ); + manifestFile = new File( args[index++] ); + keepGenerated = Boolean.valueOf( args[index++] ).booleanValue(); + + descriptors = new String[args.length - index]; + for( int i = 0; index < args.length; ++i ) + { + descriptors[i] = args[index++]; + } + } + + /** + * Command line interface for the ejbc helper task. + * + * @param args The command line arguments + * @exception Exception Description of Exception + */ + public static void main( String[] args ) + throws Exception + { + EjbcHelper helper = new EjbcHelper( args ); + helper.process(); + } + + private String[] getCommandLine( boolean debug, File descriptorFile ) + { + Vector v = new Vector(); + if( !debug ) + { + v.addElement( "-noexit" ); + } + if( keepGenerated ) + { + v.addElement( "-keepgenerated" ); + } + v.addElement( "-d" ); + v.addElement( generatedFilesDirectory.getPath() ); + v.addElement( descriptorFile.getPath() ); + + String[] args = new String[v.size()]; + v.copyInto( args ); + return args; + } + + /** + * Determine if the weblogic EJB support classes need to be regenerated for + * a given deployment descriptor. This process attempts to determine if the + * support classes need to be rebuilt. It does this by examining only some + * of the support classes which are typically generated. If the ejbc task is + * interrupted generating the support classes for a bean, all of the support + * classes should be removed to force regeneration of the support classes. + * + * @param descriptorFile the serialised deployment descriptor + * @return true if the support classes need to be regenerated. + * @throws IOException if the descriptor file cannot be closed. + */ + private boolean isRegenRequired( File descriptorFile ) + throws IOException + { + // read in the descriptor. Under weblogic, the descriptor is a weblogic + // specific subclass which has references to the implementation classes. + // These classes must, therefore, be in the classpath when the deployment + // descriptor is loaded from the .ser file + FileInputStream fis = null; + try + { + fis = new FileInputStream( descriptorFile ); + ObjectInputStream ois = new ObjectInputStream( fis ); + DeploymentDescriptor dd = ( DeploymentDescriptor )ois.readObject(); + fis.close(); + + String homeInterfacePath = dd.getHomeInterfaceClassName().replace( '.', '/' ) + ".java"; + String remoteInterfacePath = dd.getRemoteInterfaceClassName().replace( '.', '/' ) + ".java"; + String primaryKeyClassPath = null; + if( dd instanceof EntityDescriptor ) + { + primaryKeyClassPath = ( ( EntityDescriptor )dd ).getPrimaryKeyClassName().replace( '.', '/' ) + ".java"; + ; + } + + File homeInterfaceSource = new File( sourceDirectory, homeInterfacePath ); + File remoteInterfaceSource = new File( sourceDirectory, remoteInterfacePath ); + File primaryKeyClassSource = null; + if( primaryKeyClassPath != null ) + { + primaryKeyClassSource = new File( sourceDirectory, remoteInterfacePath ); + } + + // are any of the above out of date. + // we find the implementation classes and see if they are older than any + // of the above or the .ser file itself. + String beanClassBase = dd.getEnterpriseBeanClassName().replace( '.', '/' ); + File ejbImplentationClass + = new File( generatedFilesDirectory, beanClassBase + "EOImpl.class" ); + File homeImplementationClass + = new File( generatedFilesDirectory, beanClassBase + "HomeImpl.class" ); + File beanStubClass + = new File( generatedFilesDirectory, beanClassBase + "EOImpl_WLStub.class" ); + + // if the implementation classes don;t exist regenerate + if( !ejbImplentationClass.exists() || !homeImplementationClass.exists() || + !beanStubClass.exists() ) + { + return true; + } + + // Is the ser file or any of the source files newer then the class files. + // firstly find the oldest of the two class files. + long classModificationTime = ejbImplentationClass.lastModified(); + if( homeImplementationClass.lastModified() < classModificationTime ) + { + classModificationTime = homeImplementationClass.lastModified(); + } + if( beanStubClass.lastModified() < classModificationTime ) + { + classModificationTime = beanStubClass.lastModified(); + } + + if( descriptorFile.lastModified() > classModificationTime || + homeInterfaceSource.lastModified() > classModificationTime || + remoteInterfaceSource.lastModified() > classModificationTime ) + { + return true; + } + + if( primaryKeyClassSource != null && + primaryKeyClassSource.lastModified() > classModificationTime ) + { + return true; + } + } + catch( Throwable descriptorLoadException ) + { + System.out.println( "Exception occurred reading " + descriptorFile.getName() + " - continuing" ); + // any problems - just regenerate + return true; + } + finally + { + if( fis != null ) + { + fis.close(); + } + } + + return false; + } + + /** + * Process the descriptors in turn generating support classes for each and a + * manifest file for all of the beans. + * + * @exception Exception Description of Exception + */ + private void process() + throws Exception + { + String manifest = "Manifest-Version: 1.0\n\n"; + for( int i = 0; i < descriptors.length; ++i ) + { + String descriptorName = descriptors[i]; + File descriptorFile = new File( descriptorDirectory, descriptorName ); + + if( isRegenRequired( descriptorFile ) ) + { + System.out.println( "Running ejbc for " + descriptorFile.getName() ); + regenerateSupportClasses( descriptorFile ); + } + else + { + System.out.println( descriptorFile.getName() + " is up to date" ); + } + manifest += "Name: " + descriptorName.replace( '\\', '/' ) + "\nEnterprise-Bean: True\n\n"; + } + + FileWriter fw = new FileWriter( manifestFile ); + PrintWriter pw = new PrintWriter( fw ); + pw.print( manifest ); + fw.flush(); + fw.close(); + } + + /** + * Perform the weblogic.ejbc call to regenerate the support classes. Note + * that this method relies on an undocumented -noexit option to the ejbc + * tool to stop the ejbc tool exiting the VM altogether. + * + * @param descriptorFile Description of Parameter + * @exception Exception Description of Exception + */ + private void regenerateSupportClasses( File descriptorFile ) + throws Exception + { + // create a Java task to do the rebuild + + + String[] args = getCommandLine( false, descriptorFile ); + + try + { + weblogic.ejbc.main( args ); + } + catch( Exception e ) + { + // run with no exit for better reporting + String[] newArgs = getCommandLine( true, descriptorFile ); + weblogic.ejbc.main( newArgs ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java new file mode 100644 index 000000000..29a49f9aa --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java @@ -0,0 +1,970 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import javax.xml.parsers.SAXParser; +import org.apache.bcel.*; +import org.apache.bcel.classfile.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.*; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.util.depend.Dependencies; +import org.apache.tools.ant.util.depend.Filter; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + + +/** + * A deployment tool which creates generic EJB jars. Generic jars contains only + * those classes and META-INF entries specified in the EJB 1.1 standard This + * class is also used as a framework for the creation of vendor specific + * deployment tools. A number of template methods are provided through which the + * vendor specific tool can hook into the EJB creation process. + * + * @author RT + */ +public class GenericDeploymentTool implements EJBDeploymentTool +{ + /** + * Private constants that are used when constructing the standard jarfile + */ + protected final static String META_DIR = "META-INF/"; + protected final static String EJB_DD = "ejb-jar.xml"; + + /** + * Instance variable that stores the suffix for the generated jarfile. + */ + private String genericJarSuffix = "-generic.jar"; + + /** + * The classloader generated from the given classpath to load the super + * classes and super interfaces. + */ + private ClassLoader classpathLoader = null; + + /** + * List of files have been loaded into the EJB jar + */ + private List addedfiles; + + /** + * The classpath to use with this deployment tool. This is appended to any + * paths from the ejbjar task itself. + */ + private Path classpath; + + /** + * The configuration from the containing task. This config combined with the + * settings of the individual attributes here constitues the complete config + * for this deployment tool. + */ + private EjbJar.Config config; + + /** + * Stores a handle to the directory to put the Jar files in + */ + private File destDir; + + /** + * Handler used to parse the EJB XML descriptor + */ + private DescriptorHandler handler; + + /** + * The task to which this tool belongs. This is used to access services + * provided by the ant core, such as logging. + */ + private Task task; + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + this.classpath = classpath; + } + + /** + * Setter used to store the value of destination directory prior to + * execute() being called. + * + * @param inDir the destination directory. + */ + public void setDestdir( File inDir ) + { + this.destDir = inDir; + } + + /** + * Setter used to store the suffix for the generated jar file. + * + * @param inString the string to use as the suffix. + */ + public void setGenericJarSuffix( String inString ) + { + this.genericJarSuffix = inString; + } + + + /** + * Set the task which owns this tool + * + * @param task The new Task value + */ + public void setTask( Task task ) + { + this.task = task; + } + + /** + * Get the prefix for vendor deployment descriptors. This will contain the + * path and the start of the descriptor name, depending on the naming scheme + * + * @param baseName Description of Parameter + * @param descriptorFileName Description of Parameter + * @return The VendorDDPrefix value + */ + public String getVendorDDPrefix( String baseName, String descriptorFileName ) + { + String ddPrefix = null; + + if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.DESCRIPTOR ) ) + { + ddPrefix = baseName + config.baseNameTerminator; + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.BASEJARNAME ) || + config.namingScheme.getValue().equals( EjbJar.NamingScheme.EJB_NAME ) || + config.namingScheme.getValue().equals( EjbJar.NamingScheme.DIRECTORY ) ) + { + String canonicalDescriptor = descriptorFileName.replace( '\\', '/' ); + int index = canonicalDescriptor.lastIndexOf( '/' ); + if( index == -1 ) + { + ddPrefix = ""; + } + else + { + ddPrefix = descriptorFileName.substring( 0, index + 1 ); + } + } + return ddPrefix; + } + + + /** + * Configure this tool for use in the ejbjar task. + * + * @param config Description of Parameter + */ + public void configure( EjbJar.Config config ) + { + this.config = config; + + classpathLoader = null; + } + + /** + * Add the classpath for the user classes + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( task.getProject() ); + } + return classpath.createPath(); + } + + public void processDescriptor( String descriptorFileName, SAXParser saxParser ) + { + + checkConfiguration( descriptorFileName, saxParser ); + + try + { + handler = getDescriptorHandler( config.srcDir ); + + // Retrive the files to be added to JAR from EJB descriptor + Hashtable ejbFiles = parseEjbFiles( descriptorFileName, saxParser ); + + // Add any support classes specified in the build file + addSupportClasses( ejbFiles ); + + // Determine the JAR filename (without filename extension) + String baseName = getJarBaseName( descriptorFileName ); + + String ddPrefix = getVendorDDPrefix( baseName, descriptorFileName ); + + // First the regular deployment descriptor + ejbFiles.put( META_DIR + EJB_DD, + new File( config.descriptorDir, descriptorFileName ) ); + + // now the vendor specific files, if any + addVendorFiles( ejbFiles, ddPrefix ); + + // add any dependent files + checkAndAddDependants( ejbFiles ); + + // Lastly create File object for the Jar files. If we are using + // a flat destination dir, then we need to redefine baseName! + if( config.flatDestDir && baseName.length() != 0 ) + { + int startName = baseName.lastIndexOf( File.separator ); + if( startName == -1 ) + { + startName = 0; + } + + int endName = baseName.length(); + baseName = baseName.substring( startName, endName ); + } + + File jarFile = getVendorOutputJarFile( baseName ); + + // Check to see if we need a build and start doing the work! + if( needToRebuild( ejbFiles, jarFile ) ) + { + // Log that we are going to build... + log( "building " + + jarFile.getName() + + " with " + + String.valueOf( ejbFiles.size() ) + + " files", + Project.MSG_INFO ); + + // Use helper method to write the jarfile + String publicId = getPublicId(); + writeJar( baseName, jarFile, ejbFiles, publicId ); + + } + else + { + // Log that the file is up to date... + log( jarFile.toString() + " is up to date.", + Project.MSG_VERBOSE ); + } + + } + catch( SAXException se ) + { + String msg = "SAXException while parsing '" + + descriptorFileName.toString() + + "'. This probably indicates badly-formed XML." + + " Details: " + + se.getMessage(); + throw new BuildException( msg, se ); + } + catch( IOException ioe ) + { + String msg = "IOException while parsing'" + + descriptorFileName.toString() + + "'. This probably indicates that the descriptor" + + " doesn't exist. Details: " + + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + } + + /** + * Called to validate that the tool parameters have been configured. + * + * @throws BuildException If the Deployment Tool's configuration isn't valid + */ + public void validateConfigured() + throws BuildException + { + if( ( destDir == null ) || ( !destDir.isDirectory() ) ) + { + String msg = "A valid destination directory must be specified " + + "using the \"destdir\" attribute."; + throw new BuildException( msg, getLocation() ); + } + } + + + /** + * Returns a Classloader object which parses the passed in generic EjbJar + * classpath. The loader is used to dynamically load classes from + * javax.ejb.* and the classes being added to the jar. + * + * @return The ClassLoaderForBuild value + */ + protected ClassLoader getClassLoaderForBuild() + { + if( classpathLoader != null ) + { + return classpathLoader; + } + + Path combinedClasspath = getCombinedClasspath(); + + // only generate a new ClassLoader if we have a classpath + if( combinedClasspath == null ) + { + classpathLoader = getClass().getClassLoader(); + } + else + { + classpathLoader = new AntClassLoader( getTask().getProject(), combinedClasspath ); + } + + return classpathLoader; + } + + /** + * Get the classpath by combining the one from the surrounding task, if any + * and the one from this tool. + * + * @return The CombinedClasspath value + */ + protected Path getCombinedClasspath() + { + Path combinedPath = classpath; + if( config.classpath != null ) + { + if( combinedPath == null ) + { + combinedPath = config.classpath; + } + else + { + combinedPath.append( config.classpath ); + } + } + + return combinedPath; + } + + /** + * Get the basename terminator. + * + * @return The Config value + */ + protected EjbJar.Config getConfig() + { + return config; + } + + protected DescriptorHandler getDescriptorHandler( File srcDir ) + { + DescriptorHandler handler = new DescriptorHandler( getTask(), srcDir ); + + registerKnownDTDs( handler ); + + // register any DTDs supplied by the user + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + } + return handler; + } + + /** + * Get the desitination directory. + * + * @return The DestDir value + */ + protected File getDestDir() + { + return destDir; + } + + + /** + * Using the EJB descriptor file name passed from the ejbjar + * task, this method returns the "basename" which will be used to name the + * completed JAR file. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @return The "basename" which will be used to name the completed JAR file + */ + protected String getJarBaseName( String descriptorFileName ) + { + + String baseName = ""; + + // Work out what the base name is + if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.BASEJARNAME ) ) + { + String canonicalDescriptor = descriptorFileName.replace( '\\', '/' ); + int index = canonicalDescriptor.lastIndexOf( '/' ); + if( index != -1 ) + { + baseName = descriptorFileName.substring( 0, index + 1 ); + } + baseName += config.baseJarName; + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.DESCRIPTOR ) ) + { + int lastSeparatorIndex = descriptorFileName.lastIndexOf( File.separator ); + int endBaseName = -1; + if( lastSeparatorIndex != -1 ) + { + endBaseName = descriptorFileName.indexOf( config.baseNameTerminator, + lastSeparatorIndex ); + } + else + { + endBaseName = descriptorFileName.indexOf( config.baseNameTerminator ); + } + + if( endBaseName != -1 ) + { + baseName = descriptorFileName.substring( 0, endBaseName ); + } + baseName = descriptorFileName.substring( 0, endBaseName ); + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.DIRECTORY ) ) + { + int lastSeparatorIndex = descriptorFileName.lastIndexOf( File.separator ); + String dirName = descriptorFileName.substring( 0, lastSeparatorIndex ); + int dirSeparatorIndex = dirName.lastIndexOf( File.separator ); + if( dirSeparatorIndex != -1 ) + { + dirName = dirName.substring( dirSeparatorIndex + 1 ); + } + + baseName = dirName; + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.EJB_NAME ) ) + { + baseName = handler.getEjbName(); + } + return baseName; + } + + protected Location getLocation() + { + return getTask().getLocation(); + } + + /** + * Returns the Public ID of the DTD specified in the EJB descriptor. Not + * every vendor-specific DeploymentTool will need to reference + * this value or may want to determine this value in a vendor-specific way. + * + * @return Public ID of the DTD specified in the EJB descriptor. + */ + protected String getPublicId() + { + return handler.getPublicId(); + } + + /** + * Get the task for this tool. + * + * @return The Task value + */ + protected Task getTask() + { + return task; + } + + /** + * Utility method that encapsulates the logic of adding a file entry to a + * .jar file. Used by execute() to add entries to the jar file as it is + * constructed. + * + * @param jStream A JarOutputStream into which to write the jar entry. + * @param inputFile A File from which to read the contents the file being + * added. + * @param logicalFilename A String representing the name, including all + * relevant path information, that should be stored for the entry being + * added. + * @exception BuildException Description of Exception + */ + protected void addFileToJar( JarOutputStream jStream, + File inputFile, + String logicalFilename ) + throws BuildException + { + FileInputStream iStream = null; + try + { + if( !addedfiles.contains( logicalFilename ) ) + { + iStream = new FileInputStream( inputFile ); + // Create the zip entry and add it to the jar file + ZipEntry zipEntry = new ZipEntry( logicalFilename.replace( '\\', '/' ) ); + jStream.putNextEntry( zipEntry ); + + // Create the file input stream, and buffer everything over + // to the jar output stream + byte[] byteBuffer = new byte[2 * 1024]; + int count = 0; + do + { + jStream.write( byteBuffer, 0, count ); + count = iStream.read( byteBuffer, 0, byteBuffer.length ); + }while ( count != -1 ); + + //add it to list of files in jar + addedfiles.add( logicalFilename ); + } + } + catch( IOException ioe ) + { + log( "WARNING: IOException while adding entry " + + logicalFilename + " to jarfile from " + inputFile.getPath() + " " + + ioe.getClass().getName() + "-" + ioe.getMessage(), Project.MSG_WARN ); + } + finally + { + // Close up the file input stream for the class file + if( iStream != null ) + { + try + { + iStream.close(); + } + catch( IOException closeException ) + {} + } + } + } + + /** + * Adds any classes the user specifies using support nested elements + * to the ejbFiles Hashtable. + * + * @param ejbFiles Hashtable of EJB classes (and other) files that will be + * added to the completed JAR file + */ + protected void addSupportClasses( Hashtable ejbFiles ) + { + // add in support classes if any + Project project = task.getProject(); + for( Iterator i = config.supportFileSets.iterator(); i.hasNext(); ) + { + FileSet supportFileSet = ( FileSet )i.next(); + File supportBaseDir = supportFileSet.getDir( project ); + DirectoryScanner supportScanner = supportFileSet.getDirectoryScanner( project ); + supportScanner.scan(); + String[] supportFiles = supportScanner.getIncludedFiles(); + for( int j = 0; j < supportFiles.length; ++j ) + { + ejbFiles.put( supportFiles[j], new File( supportBaseDir, supportFiles[j] ) ); + } + } + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + // nothing to add for generic tool. + }// end of writeJar + + + /** + * Add all available classes, that depend on Remote, Home, Bean, PK + * + * @param checkEntries files, that are extracted from the deployment + * descriptor + * @exception BuildException Description of Exception + */ + protected void checkAndAddDependants( Hashtable checkEntries ) + throws BuildException + { + Dependencies visitor = new Dependencies(); + Set set = new TreeSet(); + Set newSet = new HashSet(); + final String base = config.srcDir.getAbsolutePath() + File.separator; + + Iterator i = checkEntries.keySet().iterator(); + while( i.hasNext() ) + { + String entryName = ( String )i.next(); + if( entryName.endsWith( ".class" ) ) + newSet.add( entryName.substring( 0, entryName.length() - ".class".length() ).replace( File.separatorChar, '/' ) ); + } + set.addAll( newSet ); + + do + { + i = newSet.iterator(); + while( i.hasNext() ) + { + String fileName = base + ( ( String )i.next() ).replace( '/', File.separatorChar ) + ".class"; + + try + { + JavaClass javaClass = new ClassParser( fileName ).parse(); + javaClass.accept( visitor ); + } + catch( IOException e ) + { + log( "exception: " + e.getMessage(), Project.MSG_INFO ); + } + } + newSet.clear(); + newSet.addAll( visitor.getDependencies() ); + visitor.clearDependencies(); + + Dependencies.applyFilter( newSet, + new Filter() + { + public boolean accept( Object object ) + { + String fileName = base + ( ( String )object ).replace( '/', File.separatorChar ) + ".class"; + return new File( fileName ).exists(); + } + } ); + newSet.removeAll( set ); + set.addAll( newSet ); + }while ( newSet.size() > 0 ); + + i = set.iterator(); + while( i.hasNext() ) + { + String next = ( ( String )i.next() ).replace( '/', File.separatorChar ); + checkEntries.put( next + ".class", new File( base + next + ".class" ) ); + log( "dependent class: " + next + ".class" + " - " + base + next + ".class", Project.MSG_VERBOSE ); + } + } + + /** + * This method is called as the first step in the processDescriptor method + * to allow vendor-specific subclasses to validate the task configuration + * prior to processing the descriptor. If the configuration is invalid, a + * BuildException should be thrown. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @exception BuildException Description of Exception + * @thows BuildException Thrown if the configuration is invalid + */ + protected void checkConfiguration( String descriptorFileName, + SAXParser saxParser ) + throws BuildException + { + + /* + * For the GenericDeploymentTool, do nothing. Vendor specific + * subclasses should throw a BuildException if the configuration is + * invalid for their server. + */ + } + + protected void log( String message, int level ) + { + getTask().log( message, level ); + } + + /** + * This method checks the timestamp on each file listed in the + * ejbFiles and compares them to the timestamp on the jarFile + * . If the jarFile's timestamp is more recent than each + * EJB file, true is returned. Otherwise, false + * is returned. TODO: find a way to check the manifest-file, that is + * found by naming convention + * + * @param ejbFiles Hashtable of EJB classes (and other) files that will be + * added to the completed JAR file + * @param jarFile JAR file which will contain all of the EJB classes (and + * other) files + * @return boolean indicating whether or not the jarFile is up + * to date + */ + protected boolean needToRebuild( Hashtable ejbFiles, File jarFile ) + { + if( jarFile.exists() ) + { + long lastBuild = jarFile.lastModified(); + + if( config.manifest != null && config.manifest.exists() && + config.manifest.lastModified() > lastBuild ) + { + log( "Build needed because manifest " + config.manifest + " is out of date", + Project.MSG_VERBOSE ); + return true; + } + + Iterator fileIter = ejbFiles.values().iterator(); + + // Loop through the files seeing if any has been touched + // more recently than the destination jar. + while( fileIter.hasNext() ) + { + File currentFile = ( File )fileIter.next(); + if( lastBuild < currentFile.lastModified() ) + { + log( "Build needed because " + currentFile.getPath() + " is out of date", + Project.MSG_VERBOSE ); + return true; + } + } + return false; + } + + return true; + } + + /** + * This method returns a list of EJB files found when the specified EJB + * descriptor is parsed and processed. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @return Hashtable of EJB class (and other) files to be added to the + * completed JAR file + * @throws SAXException Any SAX exception, possibly wrapping another + * exception + * @throws IOException An IOException from the parser, possibly from a the + * byte stream or character stream + */ + protected Hashtable parseEjbFiles( String descriptorFileName, SAXParser saxParser ) + throws IOException, SAXException + { + FileInputStream descriptorStream = null; + Hashtable ejbFiles = null; + + try + { + + /* + * Parse the ejb deployment descriptor. While it may not + * look like much, we use a SAXParser and an inner class to + * get hold of all the classfile names for the descriptor. + */ + descriptorStream = new FileInputStream( new File( config.descriptorDir, descriptorFileName ) ); + saxParser.parse( new InputSource( descriptorStream ), handler ); + + ejbFiles = handler.getFiles(); + + } + finally + { + if( descriptorStream != null ) + { + try + { + descriptorStream.close(); + } + catch( IOException closeException ) + {} + } + } + + return ejbFiles; + } + + /** + * Register the locations of all known DTDs. vendor-specific subclasses + * should override this method to define the vendor-specific locations of + * the EJB DTDs + * + * @param handler Description of Parameter + */ + protected void registerKnownDTDs( DescriptorHandler handler ) + { + // none to register for generic + } + + /** + * Returns true, if the meta-inf dir is being explicitly set, false + * otherwise. + * + * @return Description of the Returned Value + */ + protected boolean usingBaseJarName() + { + return config.baseJarName != null; + } + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarfile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + protected void writeJar( String baseName, File jarfile, Hashtable files, + String publicId ) + throws BuildException + { + + JarOutputStream jarStream = null; + try + { + // clean the addedfiles Vector + addedfiles = new ArrayList(); + + /* + * If the jarfile already exists then whack it and recreate it. + * Should probably think of a more elegant way to handle this + * so that in case of errors we don't leave people worse off + * than when we started =) + */ + if( jarfile.exists() ) + { + jarfile.delete(); + } + jarfile.getParentFile().mkdirs(); + jarfile.createNewFile(); + + InputStream in = null; + Manifest manifest = null; + try + { + File manifestFile = new File( getConfig().descriptorDir, baseName + "-manifest.mf" ); + if( manifestFile.exists() ) + { + in = new FileInputStream( manifestFile ); + } + else if( config.manifest != null ) + { + in = new FileInputStream( config.manifest ); + if( in == null ) + { + throw new BuildException( "Could not find manifest file: " + config.manifest, + getLocation() ); + } + } + else + { + String defaultManifest = "/org/apache/tools/ant/defaultManifest.mf"; + in = this.getClass().getResourceAsStream( defaultManifest ); + if( in == null ) + { + throw new BuildException( "Could not find default manifest: " + defaultManifest, + getLocation() ); + } + } + + manifest = new Manifest( in ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read manifest", e, getLocation() ); + } + finally + { + if( in != null ) + { + in.close(); + } + } + + // Create the streams necessary to write the jarfile + + jarStream = new JarOutputStream( new FileOutputStream( jarfile ), manifest ); + jarStream.setMethod( JarOutputStream.DEFLATED ); + + // Loop through all the class files found and add them to the jar + for( Iterator entryIterator = files.keySet().iterator(); entryIterator.hasNext(); ) + { + String entryName = ( String )entryIterator.next(); + File entryFile = ( File )files.get( entryName ); + + log( "adding file '" + entryName + "'", + Project.MSG_VERBOSE ); + + addFileToJar( jarStream, entryFile, entryName ); + + // See if there are any inner classes for this class and add them in if there are + InnerClassFilenameFilter flt = new InnerClassFilenameFilter( entryFile.getName() ); + File entryDir = entryFile.getParentFile(); + String[] innerfiles = entryDir.list( flt ); + for( int i = 0, n = innerfiles.length; i < n; i++ ) + { + + //get and clean up innerclass name + int entryIndex = entryName.lastIndexOf( entryFile.getName() ) - 1; + if( entryIndex < 0 ) + { + entryName = innerfiles[i]; + } + else + { + entryName = entryName.substring( 0, entryIndex ) + File.separatorChar + innerfiles[i]; + } + // link the file + entryFile = new File( config.srcDir, entryName ); + + log( "adding innerclass file '" + entryName + "'", + Project.MSG_VERBOSE ); + + addFileToJar( jarStream, entryFile, entryName ); + + } + } + } + catch( IOException ioe ) + { + String msg = "IOException while processing ejb-jar file '" + + jarfile.toString() + + "'. Details: " + + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + finally + { + if( jarStream != null ) + { + try + { + jarStream.close(); + } + catch( IOException closeException ) + {} + } + } + } + + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( destDir, baseName + genericJarSuffix ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java new file mode 100644 index 000000000..0dfdd0041 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.IOException; +import java.util.Hashtable; +import javax.xml.parsers.SAXParser; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.xml.sax.SAXException; + +/** + * This class is used to generate iPlanet Application Server (iAS) 6.0 stubs and + * skeletons and build an EJB Jar file. It is designed to be used with the Ant + * ejbjar task. If only stubs and skeletons need to be generated + * (in other words, if no JAR file needs to be created), refer to the iplanet-ejbc + * task and the IPlanetEjbcTask class.

      + * + * The following attributes may be specified by the user: + *

        + *
      • destdir -- The base directory into which the generated JAR + * files will be written. Each JAR file is written in directories which + * correspond to their location within the "descriptordir" namespace. This is + * a required attribute. + *
      • classpath -- The classpath used when generating EJB stubs and + * skeletons. This is an optional attribute (if omitted, the classpath + * specified in the "ejbjar" parent task will be used). If specified, the + * classpath elements will be prepended to the classpath specified in the + * parent "ejbjar" task. Note that nested "classpath" elements may also be + * used. + *
      • keepgenerated -- Indicates whether or not the Java source files + * which are generated by ejbc will be saved or automatically deleted. If + * "yes", the source files will be retained. This is an optional attribute (if + * omitted, it defaults to "no"). + *
      • debug -- Indicates whether or not the ejbc utility should log + * additional debugging statements to the standard output. If "yes", the + * additional debugging statements will be generated (if omitted, it defaults + * to "no"). + *
      • iashome -- May be used to specify the "home" directory for this + * iPlanet Application server installation. This is used to find the ejbc + * utility if it isn't included in the user's system path. This is an optional + * attribute (if specified, it should refer to the [install-location]/iplanet/ias6/ias + * directory). If omitted, the ejbc utility + * must be on the user's system path. + *
      • suffix -- String value appended to the JAR filename when + * creating each JAR. This attribute is not required (if omitted, it defaults + * to ".jar"). + *
      + *

      + * + * For each EJB descriptor found in the "ejbjar" parent task, this deployment + * tool will locate the three classes that comprise the EJB. If these class + * files cannot be located in the specified srcdir directory, the + * task will fail. The task will also attempt to locate the EJB stubs and + * skeletons in this directory. If found, the timestamps on the stubs and + * skeletons will be checked to ensure they are up to date. Only if these files + * cannot be found or if they are out of date will ejbc be called. + * + * @author Greg Nelson greg@netscape.com + * @see IPlanetEjbc + */ +public class IPlanetDeploymentTool extends GenericDeploymentTool +{ + + /* + * Regardless of the name of the iAS-specific EJB descriptor file, it will + * written in the completed JAR file as "ias-ejb-jar.xml". This is the + * naming convention implemented by iAS. + */ + private final static String IAS_DD = "ias-ejb-jar.xml"; + private String jarSuffix = ".jar"; + private boolean keepgenerated = false; + private boolean debug = false; + + /* + * Filenames of the standard EJB descriptor (which is passed to this class + * from the parent "ejbjar" task) and the iAS-specific EJB descriptor + * (whose name is determined by this class). Both filenames are relative + * to the directory specified by the "srcdir" attribute in the ejbjar task. + */ + private String descriptorName; + + /* + * The displayName variable stores the value of the "display-name" element + * from the standard EJB descriptor. As a future enhancement to this task, + * we may determine the name of the EJB JAR file using this display-name, + * but this has not be implemented yet. + */ + private String displayName; + private String iasDescriptorName; + + /* + * Attributes set by the Ant build file + */ + private File iashome; + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debug A boolean indicating if debugging output should be generated + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Since iAS doesn't generate a "generic" JAR as part of its processing, + * this attribute is ignored and a warning message is displayed to the user. + * + * @param inString the string to use as the suffix. This parameter is + * ignored. + */ + public void setGenericJarSuffix( String inString ) + { + log( "Since a generic JAR file is not created during processing, the " + + "iPlanet Deployment Tool does not support the " + + "\"genericjarsuffix\" attribute. It will be ignored.", + Project.MSG_WARN ); + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be [install-location]/iplanet/ias6/ias + * . + * + * @param iashome The home directory for the user's iAS installation. + */ + public void setIashome( File iashome ) + { + this.iashome = iashome; + } + + /** + * Setter method used to specify whether the Java source files generated by + * the ejbc utility should be saved or automatically deleted. + * + * @param keepgenerated boolean which, if true, indicates that + * Java source files generated by ejbc for the stubs and skeletons + * should be kept. + */ + public void setKeepgenerated( boolean keepgenerated ) + { + this.keepgenerated = keepgenerated; + } + + /** + * Setter method used to specify the filename suffix (for example, ".jar") + * for the JAR files to be created. + * + * @param jarSuffix The string to use as the JAR filename suffix. + */ + public void setSuffix( String jarSuffix ) + { + this.jarSuffix = jarSuffix; + } + + public void processDescriptor( String descriptorName, SAXParser saxParser ) + { + this.descriptorName = descriptorName; + + log( "iPlanet Deployment Tool processing: " + descriptorName + " (and " + + getIasDescriptorName() + ")", Project.MSG_VERBOSE ); + + super.processDescriptor( descriptorName, saxParser ); + } + + /** + * The iAS ejbc utility doesn't require the Public ID of the descriptor's + * DTD for it to process correctly--this method always returns null + * . + * + * @return null. + */ + protected String getPublicId() + { + return null; + } + + /** + * Add the iAS-specific EJB descriptor to the list of files which will be + * written to the JAR file. + * + * @param ejbFiles Hashtable of EJB class (and other) files to be added to + * the completed JAR file. + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + ejbFiles.put( META_DIR + IAS_DD, new File( getConfig().descriptorDir, + getIasDescriptorName() ) ); + } + + /** + * Verifies that the user selections are valid. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @throws BuildException If the user selections are invalid. + */ + protected void checkConfiguration( String descriptorFileName, + SAXParser saxParser ) + throws BuildException + { + + int startOfName = descriptorFileName.lastIndexOf( File.separatorChar ) + 1; + String stdXml = descriptorFileName.substring( startOfName ); + if( stdXml.equals( EJB_DD ) && ( getConfig().baseJarName == null ) ) + { + String msg = "No name specified for the completed JAR file. The EJB" + + " descriptor should be prepended with the JAR " + + "name or it should be specified using the " + + "attribute \"basejarname\" in the \"ejbjar\" task."; + throw new BuildException( msg, getLocation() ); + } + + File iasDescriptor = new File( getConfig().descriptorDir, + getIasDescriptorName() ); + if( ( !iasDescriptor.exists() ) || ( !iasDescriptor.isFile() ) ) + { + String msg = "The iAS-specific EJB descriptor (" + + iasDescriptor + ") was not found."; + throw new BuildException( msg, getLocation() ); + } + + if( ( iashome != null ) && ( !iashome.isDirectory() ) ) + { + String msg = "If \"iashome\" is specified, it must be a valid " + + "directory (it was set to " + iashome + ")."; + throw new BuildException( msg, getLocation() ); + } + } + + /** + * This method returns a list of EJB files found when the specified EJB + * descriptor is parsed and processed. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @return Hashtable of EJB class (and other) files to be added to the + * completed JAR file + * @throws IOException An IOException from the parser, possibly from the + * byte stream or character stream + * @throws SAXException Any SAX exception, possibly wrapping another + * exception + */ + protected Hashtable parseEjbFiles( String descriptorFileName, + SAXParser saxParser ) + throws IOException, SAXException + { + + Hashtable files; + + /* + * Build and populate an instance of the ejbc utility + */ + IPlanetEjbc ejbc = new IPlanetEjbc( + new File( getConfig().descriptorDir, + descriptorFileName ), + new File( getConfig().descriptorDir, + getIasDescriptorName() ), + getConfig().srcDir, + getCombinedClasspath().toString(), + saxParser ); + ejbc.setRetainSource( keepgenerated ); + ejbc.setDebugOutput( debug ); + if( iashome != null ) + { + ejbc.setIasHomeDir( iashome ); + } + + /* + * Execute the ejbc utility -- stubs/skeletons are rebuilt, if needed + */ + try + { + ejbc.execute(); + } + catch( IPlanetEjbc.EjbcException e ) + { + throw new BuildException( "An error has occurred while trying to " + + "execute the iAS ejbc utility", e, getLocation() ); + } + + displayName = ejbc.getDisplayName(); + files = ejbc.getEjbFiles(); + + /* + * Add CMP descriptors to the list of EJB files + */ + String[] cmpDescriptors = ejbc.getCmpDescriptors(); + if( cmpDescriptors.length > 0 ) + { + File baseDir = getConfig().descriptorDir; + + int endOfPath = descriptorFileName.lastIndexOf( File.separator ); + String relativePath = descriptorFileName.substring( 0, endOfPath + 1 ); + + for( int i = 0; i < cmpDescriptors.length; i++ ) + { + int endOfCmp = cmpDescriptors[i].lastIndexOf( '/' ); + String cmpDescriptor = cmpDescriptors[i].substring( endOfCmp + 1 ); + + File cmpFile = new File( baseDir, relativePath + cmpDescriptor ); + if( !cmpFile.exists() ) + { + throw new BuildException( "The CMP descriptor file (" + + cmpFile + ") could not be found.", getLocation() ); + } + files.put( cmpDescriptors[i], cmpFile ); + } + } + + return files; + } + + /** + * Get the name of the Jar that will be written. The modification date of + * this jar will be checked against the dependent bean classes. + * + * @param baseName String name of the EJB JAR file to be written (without a + * filename extension). + * @return File representing the JAR file which will be written. + */ + File getVendorOutputJarFile( String baseName ) + { + File jarFile = new File( getDestDir(), baseName + jarSuffix ); + log( "JAR file name: " + jarFile.toString(), Project.MSG_VERBOSE ); + return jarFile; + } + + /** + * Determines the name of the iAS-specific EJB descriptor using the + * specified standard EJB descriptor name. In general, the standard + * descriptor will be named "[basename]-ejb-jar.xml", and this method will + * return "[basename]-ias-ejb-jar.xml". + * + * @return The name of the iAS-specific EJB descriptor file. + */ + private String getIasDescriptorName() + { + + /* + * Only calculate the descriptor name once + */ + if( iasDescriptorName != null ) + { + return iasDescriptorName; + } + + String path = "";// Directory path of the EJB descriptor + String basename;// Filename appearing before name terminator + String remainder;// Filename appearing after the name terminator + + /* + * Find the end of the standard descriptor's relative path + */ + int startOfFileName = descriptorName.lastIndexOf( File.separatorChar ); + if( startOfFileName != -1 ) + { + path = descriptorName.substring( 0, startOfFileName + 1 ); + } + + /* + * Check to see if the standard name is used (there's no basename) + */ + if( descriptorName.substring( startOfFileName + 1 ).equals( EJB_DD ) ) + { + basename = ""; + remainder = EJB_DD; + + } + else + { + int endOfBaseName = descriptorName.indexOf( + getConfig().baseNameTerminator, + startOfFileName ); + /* + * Check for the odd case where the terminator and/or filename + * extension aren't found. These will ensure "ias-" appears at the + * end of the name and before the '.' (if present). + */ + if( endOfBaseName < 0 ) + { + endOfBaseName = descriptorName.lastIndexOf( '.' ) - 1; + if( endOfBaseName < 0 ) + { + endOfBaseName = descriptorName.length() - 1; + } + } + + basename = descriptorName.substring( startOfFileName + 1, + endOfBaseName + 1 ); + remainder = descriptorName.substring( endOfBaseName + 1 ); + } + + iasDescriptorName = path + basename + "ias-" + remainder; + return iasDescriptorName; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java new file mode 100644 index 000000000..8c3b18d60 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java @@ -0,0 +1,1690 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.AttributeList; +import org.xml.sax.HandlerBase; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Utility class to compile EJB stubs and skeletons for the iPlanet Application + * Server (iAS). The class will read a standard EJB descriptor (as well as an + * EJB descriptor specific to iPlanet Application Server) to identify one or + * more EJBs to process. It will search for EJB "source" classes (the remote + * interface, home interface, and EJB implementation class) and the EJB stubs + * and skeletons in the specified destination directory. Only if the stubs and + * skeletons cannot be found or if they're out of date will the iPlanet + * Application Server ejbc utility be run.

      + * + * Because this class (and it's assorted inner classes) may be bundled into the + * iPlanet Application Server distribution at some point (and removed from the + * Ant distribution), the class has been written to be independent of all + * Ant-specific classes. It is also for this reason (and to avoid cluttering the + * Apache Ant source files) that this utility has been packaged into a single + * source file.

      + * + * For more information on Ant Tasks for iPlanet Application Server, see the + * IPlanetDeploymentTool and IPlanetEjbcTask classes. + * + * @author Greg Nelson greg@netscape.com + * @see IPlanetDeploymentTool + * @see IPlanetEjbcTask + */ +public class IPlanetEjbc +{ + + /* + * Constants used for the "beantype" attribute + */ + private final static String ENTITY_BEAN = "entity"; + private final static String STATELESS_SESSION = "stateless"; + private final static String STATEFUL_SESSION = "stateful"; + + /* + * Options passed to the iAS ejbc + */ + private boolean retainSource = false; + private boolean debugOutput = false; + private EjbcHandler handler = new EjbcHandler(); + + /* + * This Hashtable maintains a list of EJB class files processed by the ejbc + * utility (both "source" class files as well as stubs and skeletons). The + * key for the Hashtable is a String representing the path to the class file + * (relative to the destination directory). The value for the Hashtable is + * a File object which reference the actual class file. + */ + private Hashtable ejbFiles = new Hashtable(); + + /* + * Classpath used when the iAS ejbc is called + */ + private String classpath; + private String[] classpathElements; + + /* + * Directory where "source" EJB files are stored and where stubs and + * skeletons will also be written. + */ + private File destDirectory; + + /* + * Value of the display-name element read from the standard EJB descriptor + */ + private String displayName; + private File iasDescriptor; + + /* + * iAS installation directory (used if ejbc isn't on user's PATH) + */ + private File iasHomeDir; + + /* + * Parser and handler used to process both EJB descriptor files + */ + private SAXParser parser; + + /* + * Filenames of the standard EJB descriptor and the iAS-specific descriptor + */ + private File stdDescriptor; + + /** + * Constructs an instance which may be used to process EJB descriptors and + * generate EJB stubs and skeletons, if needed. + * + * @param stdDescriptor File referencing a standard EJB descriptor. + * @param iasDescriptor File referencing an iAS-specific EJB descriptor. + * @param destDirectory File referencing the base directory where both EJB + * "source" files are found and where stubs and skeletons will be + * written. + * @param classpath String representation of the classpath to be used by the + * iAS ejbc utility. + * @param parser SAXParser to be used to process both of the EJB + * descriptors. + */ + public IPlanetEjbc( File stdDescriptor, + File iasDescriptor, + File destDirectory, + String classpath, + SAXParser parser ) + { + this.stdDescriptor = stdDescriptor; + this.iasDescriptor = iasDescriptor; + this.destDirectory = destDirectory; + this.classpath = classpath; + this.parser = parser; + + /* + * Parse the classpath into it's individual elements and store the + * results in the "classpathElements" instance variable. + */ + List elements = new ArrayList(); + if( classpath != null ) + { + StringTokenizer st = new StringTokenizer( classpath, + File.pathSeparator ); + while( st.hasMoreTokens() ) + { + elements.add( st.nextToken() ); + } + classpathElements + = ( String[] )elements.toArray( new String[elements.size()] ); + } + } + + /** + * Main application method for the iPlanet Application Server ejbc utility. + * If the application is run with no commandline arguments, a usage + * statement is printed for the user. + * + * @param args The commandline arguments passed to the application. + */ + public static void main( String[] args ) + { + File stdDescriptor; + File iasDescriptor; + File destDirectory = null; + String classpath = null; + SAXParser parser = null; + boolean debug = false; + boolean retainSource = false; + IPlanetEjbc ejbc; + + if( ( args.length < 2 ) || ( args.length > 8 ) ) + { + usage(); + return; + } + + stdDescriptor = new File( args[args.length - 2] ); + iasDescriptor = new File( args[args.length - 1] ); + + for( int i = 0; i < args.length - 2; i++ ) + { + if( args[i].equals( "-classpath" ) ) + { + classpath = args[++i]; + } + else if( args[i].equals( "-d" ) ) + { + destDirectory = new File( args[++i] ); + } + else if( args[i].equals( "-debug" ) ) + { + debug = true; + } + else if( args[i].equals( "-keepsource" ) ) + { + retainSource = true; + } + else + { + usage(); + return; + } + } + + /* + * If the -classpath flag isn't specified, use the system classpath + */ + if( classpath == null ) + { + Properties props = System.getProperties(); + classpath = props.getProperty( "java.class.path" ); + } + + /* + * If the -d flag isn't specified, use the working directory as the + * destination directory + */ + if( destDirectory == null ) + { + Properties props = System.getProperties(); + destDirectory = new File( props.getProperty( "user.dir" ) ); + } + + /* + * Construct a SAXParser used to process the descriptors + */ + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + parserFactory.setValidating( true ); + try + { + parser = parserFactory.newSAXParser(); + } + catch( Exception e ) + { + // SAXException or ParserConfigurationException may be thrown + System.out.println( "An exception was generated while trying to " ); + System.out.println( "create a new SAXParser." ); + e.printStackTrace(); + return; + } + + /* + * Build and populate an instance of the ejbc utility + */ + ejbc = new IPlanetEjbc( stdDescriptor, iasDescriptor, destDirectory, + classpath, parser ); + ejbc.setDebugOutput( debug ); + ejbc.setRetainSource( retainSource ); + + /* + * Execute the ejbc utility -- stubs/skeletons are rebuilt, if needed + */ + try + { + ejbc.execute(); + } + catch( IOException e ) + { + System.out.println( "An IOException has occurred while reading the " + + "XML descriptors (" + e.getMessage() + ")." ); + return; + } + catch( SAXException e ) + { + System.out.println( "A SAXException has occurred while reading the " + + "XML descriptors (" + e.getMessage() + ")." ); + return; + } + catch( IPlanetEjbc.EjbcException e ) + { + System.out.println( "An error has occurred while executing the ejbc " + + "utility (" + e.getMessage() + ")." ); + return; + } + } + + /** + * Print a usage statement. + */ + private static void usage() + { + System.out.println( "java org.apache.tools.ant.taskdefs.optional.ejb.IPlanetEjbc \\" ); + System.out.println( " [OPTIONS] [EJB 1.1 descriptor] [iAS EJB descriptor]" ); + System.out.println( "" ); + System.out.println( "Where OPTIONS are:" ); + System.out.println( " -debug -- for additional debugging output" ); + System.out.println( " -keepsource -- to retain Java source files generated" ); + System.out.println( " -classpath [classpath] -- classpath used for compilation" ); + System.out.println( " -d [destination directory] -- directory for compiled classes" ); + System.out.println( "" ); + System.out.println( "If a classpath is not specified, the system classpath" ); + System.out.println( "will be used. If a destination directory is not specified," ); + System.out.println( "the current working directory will be used (classes will" ); + System.out.println( "still be placed in subfolders which correspond to their" ); + System.out.println( "package name)." ); + System.out.println( "" ); + System.out.println( "The EJB home interface, remote interface, and implementation" ); + System.out.println( "class must be found in the destination directory. In" ); + System.out.println( "addition, the destination will look for the stubs and skeletons" ); + System.out.println( "in the destination directory to ensure they are up to date." ); + } + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debugOutput A boolean indicating if debugging output should be + * generated + */ + public void setDebugOutput( boolean debugOutput ) + { + this.debugOutput = debugOutput; + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be [install-location]/iplanet/ias6/ias + * . + * + * @param iasHomeDir The new IasHomeDir value + */ + public void setIasHomeDir( File iasHomeDir ) + { + this.iasHomeDir = iasHomeDir; + } + + /** + * Sets whether or not the Java source files which are generated by the ejbc + * process should be retained or automatically deleted. + * + * @param retainSource The new RetainSource value + */ + public void setRetainSource( boolean retainSource ) + { + this.retainSource = retainSource; + } + + /** + * Returns the list of CMP descriptors referenced in the EJB descriptors. + * + * @return An array of CMP descriptors. + */ + public String[] getCmpDescriptors() + { + List returnList = new ArrayList(); + + EjbInfo[] ejbs = handler.getEjbs(); + + for( int i = 0; i < ejbs.length; i++ ) + { + List descriptors = ( List )ejbs[i].getCmpDescriptors(); + returnList.addAll( descriptors ); + } + + return ( String[] )returnList.toArray( new String[returnList.size()] ); + } + + /** + * Returns the display-name element read from the standard EJB descriptor. + * + * @return The EJB-JAR display name. + */ + public String getDisplayName() + { + return displayName; + } + + /** + * Returns a Hashtable which contains a list of EJB class files processed by + * the ejbc utility (both "source" class files as well as stubs and + * skeletons). The key for the Hashtable is a String representing the path + * to the class file (relative to the destination directory). The value for + * the Hashtable is a File object which reference the actual class file. + * + * @return The list of EJB files processed by the ejbc utility. + */ + public Hashtable getEjbFiles() + { + return ejbFiles; + } + + /** + * Compiles the stub and skeletons for the specified EJBs, if they need to + * be updated. + * + * @throws EjbcException If the ejbc utility cannot be correctly configured + * or if one or more of the EJB "source" classes cannot be found in the + * destination directory + * @throws IOException If the parser encounters a problem reading the XML + * file + * @throws SAXException If the parser encounters a problem processing the + * XML descriptor (it may wrap another exception) + */ + public void execute() + throws EjbcException, IOException, SAXException + { + + checkConfiguration();// Throws EjbcException if unsuccessful + + EjbInfo[] ejbs = getEjbs();// Returns list of EJBs for processing + + for( int i = 0; i < ejbs.length; i++ ) + { + log( "EJBInfo..." ); + log( ejbs[i].toString() ); + } + + for( int i = 0; i < ejbs.length; i++ ) + { + EjbInfo ejb = ejbs[i]; + + ejb.checkConfiguration( destDirectory );// Throws EjbcException + + if( ejb.mustBeRecompiled( destDirectory ) ) + { + log( ejb.getName() + " must be recompiled using ejbc." ); + + String[] arguments = buildArgumentList( ejb ); + callEjbc( arguments ); + + } + else + { + log( ejb.getName() + " is up to date." ); + } + } + } + + /** + * Registers the location of a local DTD file or resource. By registering a + * local DTD, EJB descriptors can be parsed even when the remote servers + * which contain the "public" DTDs cannot be accessed. + * + * @param publicID The public DTD identifier found in an XML document. + * @param location The file or resource name for the appropriate DTD stored + * on the local machine. + */ + public void registerDTD( String publicID, String location ) + { + handler.registerDTD( publicID, location ); + } + + /** + * Verifies that the user selections are valid. + * + * @throws EjbcException If the user selections are invalid. + */ + protected void checkConfiguration() + throws EjbcException + { + + String msg = ""; + + if( stdDescriptor == null ) + { + msg += "A standard XML descriptor file must be specified. "; + } + if( iasDescriptor == null ) + { + msg += "An iAS-specific XML descriptor file must be specified. "; + } + if( classpath == null ) + { + msg += "A classpath must be specified. "; + } + if( parser == null ) + { + msg += "An XML parser must be specified. "; + } + + if( destDirectory == null ) + { + msg += "A destination directory must be specified. "; + } + else if( !destDirectory.exists() ) + { + msg += "The destination directory specified does not exist. "; + } + else if( !destDirectory.isDirectory() ) + { + msg += "The destination specified is not a directory. "; + } + + if( msg.length() > 0 ) + { + throw new EjbcException( msg ); + } + } + + /** + * Parses the EJB descriptors and returns a list of EJBs which may need to + * be compiled. + * + * @return An array of objects which describe the EJBs to be processed. + * @throws IOException If the parser encounters a problem reading the XML + * files + * @throws SAXException If the parser encounters a problem processing the + * XML descriptor (it may wrap another exception) + */ + private EjbInfo[] getEjbs() + throws IOException, SAXException + { + EjbInfo[] ejbs = null; + + /* + * The EJB information is gathered from the standard XML EJB descriptor + * and the iAS-specific XML EJB descriptor using a SAX parser. + */ + parser.parse( stdDescriptor, handler ); + parser.parse( iasDescriptor, handler ); + ejbs = handler.getEjbs(); + + return ejbs; + } + + /** + * Based on this object's instance variables as well as the EJB to be + * processed, the correct flags and parameters are set for the ejbc + * command-line utility. + * + * @param ejb The EJB for which stubs and skeletons will be compiled. + * @return An array of Strings which are the command-line parameters for for + * the ejbc utility. + */ + private String[] buildArgumentList( EjbInfo ejb ) + { + + List arguments = new ArrayList(); + + /* + * OPTIONAL COMMAND LINE PARAMETERS + */ + if( debugOutput ) + { + arguments.add( "-debug" ); + } + + /* + * No beantype flag is needed for an entity bean + */ + if( ejb.getBeantype().equals( STATELESS_SESSION ) ) + { + arguments.add( "-sl" ); + } + else if( ejb.getBeantype().equals( STATEFUL_SESSION ) ) + { + arguments.add( "-sf" ); + } + + if( ejb.getIiop() ) + { + arguments.add( "-iiop" ); + } + + if( ejb.getCmp() ) + { + arguments.add( "-cmp" ); + } + + if( retainSource ) + { + arguments.add( "-gs" ); + } + + if( ejb.getHasession() ) + { + arguments.add( "-fo" ); + } + + /* + * REQUIRED COMMAND LINE PARAMETERS + */ + arguments.add( "-classpath" ); + arguments.add( classpath ); + + arguments.add( "-d" ); + arguments.add( destDirectory.toString() ); + + arguments.add( ejb.getHome().getQualifiedClassName() ); + arguments.add( ejb.getRemote().getQualifiedClassName() ); + arguments.add( ejb.getImplementation().getQualifiedClassName() ); + + /* + * Convert the List into an Array and return it + */ + return ( String[] )arguments.toArray( new String[arguments.size()] ); + } + + /** + * Executes the iPlanet Application Server ejbc command-line utility. + * + * @param arguments Command line arguments to be passed to the ejbc utility. + */ + private void callEjbc( String[] arguments ) + { + + /* + * Concatenate all of the command line arguments into a single String + */ + StringBuffer args = new StringBuffer(); + for( int i = 0; i < arguments.length; i++ ) + { + args.append( arguments[i] ).append( " " ); + } + + /* + * If an iAS home directory is specified, prepend it to the commmand + */ + String command; + if( iasHomeDir == null ) + { + command = ""; + } + else + { + command = iasHomeDir.toString() + File.separator + "bin" + + File.separator; + } + command += "ejbc "; + + log( command + args ); + + /* + * Use the Runtime object to execute an external command. Use the + * RedirectOutput inner class to direct the standard and error output + * from the command to the JRE's standard output + */ + try + { + Process p = Runtime.getRuntime().exec( command + args ); + RedirectOutput output = new RedirectOutput( p.getInputStream() ); + RedirectOutput error = new RedirectOutput( p.getErrorStream() ); + output.start(); + error.start(); + p.waitFor(); + p.destroy(); + } + catch( IOException e ) + { + log( "An IOException has occurred while trying to execute ejbc." ); + e.printStackTrace(); + } + catch( InterruptedException e ) + { + // Do nothing + } + } + + /** + * Convenience method used to print messages to the user if debugging + * messages are enabled. + * + * @param msg The String to print to standard output. + */ + private void log( String msg ) + { + if( debugOutput ) + { + System.out.println( msg ); + } + } + + + /* + * Inner classes follow + */ + + /** + * This inner class is used to signal any problems during the execution of + * the ejbc compiler. + * + * @author Greg Nelson greg@netscape.com + * + */ + public class EjbcException extends Exception + { + + /** + * Constructs an exception with the given descriptive message. + * + * @param msg Description of the exception which has occurred. + */ + public EjbcException( String msg ) + { + super( msg ); + } + }// End of EjbInfo inner class + + /** + * Convenience class used to represent the fully qualified name of a Java + * class. It provides an easy way to retrieve components of the class name + * in a format that is convenient for building iAS stubs and skeletons. + * + * @author Greg Nelson greg@netscape.com + * + */ + private class Classname + {// Name of the package for this class + private String className;// Fully qualified name of the Java class + private String packageName; + private String qualifiedName;// Name of the class without the package + + /** + * This constructor builds an object which represents the name of a Java + * class. + * + * @param qualifiedName String representing the fully qualified class + * name of the Java class. + */ + public Classname( String qualifiedName ) + { + if( qualifiedName == null ) + { + return; + } + + this.qualifiedName = qualifiedName; + + int index = qualifiedName.lastIndexOf( '.' ); + if( index == -1 ) + { + className = qualifiedName; + packageName = ""; + } + else + { + packageName = qualifiedName.substring( 0, index ); + className = qualifiedName.substring( index + 1 ); + } + } + + /** + * Returns a File which references the class relative to the specified + * directory. Note that the class file may or may not exist. + * + * @param directory A File referencing the base directory containing + * class files. + * @return File referencing this class. + */ + public File getClassFile( File directory ) + { + String pathToFile = qualifiedName.replace( '.', File.separatorChar ) + + ".class"; + return new File( directory, pathToFile ); + } + + /** + * Gets the Java class name without the package structure. + * + * @return String representing the name for the class. + */ + public String getClassName() + { + return className; + } + + /** + * Gets the package name for the Java class. + * + * @return String representing the package name for the class. + */ + public String getPackageName() + { + return packageName; + } + + /** + * Gets the fully qualified name of the Java class. + * + * @return String representing the fully qualified class name. + */ + public String getQualifiedClassName() + { + return qualifiedName; + } + + /** + * Gets the fully qualified name of the Java class with underscores + * separating the components of the class name rather than periods. This + * format is used in naming some of the stub and skeleton classes for + * the iPlanet Application Server. + * + * @return String representing the fully qualified class name using + * underscores instead of periods. + */ + public String getQualifiedWithUnderscores() + { + return qualifiedName.replace( '.', '_' ); + } + + /** + * String representation of this class name. It returns the fully + * qualified class name. + * + * @return String representing the fully qualified class name. + */ + public String toString() + { + return getQualifiedClassName(); + } + }// End of EjbcHandler inner class + + + /** + * This inner class represents an EJB that will be compiled using ejbc. + * + * @author Greg Nelson greg@netscape.com + * + */ + private class EjbInfo + {// EJB's implementation class + private String beantype = "entity";// or "stateful" or "stateless" + private boolean cmp = false;// Does this EJB support CMP? + private boolean iiop = false;// Does this EJB support IIOP? + private boolean hasession = false;// Does this EJB require failover? + private List cmpDescriptors = new ArrayList();// EJB's display name + private Classname home;// EJB's remote interface name + private Classname implementation; + private String name;// EJB's home interface name + private Classname remote;// CMP descriptor list + + /** + * Construct a new EJBInfo object with the given name. + * + * @param name The display name for the EJB. + */ + public EjbInfo( String name ) + { + this.name = name; + } + + public void setBeantype( String beantype ) + { + this.beantype = beantype.toLowerCase(); + } + + public void setCmp( boolean cmp ) + { + this.cmp = cmp; + } + + public void setCmp( String cmp ) + { + setCmp( cmp.equals( "Container" ) ); + } + + public void setHasession( boolean hasession ) + { + this.hasession = hasession; + } + + public void setHasession( String hasession ) + { + setHasession( hasession.equals( "true" ) ); + } + + /* + * Below are getter's and setter's for each of the instance variables. + * Note that (in addition to supporting setters with the same type as + * the instance variable) a setter is provided with takes a String + * argument -- this are provided so the XML document handler can set + * the EJB values using the Strings it parses. + */ + public void setHome( String home ) + { + setHome( new Classname( home ) ); + } + + public void setHome( Classname home ) + { + this.home = home; + } + + public void setIiop( boolean iiop ) + { + this.iiop = iiop; + } + + public void setIiop( String iiop ) + { + setIiop( iiop.equals( "true" ) ); + } + + public void setImplementation( String implementation ) + { + setImplementation( new Classname( implementation ) ); + } + + public void setImplementation( Classname implementation ) + { + this.implementation = implementation; + } + + public void setRemote( String remote ) + { + setRemote( new Classname( remote ) ); + } + + public void setRemote( Classname remote ) + { + this.remote = remote; + } + + public String getBeantype() + { + return beantype; + } + + public boolean getCmp() + { + return cmp; + } + + public List getCmpDescriptors() + { + return cmpDescriptors; + } + + public boolean getHasession() + { + return hasession; + } + + public Classname getHome() + { + return home; + } + + public boolean getIiop() + { + return iiop; + } + + public Classname getImplementation() + { + return implementation; + } + + /** + * Returns the display name of the EJB. If a display name has not been + * set, it returns the EJB implementation classname (if the + * implementation class is not set, it returns "[unnamed]"). + * + * @return The display name for the EJB. + */ + public String getName() + { + if( name == null ) + { + if( implementation == null ) + { + return "[unnamed]"; + } + else + { + return implementation.getClassName(); + } + } + return name; + } + + public Classname getRemote() + { + return remote; + } + + public void addCmpDescriptor( String descriptor ) + { + cmpDescriptors.add( descriptor ); + } + + /** + * Determines if the ejbc utility needs to be run or not. If the stubs + * and skeletons can all be found in the destination directory AND all + * of their timestamps are more recent than the EJB source classes + * (home, remote, and implementation classes), the method returns false + * . Otherwise, the method returns true. + * + * @param destDir The directory where the EJB source classes, stubs and + * skeletons are located. + * @return A boolean indicating whether or not the ejbc utility needs to + * be run to bring the stubs and skeletons up to date. + */ + public boolean mustBeRecompiled( File destDir ) + { + + long sourceModified = sourceClassesModified( destDir ); + + long destModified = destClassesModified( destDir ); + + return ( destModified < sourceModified ); + } + + /** + * Convenience method which creates a String representation of all the + * instance variables of an EjbInfo object. + * + * @return A String representing the EjbInfo instance. + */ + public String toString() + { + String s = "EJB name: " + name + + "\n\r home: " + home + + "\n\r remote: " + remote + + "\n\r impl: " + implementation + + "\n\r beantype: " + beantype + + "\n\r cmp: " + cmp + + "\n\r iiop: " + iiop + + "\n\r hasession: " + hasession; + + Iterator i = cmpDescriptors.iterator(); + while( i.hasNext() ) + { + s += "\n\r CMP Descriptor: " + i.next(); + } + + return s; + } + + /** + * Verifies that the EJB is valid--if it is invalid, an exception is + * thrown + * + * @param buildDir The directory where the EJB remote interface, home + * interface, and implementation class must be found. + * @throws EjbcException If the EJB is invalid. + */ + private void checkConfiguration( File buildDir ) + throws EjbcException + { + + /* + * Check that the specified instance variables are valid + */ + if( home == null ) + { + throw new EjbcException( "A home interface was not found " + + "for the " + name + " EJB." ); + } + if( remote == null ) + { + throw new EjbcException( "A remote interface was not found " + + "for the " + name + " EJB." ); + } + if( implementation == null ) + { + throw new EjbcException( "An EJB implementation class was not " + + "found for the " + name + " EJB." ); + } + + if( ( !beantype.equals( ENTITY_BEAN ) ) + && ( !beantype.equals( STATELESS_SESSION ) ) + && ( !beantype.equals( STATEFUL_SESSION ) ) ) + { + throw new EjbcException( "The beantype found (" + beantype + ") " + + "isn't valid in the " + name + " EJB." ); + } + + if( cmp && ( !beantype.equals( ENTITY_BEAN ) ) ) + { + System.out.println( "CMP stubs and skeletons may not be generated" + + " for a Session Bean -- the \"cmp\" attribute will be" + + " ignoredfor the " + name + " EJB." ); + } + + if( hasession && ( !beantype.equals( STATEFUL_SESSION ) ) ) + { + System.out.println( "Highly available stubs and skeletons may " + + "only be generated for a Stateful Session Bean -- the " + + "\"hasession\" attribute will be ignored for the " + + name + " EJB." ); + } + + /* + * Check that the EJB "source" classes all exist + */ + if( !remote.getClassFile( buildDir ).exists() ) + { + throw new EjbcException( "The remote interface " + + remote.getQualifiedClassName() + " could not be " + + "found." ); + } + if( !home.getClassFile( buildDir ).exists() ) + { + throw new EjbcException( "The home interface " + + home.getQualifiedClassName() + " could not be " + + "found." ); + } + if( !implementation.getClassFile( buildDir ).exists() ) + { + throw new EjbcException( "The EJB implementation class " + + implementation.getQualifiedClassName() + " could " + + "not be found." ); + } + } + + /** + * Builds an array of class names which represent the stubs and + * skeletons which need to be generated for a given EJB. The class names + * are fully qualified. Nine classes are generated for all EJBs while an + * additional six classes are generated for beans requiring RMI/IIOP + * access. + * + * @return An array of Strings representing the fully-qualified class + * names for the stubs and skeletons to be generated. + */ + private String[] classesToGenerate() + { + String[] classnames = ( iiop ) ? new String[15] : new String[9]; + + final String remotePkg = remote.getPackageName() + "."; + final String remoteClass = remote.getClassName(); + final String homePkg = home.getPackageName() + "."; + final String homeClass = home.getClassName(); + final String implPkg = implementation.getPackageName() + "."; + final String implFullClass = implementation.getQualifiedWithUnderscores(); + int index = 0; + + String fullPath; + + classnames[index++] = implPkg + "ejb_fac_" + implFullClass; + classnames[index++] = implPkg + "ejb_home_" + implFullClass; + classnames[index++] = implPkg + "ejb_skel_" + implFullClass; + classnames[index++] = remotePkg + "ejb_kcp_skel_" + remoteClass; + classnames[index++] = homePkg + "ejb_kcp_skel_" + homeClass; + classnames[index++] = remotePkg + "ejb_kcp_stub_" + remoteClass; + classnames[index++] = homePkg + "ejb_kcp_stub_" + homeClass; + classnames[index++] = remotePkg + "ejb_stub_" + remoteClass; + classnames[index++] = homePkg + "ejb_stub_" + homeClass; + + if( !iiop ) + { + return classnames; + } + + classnames[index++] = remotePkg + "_" + remoteClass + "_Stub"; + classnames[index++] = homePkg + "_" + homeClass + "_Stub"; + classnames[index++] = remotePkg + "_ejb_RmiCorbaBridge_" + + remoteClass + "_Tie"; + classnames[index++] = homePkg + "_ejb_RmiCorbaBridge_" + homeClass + + "_Tie"; + classnames[index++] = remotePkg + "ejb_RmiCorbaBridge_" + + remoteClass; + classnames[index++] = homePkg + "ejb_RmiCorbaBridge_" + homeClass; + + return classnames; + } + + /** + * Examines each of the EJB stubs and skeletons in the destination + * directory and returns the modification timestamp for the "oldest" + * class. If one of the stubs or skeletons cannot be found, -1 + * is returned. + * + * @param destDir Description of Parameter + * @return The modification timestamp for the "oldest" EJB stub or + * skeleton. If one of the classes cannot be found, -1 + * is returned. + * @throws BuildException If the canonical path of the destination + * directory cannot be found. + */ + private long destClassesModified( File destDir ) + { + String[] classnames = classesToGenerate();// List of all stubs & skels + long destClassesModified = new Date().getTime();// Earliest mod time + boolean allClassesFound = true;// Has each been found? + + /* + * Loop through each stub/skeleton class that must be generated, and + * determine (if all exist) which file has the most recent timestamp + */ + for( int i = 0; i < classnames.length; i++ ) + { + + String pathToClass = + classnames[i].replace( '.', File.separatorChar ) + ".class"; + File classFile = new File( destDir, pathToClass ); + + /* + * Add each stub/skeleton class to the list of EJB files. Note + * that each class is added even if it doesn't exist now. + */ + ejbFiles.put( pathToClass, classFile ); + + allClassesFound = allClassesFound && classFile.exists(); + + if( allClassesFound ) + { + long fileMod = classFile.lastModified(); + + /* + * Keep track of the oldest modification timestamp + */ + destClassesModified = Math.min( destClassesModified, fileMod ); + } + } + + return ( allClassesFound ) ? destClassesModified : -1; + } + + /** + * Examines each of the EJB source classes (home, remote, and + * implementation) and returns the modification timestamp for the + * "oldest" class. + * + * @param buildDir Description of Parameter + * @return The modification timestamp for the "oldest" EJB source class. + * @throws BuildException If one of the EJB source classes cannot be + * found on the classpath. + */ + private long sourceClassesModified( File buildDir ) + { + long latestModified;// The timestamp of the "newest" class + long modified;// Timestamp for a given class + File remoteFile;// File for the remote interface class + File homeFile;// File for the home interface class + File implFile;// File for the EJB implementation class + + /* + * Check the timestamp on the remote interface + */ + remoteFile = remote.getClassFile( buildDir ); + modified = remoteFile.lastModified(); + if( modified == -1 ) + { + System.out.println( "The class " + + remote.getQualifiedClassName() + " couldn't " + + "be found on the classpath" ); + return -1; + } + latestModified = modified; + + /* + * Check the timestamp on the home interface + */ + homeFile = home.getClassFile( buildDir ); + modified = homeFile.lastModified(); + if( modified == -1 ) + { + System.out.println( "The class " + + home.getQualifiedClassName() + " couldn't be " + + "found on the classpath" ); + return -1; + } + latestModified = Math.max( latestModified, modified ); + + /* + * Check the timestamp on the EJB implementation class. + * + * Note that if ONLY the implementation class has changed, it's not + * necessary to rebuild the EJB stubs and skeletons. For this + * reason, we ensure the file exists (using lastModified above), but + * we DON'T compare it's timestamp with the timestamps of the home + * and remote interfaces (because it's irrelevant in determining if + * ejbc must be run) + */ + implFile = implementation.getClassFile( buildDir ); + modified = implFile.lastModified(); + if( modified == -1 ) + { + System.out.println( "The class " + + implementation.getQualifiedClassName() + + " couldn't be found on the classpath" ); + return -1; + } + + String pathToFile = remote.getQualifiedClassName(); + pathToFile = pathToFile.replace( '.', File.separatorChar ) + ".class"; + ejbFiles.put( pathToFile, remoteFile ); + + pathToFile = home.getQualifiedClassName(); + pathToFile = pathToFile.replace( '.', File.separatorChar ) + ".class"; + ejbFiles.put( pathToFile, homeFile ); + + pathToFile = implementation.getQualifiedClassName(); + pathToFile = pathToFile.replace( '.', File.separatorChar ) + ".class"; + ejbFiles.put( pathToFile, implFile ); + + return latestModified; + } + + }// End of EjbcException inner class + + + /** + * This inner class is an XML document handler that can be used to parse EJB + * descriptors (both the standard EJB descriptor as well as the iAS-specific + * descriptor that stores additional values for iAS). Once the descriptors + * have been processed, the list of EJBs found can be obtained by calling + * the getEjbs() method. + * + * @author Greg Nelson greg@netscape.com + * + * @see EjbInfo + */ + private class EjbcHandler extends HandlerBase + { + + /* + * Two Maps are used to track local DTDs that will be used in case the + * remote copies of these DTDs cannot be accessed. The key for the Map + * is the DTDs public ID and the value is the local location for the DTD + */ + private Map resourceDtds = new HashMap(); + private Map fileDtds = new HashMap(); + + private Map ejbs = new HashMap();// One item within the Map + private boolean iasDescriptor = false;// Is doc iAS or EJB descriptor + + private String currentLoc = "";// List of EJBs found in XML + private EjbInfo currentEjb;// Tracks current element + private String currentText;// Tracks current text data + private String ejbType;// "session" or "entity" + + /** + * Constructs a new instance of the handler and registers local copies + * of the standard EJB 1.1 descriptor DTD as well as iAS's EJB + * descriptor DTD. + */ + public EjbcHandler() + { + final String PUBLICID_EJB11 = + "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"; + final String PUBLICID_IPLANET_EJB_60 = + "-//Sun Microsystems, Inc.//DTD iAS Enterprise JavaBeans 1.0//EN"; + + final String DEFAULT_IAS60_EJB11_DTD_LOCATION = + "ejb-jar_1_1.dtd"; + final String DEFAULT_IAS60_DTD_LOCATION = + "IASEjb_jar_1_0.dtd"; + + registerDTD( PUBLICID_EJB11, DEFAULT_IAS60_EJB11_DTD_LOCATION ); + registerDTD( PUBLICID_IPLANET_EJB_60, DEFAULT_IAS60_DTD_LOCATION ); + } + + /** + * Returns the value of the display-name element found in the standard + * EJB 1.1 descriptor. + * + * @return String display-name value. + */ + public String getDisplayName() + { + return displayName; + } + + /** + * Returns the list of EJB objects found during the processing of the + * standard EJB 1.1 descriptor and iAS-specific EJB descriptor. + * + * @return An array of EJBs which were found during the descriptor + * parsing. + */ + public EjbInfo[] getEjbs() + { + return ( EjbInfo[] )ejbs.values().toArray( new EjbInfo[ejbs.size()] ); + } + + /** + * Receive notification that character data has been found in the XML + * document + * + * @param ch Array of characters which have been found in the document. + * @param start Starting index of the data found in the document. + * @param len The number of characters found in the document. + * @throws SAXException If the parser cannot process the document. + */ + public void characters( char[] ch, int start, int len ) + throws SAXException + { + + currentText += new String( ch ).substring( start, start + len ); + } + + /** + * Receive notification that the end of an XML element has been found. + * + * @param name String name of the element. + * @throws SAXException If the parser cannot process the document. + */ + public void endElement( String name ) + throws SAXException + { + + /* + * If this is a standard EJB 1.1 descriptor, we are looking for one + * set of data, while if this is an iAS-specific descriptor, we're + * looking for different set of data. Hand the processing off to + * the appropriate method. + */ + if( iasDescriptor ) + { + iasCharacters( currentText ); + } + else + { + stdCharacters( currentText ); + } + + /* + * I need to "pop" the element off the String (currentLoc) which + * always represents my current location in the XML document. + */ + int nameLength = name.length() + 1;// Add one for the "\" + int locLength = currentLoc.length(); + + currentLoc = currentLoc.substring( 0, locLength - nameLength ); + } + + /** + * Registers a local DTD that will be used when parsing an EJB + * descriptor. When the DTD's public identifier is found in an XML + * document, the parser will reference the local DTD rather than the + * remote DTD. This enables XML documents to be processed even when the + * public DTD isn't available. + * + * @param publicID The DTD's public identifier. + * @param location The location of the local DTD copy -- the location + * may either be a resource found on the classpath or a local file. + */ + public void registerDTD( String publicID, String location ) + { + log( "Registering: " + location ); + if( ( publicID == null ) || ( location == null ) ) + { + return; + } + + if( ClassLoader.getSystemResource( location ) != null ) + { + log( "Found resource: " + location ); + resourceDtds.put( publicID, location ); + } + else + { + File dtdFile = new File( location ); + if( dtdFile.exists() && dtdFile.isFile() ) + { + log( "Found file: " + location ); + fileDtds.put( publicID, location ); + } + } + } + + /** + * Resolves an external entity found during XML processing. If a public + * ID is found that has been registered with the handler, an + * InputSource will be returned which refers to the local copy. + * If the public ID hasn't been registered or if an error occurs, the + * superclass implementation is used. + * + * @param publicId The DTD's public identifier. + * @param systemId The location of the DTD, as found in the XML + * document. + * @return Description of the Returned Value + * @exception SAXException Description of Exception + */ + public InputSource resolveEntity( String publicId, String systemId ) + throws SAXException + { + InputStream inputStream = null; + + try + { + + /* + * Search the resource Map and (if not found) file Map + */ + String location = ( String )resourceDtds.get( publicId ); + if( location != null ) + { + inputStream + = ClassLoader.getSystemResource( location ).openStream(); + } + else + { + location = ( String )fileDtds.get( publicId ); + if( location != null ) + { + inputStream = new FileInputStream( location ); + } + } + } + catch( IOException e ) + { + return super.resolveEntity( publicId, systemId ); + } + + if( inputStream == null ) + { + return super.resolveEntity( publicId, systemId ); + } + else + { + return new InputSource( inputStream ); + } + } + + /** + * Receive notification that the start of an XML element has been found. + * + * @param name String name of the element found. + * @param atts AttributeList of the attributes included with the element + * (if any). + * @throws SAXException If the parser cannot process the document. + */ + public void startElement( String name, AttributeList atts ) + throws SAXException + { + + /* + * I need to "push" the element onto the String (currentLoc) which + * always represents the current location in the XML document. + */ + currentLoc += "\\" + name; + + /* + * A new element has started, so reset the text being captured + */ + currentText = ""; + + if( currentLoc.equals( "\\ejb-jar" ) ) + { + iasDescriptor = false; + } + else if( currentLoc.equals( "\\ias-ejb-jar" ) ) + { + iasDescriptor = true; + } + + if( ( name.equals( "session" ) ) || ( name.equals( "entity" ) ) ) + { + ejbType = name; + } + } + + /** + * Receive notification that character data has been found in an + * iAS-specific descriptor. We're interested in retrieving data + * indicating whether the bean must support RMI/IIOP access, whether the + * bean must provide highly available stubs and skeletons (in the case + * of stateful session beans), and if this bean uses additional CMP XML + * descriptors (in the case of entity beans with CMP). + * + * @param value String data found in the XML document. + */ + private void iasCharacters( String value ) + { + String base = "\\ias-ejb-jar\\enterprise-beans\\" + ejbType; + + if( currentLoc.equals( base + "\\ejb-name" ) ) + { + currentEjb = ( EjbInfo )ejbs.get( value ); + if( currentEjb == null ) + { + currentEjb = new EjbInfo( value ); + ejbs.put( value, currentEjb ); + } + } + else if( currentLoc.equals( base + "\\iiop" ) ) + { + currentEjb.setIiop( value ); + } + else if( currentLoc.equals( base + "\\failover-required" ) ) + { + currentEjb.setHasession( value ); + } + else if( currentLoc.equals( base + "\\persistence-manager" + + "\\properties-file-location" ) ) + { + currentEjb.addCmpDescriptor( value ); + } + } + + /** + * Receive notification that character data has been found in a standard + * EJB 1.1 descriptor. We're interested in retrieving the home + * interface, remote interface, implementation class, the type of bean, + * and if the bean uses CMP. + * + * @param value String data found in the XML document. + */ + private void stdCharacters( String value ) + { + + if( currentLoc.equals( "\\ejb-jar\\display-name" ) ) + { + displayName = value; + return; + } + + String base = "\\ejb-jar\\enterprise-beans\\" + ejbType; + + if( currentLoc.equals( base + "\\ejb-name" ) ) + { + currentEjb = ( EjbInfo )ejbs.get( value ); + if( currentEjb == null ) + { + currentEjb = new EjbInfo( value ); + ejbs.put( value, currentEjb ); + } + } + else if( currentLoc.equals( base + "\\home" ) ) + { + currentEjb.setHome( value ); + } + else if( currentLoc.equals( base + "\\remote" ) ) + { + currentEjb.setRemote( value ); + } + else if( currentLoc.equals( base + "\\ejb-class" ) ) + { + currentEjb.setImplementation( value ); + } + else if( currentLoc.equals( base + "\\session-type" ) ) + { + currentEjb.setBeantype( value ); + } + else if( currentLoc.equals( base + "\\persistence-type" ) ) + { + currentEjb.setCmp( value ); + } + } + }// End of Classname inner class + + + /** + * Thread class used to redirect output from an InputStream to + * the JRE standard output. This class may be used to redirect output from + * an external process to the standard output. + * + * @author Greg Nelson greg@netscape.com + * + */ + private class RedirectOutput extends Thread + { + InputStream stream;// Stream to read and redirect to standard output + + /** + * Constructs a new instance that will redirect output from the + * specified stream to the standard output. + * + * @param stream InputStream which will be read and redirected to the + * standard output. + */ + public RedirectOutput( InputStream stream ) + { + this.stream = stream; + } + + /** + * Reads text from the input stream and redirects it to standard output + * using a separate thread. + */ + public void run() + { + BufferedReader reader = new BufferedReader( + new InputStreamReader( stream ) ); + String text; + try + { + while( ( text = reader.readLine() ) != null ) + { + System.out.println( text ); + } + } + catch( IOException e ) + { + e.printStackTrace(); + } + finally + { + try + { + reader.close(); + } + catch( IOException e ) + { + // Do nothing + } + } + } + }// End of RedirectOutput inner class + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java new file mode 100644 index 000000000..ce5f22396 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.IOException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.xml.sax.SAXException; + +/** + * Task to compile EJB stubs and skeletons for the iPlanet Application Server. + * The EJBs to be processed are specified by the EJB 1.1 standard XML + * descriptor, and additional attributes are obtained from the iPlanet + * Application Server-specific XML descriptor. Since the XML descriptors can + * include multiple EJBs, this is a convenient way of specifying many EJBs in a + * single Ant task. The following attributes are allowed: + *

        + *
      • ejbdescriptor -- Standard EJB 1.1 XML descriptor (typically + * titled "ejb-jar.xml"). This attribute is required. + *
      • iasdescriptor -- EJB XML descriptor for iPlanet Application + * Server (typically titled "ias-ejb-jar.xml). This attribute is required. + * + *
      • dest -- The is the base directory where the RMI stubs and + * skeletons are written. In addition, the class files for each bean (home + * interface, remote interface, and EJB implementation) must be found in this + * directory. This attribute is required. + *
      • classpath -- The classpath used when generating EJB stubs and + * skeletons. This is an optional attribute (if omitted, the classpath + * specified when Ant was started will be used). Nested "classpath" elements + * may also be used. + *
      • keepgenerated -- Indicates whether or not the Java source files + * which are generated by ejbc will be saved or automatically deleted. If + * "yes", the source files will be retained. This is an optional attribute (if + * omitted, it defaults to "no"). + *
      • debug -- Indicates whether or not the ejbc utility should log + * additional debugging statements to the standard output. If "yes", the + * additional debugging statements will be generated (if omitted, it defaults + * to "no"). + *
      • iashome -- May be used to specify the "home" directory for this + * iPlanet Application Server installation. This is used to find the ejbc + * utility if it isn't included in the user's system path. This is an optional + * attribute (if specified, it should refer to the [install-location]/iplanet/ias6/ias + * directory). If omitted, the ejbc utility + * must be on the user's system path. + *
      + *

      + * + * For each EJB specified, this task will locate the three classes that comprise + * the EJB. If these class files cannot be located in the dest + * directory, the task will fail. The task will also attempt to locate the EJB + * stubs and skeletons in this directory. If found, the timestamps on the stubs + * and skeletons will be checked to ensure they are up to date. Only if these + * files cannot be found or if they are out of date will ejbc be called to + * generate new stubs and skeletons. + * + * @author Greg Nelson greg@netscape.com + * @see IPlanetEjbc + */ +public class IPlanetEjbcTask extends Task +{ + private boolean keepgenerated = false; + private boolean debug = false; + private Path classpath; + private File dest; + + /* + * Attributes set by the Ant build file + */ + private File ejbdescriptor; + private File iasdescriptor; + private File iashome; + + /** + * Sets the classpath to be used when compiling the EJB stubs and skeletons. + * + * @param classpath The classpath to be used. + */ + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debug A boolean indicating if debugging output should be generated + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Sets the destination directory where the EJB "source" classes must exist + * and where the stubs and skeletons will be written. The destination + * directory must exist before this task is executed. + * + * @param dest The directory where the compiled classes will be written. + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Sets the location of the standard XML EJB descriptor. Typically, this + * file is named "ejb-jar.xml". + * + * @param ejbdescriptor The name and location of the EJB descriptor. + */ + public void setEjbdescriptor( File ejbdescriptor ) + { + this.ejbdescriptor = ejbdescriptor; + } + + /** + * Sets the location of the iAS-specific XML EJB descriptor. Typically, this + * file is named "ias-ejb-jar.xml". + * + * @param iasdescriptor The name and location of the iAS-specific EJB + * descriptor. + */ + public void setIasdescriptor( File iasdescriptor ) + { + this.iasdescriptor = iasdescriptor; + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be [install-location]/iplanet/ias6/ias + * . + * + * @param iashome The home directory for the user's iAS installation. + */ + public void setIashome( File iashome ) + { + this.iashome = iashome; + } + + /** + * Sets whether or not the Java source files which are generated by the ejbc + * process should be retained or automatically deleted. + * + * @param keepgenerated A boolean indicating if the Java source files for + * the stubs and skeletons should be retained. + */ + public void setKeepgenerated( boolean keepgenerated ) + { + this.keepgenerated = keepgenerated; + } + + /** + * Creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Does the work. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + checkConfiguration(); + + executeEjbc( getParser() ); + } + + /** + * Returns the CLASSPATH to be used when calling EJBc. If no user CLASSPATH + * is specified, the System classpath is returned instead. + * + * @return Path The classpath to be used for EJBc. + */ + private Path getClasspath() + { + if( classpath == null ) + { + classpath = Path.systemClasspath; + } + + return classpath; + } + + /** + * Returns a SAXParser that may be used to process the XML descriptors. + * + * @return Parser which may be used to process the EJB descriptors. + * @throws BuildException If the parser cannot be created or configured. + */ + private SAXParser getParser() + throws BuildException + { + + SAXParser saxParser = null; + try + { + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setValidating( true ); + saxParser = saxParserFactory.newSAXParser(); + } + catch( SAXException e ) + { + String msg = "Unable to create a SAXParser: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + catch( ParserConfigurationException e ) + { + String msg = "Unable to create a SAXParser: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + + return saxParser; + } + + /** + * Verifies that the user selections are valid. + * + * @throws BuildException If the user selections are invalid. + */ + private void checkConfiguration() + throws BuildException + { + + if( ejbdescriptor == null ) + { + String msg = "The standard EJB descriptor must be specified using " + + "the \"ejbdescriptor\" attribute."; + throw new BuildException( msg, location ); + } + if( ( !ejbdescriptor.exists() ) || ( !ejbdescriptor.isFile() ) ) + { + String msg = "The standard EJB descriptor (" + ejbdescriptor + + ") was not found or isn't a file."; + throw new BuildException( msg, location ); + } + + if( iasdescriptor == null ) + { + String msg = "The iAS-speific XML descriptor must be specified using" + + " the \"iasdescriptor\" attribute."; + throw new BuildException( msg, location ); + } + if( ( !iasdescriptor.exists() ) || ( !iasdescriptor.isFile() ) ) + { + String msg = "The iAS-specific XML descriptor (" + iasdescriptor + + ") was not found or isn't a file."; + throw new BuildException( msg, location ); + } + + if( dest == null ) + { + String msg = "The destination directory must be specified using " + + "the \"dest\" attribute."; + throw new BuildException( msg, location ); + } + if( ( !dest.exists() ) || ( !dest.isDirectory() ) ) + { + String msg = "The destination directory (" + dest + ") was not " + + "found or isn't a directory."; + throw new BuildException( msg, location ); + } + + if( ( iashome != null ) && ( !iashome.isDirectory() ) ) + { + String msg = "If \"iashome\" is specified, it must be a valid " + + "directory (it was set to " + iashome + ")."; + throw new BuildException( msg, getLocation() ); + } + } + + /** + * Executes the EJBc utility using the SAXParser provided. + * + * @param saxParser SAXParser that may be used to process the EJB + * descriptors + * @throws BuildException If there is an error reading or parsing the XML + * descriptors + */ + private void executeEjbc( SAXParser saxParser ) + throws BuildException + { + IPlanetEjbc ejbc = new IPlanetEjbc( ejbdescriptor, + iasdescriptor, + dest, + getClasspath().toString(), + saxParser ); + ejbc.setRetainSource( keepgenerated ); + ejbc.setDebugOutput( debug ); + if( iashome != null ) + { + ejbc.setIasHomeDir( iashome ); + } + + try + { + ejbc.execute(); + } + catch( IOException e ) + { + String msg = "An IOException occurred while trying to read the XML " + + "descriptor file: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + catch( SAXException e ) + { + String msg = "A SAXException occurred while trying to read the XML " + + "descriptor file: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + catch( IPlanetEjbc.EjbcException e ) + { + String msg = "An exception occurred while trying to run the ejbc " + + "utility: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java new file mode 100644 index 000000000..a5a7d70f0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FilenameFilter; + +public class InnerClassFilenameFilter implements FilenameFilter +{ + private String baseClassName; + + InnerClassFilenameFilter( String baseclass ) + { + int extidx = baseclass.lastIndexOf( ".class" ); + if( extidx == -1 ) + { + extidx = baseclass.length() - 1; + } + baseClassName = baseclass.substring( 0, extidx ); + } + + public boolean accept( File Dir, String filename ) + { + if( ( filename.lastIndexOf( "." ) != filename.lastIndexOf( ".class" ) ) + || ( filename.indexOf( baseClassName + "$" ) != 0 ) ) + { + return false; + } + return true; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java new file mode 100644 index 000000000..06fd4ad05 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.util.Hashtable; +import org.apache.tools.ant.Project; + +/** + * The deployment tool to add the jboss specific deployment descriptor to the + * ejb jar file. Jboss only requires one additional file jboss.xml and does not + * require any additional compilation. + * + * @author Paul Austin + * @version 1.0 + * @see EjbJar#createJboss + */ +public class JbossDeploymentTool extends GenericDeploymentTool +{ + protected final static String JBOSS_DD = "jboss.xml"; + protected final static String JBOSS_CMPD = "jaws.xml"; + + /** + * Instance variable that stores the suffix for the jboss jarfile. + */ + private String jarSuffix = ".jar"; + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + File jbossDD = new File( getConfig().descriptorDir, ddPrefix + JBOSS_DD ); + if( jbossDD.exists() ) + { + ejbFiles.put( META_DIR + JBOSS_DD, jbossDD ); + } + else + { + log( "Unable to locate jboss deployment descriptor. It was expected to be in " + jbossDD.getPath(), Project.MSG_WARN ); + return; + } + + File jbossCMPD = new File( getConfig().descriptorDir, ddPrefix + JBOSS_CMPD ); + if( jbossCMPD.exists() ) + { + ejbFiles.put( META_DIR + JBOSS_CMPD, jbossCMPD ); + } + } + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( getDestDir(), baseName + jarSuffix ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java new file mode 100644 index 000000000..e4d43714a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java @@ -0,0 +1,424 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; + +/** + * Execute a Weblogic server. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class WLRun extends Task +{ + protected final static String DEFAULT_WL51_POLICY_FILE = "weblogic.policy"; + protected final static String DEFAULT_WL60_POLICY_FILE = "lib/weblogic.policy"; + protected final static String DEFAULT_PROPERTIES_FILE = "weblogic.properties"; + + private String weblogicMainClass = "weblogic.Server"; + + /** + * Addional arguments to pass to the JVM used to run weblogic + */ + private String additionalArgs = ""; + + /** + * The name of the weblogic server - used to select the server's directory + * in the weblogic home directory. + */ + private String weblogicSystemName = "myserver"; + + /** + * The file containing the weblogic properties for this server. + */ + private String weblogicPropertiesFile = null; + + /** + * additional args to pass to the spawned jvm + */ + private String additionalJvmArgs = ""; + + /** + * The location of the BEA Home under which this server is run. WL6 only + */ + private File beaHome = null; + + /** + * The management username + */ + private String managementUsername = "system"; + + /** + * The management password + */ + private String managementPassword = null; + + /** + * The provate key password - used for SSL + */ + private String pkPassword = null; + + /** + * The classpath to be used when running the Java VM. It must contain the + * weblogic classes and the implementation classes of the home and + * remote interfaces. + */ + private Path classpath; + + /** + * The security policy to use when running the weblogic server + */ + private String securityPolicy; + + /** + * The weblogic classpath to the be used when running weblogic. + */ + private Path weblogicClasspath; + + /** + * The weblogic domain + */ + private String weblogicDomainName; + + /** + * The weblogic system home directory + */ + private File weblogicSystemHome; + + public void setArgs( String args ) + { + additionalArgs = args; + } + + /** + * The location of the BEA Home. + * + * @param beaHome the BEA Home directory. + */ + public void setBEAHome( File beaHome ) + { + this.beaHome = beaHome; + } + + + /** + * Set the classpath to be used for this execution. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + this.classpath = classpath; + } + + /** + * Set the Domain to run in + * + * @param domain the domain + */ + public void setDomain( String domain ) + { + this.weblogicDomainName = domain; + } + + /** + * The location where weblogic lives. + * + * @param weblogicHome the home directory of weblogic. + */ + public void setHome( File weblogicHome ) + { + weblogicSystemHome = weblogicHome; + } + + /** + * Set the additional arguments to pass to the weblogic JVM + * + * @param args the arguments to be passed to the JVM + */ + public void setJvmargs( String args ) + { + this.additionalJvmArgs = args; + } + + /** + * Set the name of the server to run + * + * @param serverName The new Name value + */ + public void setName( String serverName ) + { + this.weblogicSystemName = serverName; + } + + /** + * Set the private key password so the server can decrypt the SSL private + * key file. + * + * @param pkpassword the private key password, + */ + public void setPKPassword( String pkpassword ) + { + this.pkPassword = pkpassword; + } + + + /** + * Set the management password of the server + * + * @param password the management pasword of the server. + */ + public void setPassword( String password ) + { + this.managementPassword = password; + } + + /** + * Set the security policy for this invocation of weblogic. + * + * @param securityPolicy the security policy to use. + */ + public void setPolicy( String securityPolicy ) + { + this.securityPolicy = securityPolicy; + } + + /** + * Set the properties file to use. The location of the properties file is + * relative to the weblogi system home + * + * @param propertiesFilename the properties file name + */ + public void setProperties( String propertiesFilename ) + { + this.weblogicPropertiesFile = propertiesFilename; + } + + /** + * Set the management username to run the server + * + * @param username the management username of the server. + */ + public void setUsername( String username ) + { + this.managementUsername = username; + } + + + public void setWeblogicMainClass( String c ) + { + weblogicMainClass = c; + } + + /** + * Set the weblogic classpath. The weblogic classpath is used by weblogic to + * support dynamic class loading. + * + * @param weblogicClasspath the weblogic classpath + */ + public void setWlclasspath( Path weblogicClasspath ) + { + this.weblogicClasspath = weblogicClasspath; + } + + /** + * Add the classpath for the user classes + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Get the classpath to the weblogic classpaths + * + * @return Description of the Returned Value + */ + public Path createWLClasspath() + { + if( weblogicClasspath == null ) + { + weblogicClasspath = new Path( project ); + } + return weblogicClasspath.createPath(); + } + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * a helper task. This approach allows the classpath of the helper task to + * be set. Since the weblogic tools require the class files of the project's + * home and remote interfaces to be available in the classpath, this also + * avoids having to start ant with the class path of the project it is + * building. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( weblogicSystemHome == null ) + { + throw new BuildException( "weblogic home must be set" ); + } + if( !weblogicSystemHome.isDirectory() ) + { + throw new BuildException( "weblogic home directory " + weblogicSystemHome.getPath() + + " is not valid" ); + } + + if( beaHome != null ) + { + executeWLS6(); + } + else + { + executeWLS(); + } + } + + private void executeWLS() + { + File securityPolicyFile = findSecurityPolicyFile( DEFAULT_WL51_POLICY_FILE ); + File propertiesFile = null; + + if( weblogicPropertiesFile == null ) + { + weblogicPropertiesFile = DEFAULT_PROPERTIES_FILE; + } + propertiesFile = new File( weblogicSystemHome, weblogicPropertiesFile ); + if( !propertiesFile.exists() ) + { + // OK, properties file may be absolute + propertiesFile = project.resolveFile( weblogicPropertiesFile ); + if( !propertiesFile.exists() ) + { + throw new BuildException( "Properties file " + weblogicPropertiesFile + + " not found in weblogic home " + weblogicSystemHome + + " or as absolute file" ); + } + } + + Java weblogicServer = ( Java )project.createTask( "java" ); + weblogicServer.setTaskName( getTaskName() ); + weblogicServer.setFork( true ); + weblogicServer.setClassname( weblogicMainClass ); + + String jvmArgs = additionalJvmArgs; + + if( weblogicClasspath != null ) + { + jvmArgs += " -Dweblogic.class.path=" + weblogicClasspath; + } + + jvmArgs += " -Djava.security.manager -Djava.security.policy==" + securityPolicyFile; + jvmArgs += " -Dweblogic.system.home=" + weblogicSystemHome; + jvmArgs += " -Dweblogic.system.name=" + weblogicSystemName; + jvmArgs += " -Dweblogic.system.propertiesFile=" + weblogicPropertiesFile; + + weblogicServer.createJvmarg().setLine( jvmArgs ); + weblogicServer.createArg().setLine( additionalArgs ); + + if( classpath != null ) + { + weblogicServer.setClasspath( classpath ); + } + if( weblogicServer.executeJava() != 0 ) + { + throw new BuildException( "Execution of weblogic server failed" ); + } + } + + private void executeWLS6() + { + File securityPolicyFile = findSecurityPolicyFile( DEFAULT_WL60_POLICY_FILE ); + if( !beaHome.isDirectory() ) + { + throw new BuildException( "BEA home " + beaHome.getPath() + + " is not valid" ); + } + + File configFile = new File( weblogicSystemHome, "config/" + weblogicDomainName + "/config.xml" ); + if( !configFile.exists() ) + { + throw new BuildException( "Server config file " + configFile + " not found." ); + } + + if( managementPassword == null ) + { + throw new BuildException( "You must supply a management password to start the server" ); + } + + Java weblogicServer = ( Java )project.createTask( "java" ); + weblogicServer.setTaskName( getTaskName() ); + weblogicServer.setFork( true ); + weblogicServer.setDir( weblogicSystemHome ); + weblogicServer.setClassname( weblogicMainClass ); + + String jvmArgs = additionalJvmArgs; + + jvmArgs += " -Dweblogic.Domain=" + weblogicDomainName; + jvmArgs += " -Dweblogic.Name=" + weblogicSystemName; + jvmArgs += " -Dweblogic.system.home=" + weblogicSystemHome; + + jvmArgs += " -Dbea.home=" + beaHome; + jvmArgs += " -Djava.security.policy==" + securityPolicyFile; + + jvmArgs += " -Dweblogic.management.username=" + managementUsername; + jvmArgs += " -Dweblogic.management.password=" + managementPassword; + if( pkPassword != null ) + { + jvmArgs += " -Dweblogic.pkpassword=" + pkPassword; + } + + weblogicServer.createJvmarg().setLine( jvmArgs ); + weblogicServer.createArg().setLine( additionalArgs ); + + if( classpath != null ) + { + weblogicServer.setClasspath( classpath ); + } + + if( weblogicServer.executeJava() != 0 ) + { + throw new BuildException( "Execution of weblogic server failed" ); + } + } + + private File findSecurityPolicyFile( String defaultSecurityPolicy ) + { + String securityPolicy = this.securityPolicy; + if( securityPolicy == null ) + { + securityPolicy = defaultSecurityPolicy; + } + File securityPolicyFile = new File( weblogicSystemHome, securityPolicy ); + // If an explicit securityPolicy file was specified, it maybe an + // absolute path. Use the project to resolve it. + if( this.securityPolicy != null && !securityPolicyFile.exists() ) + { + securityPolicyFile = project.resolveFile( securityPolicy ); + } + // If we still can't find it, complain + if( !securityPolicyFile.exists() ) + { + throw new BuildException( "Security policy " + securityPolicy + + " was not found." ); + } + return securityPolicyFile; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java new file mode 100644 index 000000000..076e057ed --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; + +/** + * Shutdown a Weblogic server. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class WLStop extends Task +{ + + /** + * The delay (in seconds) to wait before shutting down. + */ + private int delay = 0; + + /** + * The location of the BEA Home under which this server is run. WL6 only + */ + private File beaHome = null; + /** + * The classpath to be used. It must contains the weblogic.Admin class. + */ + private Path classpath; + + /** + * The password to use to shutdown the weblogic server. + */ + private String password; + + /** + * The URL which the weblogic server is listening on. + */ + private String serverURL; + + /** + * The weblogic username to use to request the shutdown. + */ + private String username; + + /** + * The location of the BEA Home. + * + * @param beaHome the BEA Home directory. + */ + public void setBEAHome( File beaHome ) + { + this.beaHome = beaHome; + } + + /** + * Set the classpath to be used for this compilation. + * + * @param path The new Classpath value + */ + public void setClasspath( Path path ) + { + this.classpath = path; + } + + + /** + * Set the delay (in seconds) before shutting down the server. + * + * @param s the selay. + */ + public void setDelay( String s ) + { + delay = Integer.parseInt( s ); + } + + /** + * Set the password to use to request shutdown of the server. + * + * @param s the password. + */ + public void setPassword( String s ) + { + this.password = s; + } + + /** + * Set the URL to which the weblogic server is listening. + * + * @param s the url. + */ + public void setUrl( String s ) + { + this.serverURL = s; + } + + /** + * Set the username to use to request shutdown of the server. + * + * @param s the username. + */ + public void setUser( String s ) + { + this.username = s; + } + + /** + * Add the classpath for the user classes + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * the weblogic admin task This approach allows the classpath of the helper + * task to be set. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( username == null || password == null ) + { + throw new BuildException( "weblogic username and password must both be set" ); + } + + if( serverURL == null ) + { + throw new BuildException( "The url of the weblogic server must be provided." ); + } + + Java weblogicAdmin = ( Java )project.createTask( "java" ); + weblogicAdmin.setFork( true ); + weblogicAdmin.setClassname( "weblogic.Admin" ); + String args; + + if( beaHome == null ) + { + args = serverURL + " SHUTDOWN " + username + " " + password + " " + delay; + } + else + { + args = " -url " + serverURL + + " -username " + username + + " -password " + password + + " SHUTDOWN " + " " + delay; + } + + weblogicAdmin.setArgs( args ); + weblogicAdmin.setClasspath( classpath ); + weblogicAdmin.execute(); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java new file mode 100644 index 000000000..cc7d965ad --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java @@ -0,0 +1,852 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; +import org.xml.sax.InputSource; + +public class WeblogicDeploymentTool extends GenericDeploymentTool +{ + public final static String PUBLICID_EJB11 + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"; + public final static String PUBLICID_EJB20 + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"; + public final static String PUBLICID_WEBLOGIC_EJB510 + = "-//BEA Systems, Inc.//DTD WebLogic 5.1.0 EJB//EN"; + public final static String PUBLICID_WEBLOGIC_EJB600 + = "-//BEA Systems, Inc.//DTD WebLogic 6.0.0 EJB//EN"; + + protected final static String DEFAULT_WL51_EJB11_DTD_LOCATION + = "/weblogic/ejb/deployment/xml/ejb-jar.dtd"; + protected final static String DEFAULT_WL60_EJB11_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/ejb11-jar.dtd"; + protected final static String DEFAULT_WL60_EJB20_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/ejb20-jar.dtd"; + + protected final static String DEFAULT_WL51_DTD_LOCATION + = "/weblogic/ejb/deployment/xml/weblogic-ejb-jar.dtd"; + protected final static String DEFAULT_WL60_51_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/weblogic510-ejb-jar.dtd"; + protected final static String DEFAULT_WL60_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/weblogic600-ejb-jar.dtd"; + + protected final static String DEFAULT_COMPILER = "default"; + + protected final static String WL_DD = "weblogic-ejb-jar.xml"; + protected final static String WL_CMP_DD = "weblogic-cmp-rdbms-jar.xml"; + + protected final static String COMPILER_EJB11 = "weblogic.ejbc"; + protected final static String COMPILER_EJB20 = "weblogic.ejbc20"; + + /** + * Instance variable that stores the suffix for the weblogic jarfile. + */ + private String jarSuffix = ".jar"; + + /** + * Instance variable that determines whether generic ejb jars are kept. + */ + private boolean keepgenerated = false; + + /** + * Instance variable that stores the fully qualified classname of the + * weblogic EJBC compiler + */ + private String ejbcClass = null; + + private String additionalArgs = ""; + + private boolean keepGeneric = false; + + private String compiler = null; + + private boolean alwaysRebuild = true; + + /** + * controls whether ejbc is run on the generated jar + */ + private boolean noEJBC = false; + + /** + * Indicates if the old CMP location convention is to be used. + */ + private boolean newCMP = false; + + /** + * The classpath to the weblogic classes. + */ + private Path wlClasspath = null; + + /** + * The weblogic.StdoutSeverityLevel to use when running the JVM that + * executes ejbc. Set to 16 to avoid the warnings about EJB Home and Remotes + * being in the classpath + */ + private Integer jvmDebugLevel = null; + + /** + * Instance variable that stores the location of the ejb 1.1 DTD file. + */ + private String ejb11DTD; + + /** + * Instance variable that stores the location of the weblogic DTD file. + */ + private String weblogicDTD; + + /** + * sets some additional args to send to ejbc. + * + * @param args The new Args value + */ + public void setArgs( String args ) + { + this.additionalArgs = args; + } + + /** + * The compiler (switch -compiler) to use + * + * @param compiler The new Compiler value + */ + public void setCompiler( String compiler ) + { + this.compiler = compiler; + } + + /** + * Setter used to store the location of the Sun's Generic EJB DTD. This can + * be a file on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setEJBdtd( String inString ) + { + this.ejb11DTD = inString; + } + + /** + * Set the classname of the ejbc compiler + * + * @param ejbcClass The new EjbcClass value + */ + public void setEjbcClass( String ejbcClass ) + { + this.ejbcClass = ejbcClass; + } + + /** + * Sets the weblogic.StdoutSeverityLevel to use when running the JVM that + * executes ejbc. Set to 16 to avoid the warnings about EJB Home and Remotes + * being in the classpath + * + * @param jvmDebugLevel The new JvmDebugLevel value + */ + public void setJvmDebugLevel( Integer jvmDebugLevel ) + { + this.jvmDebugLevel = jvmDebugLevel; + } + + /** + * Sets whether -keepgenerated is passed to ejbc (that is, the .java source + * files are kept). + * + * @param inValue either 'true' or 'false' + */ + public void setKeepgenerated( String inValue ) + { + this.keepgenerated = Boolean.valueOf( inValue ).booleanValue(); + } + + /** + * Setter used to store the value of keepGeneric + * + * @param inValue a string, either 'true' or 'false'. + */ + public void setKeepgeneric( boolean inValue ) + { + this.keepGeneric = inValue; + } + + /** + * Set the value of the newCMP scheme. The old CMP scheme locates the + * weblogic CMP descriptor based on the naming convention where the weblogic + * CMP file is expected to be named with the bean name as the prefix. Under + * this scheme the name of the CMP descriptor does not match the name + * actually used in the main weblogic EJB descriptor. Also, descriptors + * which contain multiple CMP references could not be used. + * + * @param newCMP The new NewCMP value + */ + public void setNewCMP( boolean newCMP ) + { + this.newCMP = newCMP; + } + + /** + * Do not EJBC the jar after it has been put together. + * + * @param noEJBC The new NoEJBC value + */ + public void setNoEJBC( boolean noEJBC ) + { + this.noEJBC = noEJBC; + } + + /** + * Set the value of the oldCMP scheme. This is an antonym for newCMP + * + * @param oldCMP The new OldCMP value + */ + public void setOldCMP( boolean oldCMP ) + { + this.newCMP = !oldCMP; + } + + /** + * Set the rebuild flag to false to only update changes in the jar rather + * than rerunning ejbc + * + * @param rebuild The new Rebuild value + */ + public void setRebuild( boolean rebuild ) + { + this.alwaysRebuild = rebuild; + } + + + /** + * Setter used to store the suffix for the generated weblogic jar file. + * + * @param inString the string to use as the suffix. + */ + public void setSuffix( String inString ) + { + this.jarSuffix = inString; + } + + public void setWLClasspath( Path wlClasspath ) + { + this.wlClasspath = wlClasspath; + } + + /** + * Setter used to store the location of the weblogic DTD. This can be a file + * on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setWLdtd( String inString ) + { + this.weblogicDTD = inString; + } + + + /** + * Setter used to store the location of the ejb-jar DTD. This can be a file + * on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setWeblogicdtd( String inString ) + { + setEJBdtd( inString ); + } + + /** + * Get the ejbc compiler class + * + * @return The EjbcClass value + */ + public String getEjbcClass() + { + return ejbcClass; + } + + public Integer getJvmDebugLevel() + { + return jvmDebugLevel; + } + + /** + * Get the classpath to the weblogic classpaths + * + * @return Description of the Returned Value + */ + public Path createWLClasspath() + { + if( wlClasspath == null ) + { + wlClasspath = new Path( getTask().getProject() ); + } + return wlClasspath.createPath(); + } + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + public void validateConfigured() + throws BuildException + { + super.validateConfigured(); + } + + /** + * Helper method invoked by isRebuildRequired to get a ClassLoader for a Jar + * File passed to it. + * + * @param classjar java.io.File representing jar file to get classes from. + * @return The ClassLoaderFromJar value + * @exception IOException Description of Exception + */ + protected ClassLoader getClassLoaderFromJar( File classjar ) + throws IOException + { + Path lookupPath = new Path( getTask().getProject() ); + lookupPath.setLocation( classjar ); + + Path classpath = getCombinedClasspath(); + if( classpath != null ) + { + lookupPath.append( classpath ); + } + + return new AntClassLoader( getTask().getProject(), lookupPath ); + } + + protected DescriptorHandler getWeblogicDescriptorHandler( final File srcDir ) + { + DescriptorHandler handler = + new DescriptorHandler( getTask(), srcDir ) + { + protected void processElement() + { + if( currentElement.equals( "type-storage" ) ) + { + // Get the filename of vendor specific descriptor + String fileNameWithMETA = currentText; + //trim the META_INF\ off of the file name + String fileName = fileNameWithMETA.substring( META_DIR.length(), + fileNameWithMETA.length() ); + File descriptorFile = new File( srcDir, fileName ); + + ejbFiles.put( fileNameWithMETA, descriptorFile ); + } + } + }; + + handler.registerDTD( PUBLICID_WEBLOGIC_EJB510, DEFAULT_WL51_DTD_LOCATION ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB510, DEFAULT_WL60_51_DTD_LOCATION ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB600, DEFAULT_WL60_DTD_LOCATION ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB510, weblogicDTD ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB600, weblogicDTD ); + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + } + return handler; + } + + + /** + * Helper method to check to see if a weblogic EBJ1.1 jar needs to be + * rebuilt using ejbc. Called from writeJar it sees if the "Bean" classes + * are the only thing that needs to be updated and either updates the Jar + * with the Bean classfile or returns true, saying that the whole weblogic + * jar needs to be regened with ejbc. This allows faster build times for + * working developers.

      + * + * The way weblogic ejbc works is it creates wrappers for the publicly + * defined methods as they are exposed in the remote interface. If the + * actual bean changes without changing the the method signatures then only + * the bean classfile needs to be updated and the rest of the weblogic jar + * file can remain the same. If the Interfaces, ie. the method signatures + * change or if the xml deployment dicriptors changed, the whole jar needs + * to be rebuilt with ejbc. This is not strictly true for the xml files. If + * the JNDI name changes then the jar doesnt have to be rebuild, but if the + * resources references change then it does. At this point the weblogic jar + * gets rebuilt if the xml files change at all. + * + * @param genericJarFile java.io.File The generic jar file. + * @param weblogicJarFile java.io.File The weblogic jar file to check to see + * if it needs to be rebuilt. + * @return The RebuildRequired value + */ + protected boolean isRebuildRequired( File genericJarFile, File weblogicJarFile ) + { + boolean rebuild = false; + + JarFile genericJar = null; + JarFile wlJar = null; + File newWLJarFile = null; + JarOutputStream newJarStream = null; + + try + { + log( "Checking if weblogic Jar needs to be rebuilt for jar " + weblogicJarFile.getName(), + Project.MSG_VERBOSE ); + // Only go forward if the generic and the weblogic file both exist + if( genericJarFile.exists() && genericJarFile.isFile() + && weblogicJarFile.exists() && weblogicJarFile.isFile() ) + { + //open jar files + genericJar = new JarFile( genericJarFile ); + wlJar = new JarFile( weblogicJarFile ); + + Hashtable genericEntries = new Hashtable(); + Hashtable wlEntries = new Hashtable(); + Hashtable replaceEntries = new Hashtable(); + + //get the list of generic jar entries + for( Enumeration e = genericJar.entries(); e.hasMoreElements(); ) + { + JarEntry je = ( JarEntry )e.nextElement(); + genericEntries.put( je.getName().replace( '\\', '/' ), je ); + } + //get the list of weblogic jar entries + for( Enumeration e = wlJar.entries(); e.hasMoreElements(); ) + { + JarEntry je = ( JarEntry )e.nextElement(); + wlEntries.put( je.getName(), je ); + } + + //Cycle Through generic and make sure its in weblogic + ClassLoader genericLoader = getClassLoaderFromJar( genericJarFile ); + for( Enumeration e = genericEntries.keys(); e.hasMoreElements(); ) + { + String filepath = ( String )e.nextElement(); + if( wlEntries.containsKey( filepath ) ) + {// File name/path match + + // Check files see if same + JarEntry genericEntry = ( JarEntry )genericEntries.get( filepath ); + JarEntry wlEntry = ( JarEntry )wlEntries.get( filepath ); + if( ( genericEntry.getCrc() != wlEntry.getCrc() ) || // Crc's Match + ( genericEntry.getSize() != wlEntry.getSize() ) ) + {// Size Match + + if( genericEntry.getName().endsWith( ".class" ) ) + { + //File are different see if its an object or an interface + String classname = genericEntry.getName().replace( File.separatorChar, '.' ); + classname = classname.substring( 0, classname.lastIndexOf( ".class" ) ); + Class genclass = genericLoader.loadClass( classname ); + if( genclass.isInterface() ) + { + //Interface changed rebuild jar. + log( "Interface " + genclass.getName() + " has changed", Project.MSG_VERBOSE ); + rebuild = true; + break; + } + else + { + //Object class Changed update it. + replaceEntries.put( filepath, genericEntry ); + } + } + else + { + // is it the manifest. If so ignore it + if( !genericEntry.getName().equals( "META-INF/MANIFEST.MF" ) ) + { + //File other then class changed rebuild + log( "Non class file " + genericEntry.getName() + " has changed", Project.MSG_VERBOSE ); + rebuild = true; + break; + } + } + } + } + else + {// a file doesnt exist rebuild + + log( "File " + filepath + " not present in weblogic jar", Project.MSG_VERBOSE ); + rebuild = true; + break; + } + } + + if( !rebuild ) + { + log( "No rebuild needed - updating jar", Project.MSG_VERBOSE ); + newWLJarFile = new File( weblogicJarFile.getAbsolutePath() + ".temp" ); + if( newWLJarFile.exists() ) + { + newWLJarFile.delete(); + } + + newJarStream = new JarOutputStream( new FileOutputStream( newWLJarFile ) ); + newJarStream.setLevel( 0 ); + + //Copy files from old weblogic jar + for( Enumeration e = wlEntries.elements(); e.hasMoreElements(); ) + { + byte[] buffer = new byte[1024]; + int bytesRead; + InputStream is; + JarEntry je = ( JarEntry )e.nextElement(); + if( je.getCompressedSize() == -1 || + je.getCompressedSize() == je.getSize() ) + { + newJarStream.setLevel( 0 ); + } + else + { + newJarStream.setLevel( 9 ); + } + + // Update with changed Bean class + if( replaceEntries.containsKey( je.getName() ) ) + { + log( "Updating Bean class from generic Jar " + je.getName(), Project.MSG_VERBOSE ); + // Use the entry from the generic jar + je = ( JarEntry )replaceEntries.get( je.getName() ); + is = genericJar.getInputStream( je ); + } + else + {//use fle from original weblogic jar + + is = wlJar.getInputStream( je ); + } + newJarStream.putNextEntry( new JarEntry( je.getName() ) ); + + while( ( bytesRead = is.read( buffer ) ) != -1 ) + { + newJarStream.write( buffer, 0, bytesRead ); + } + is.close(); + } + } + else + { + log( "Weblogic Jar rebuild needed due to changed interface or XML", Project.MSG_VERBOSE ); + } + } + else + { + rebuild = true; + } + } + catch( ClassNotFoundException cnfe ) + { + String cnfmsg = "ClassNotFoundException while processing ejb-jar file" + + ". Details: " + + cnfe.getMessage(); + throw new BuildException( cnfmsg, cnfe ); + } + catch( IOException ioe ) + { + String msg = "IOException while processing ejb-jar file " + + ". Details: " + + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + finally + { + // need to close files and perhaps rename output + if( genericJar != null ) + { + try + { + genericJar.close(); + } + catch( IOException closeException ) + {} + } + + if( wlJar != null ) + { + try + { + wlJar.close(); + } + catch( IOException closeException ) + {} + } + + if( newJarStream != null ) + { + try + { + newJarStream.close(); + } + catch( IOException closeException ) + {} + + weblogicJarFile.delete(); + newWLJarFile.renameTo( weblogicJarFile ); + if( !weblogicJarFile.exists() ) + { + rebuild = true; + } + } + } + + return rebuild; + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + File weblogicDD = new File( getConfig().descriptorDir, ddPrefix + WL_DD ); + + if( weblogicDD.exists() ) + { + ejbFiles.put( META_DIR + WL_DD, + weblogicDD ); + } + else + { + log( "Unable to locate weblogic deployment descriptor. It was expected to be in " + + weblogicDD.getPath(), Project.MSG_WARN ); + return; + } + + if( !newCMP ) + { + log( "The old method for locating CMP files has been DEPRECATED.", Project.MSG_VERBOSE ); + log( "Please adjust your weblogic descriptor and set newCMP=\"true\" " + + "to use the new CMP descriptor inclusion mechanism. ", Project.MSG_VERBOSE ); + // The the weblogic cmp deployment descriptor + File weblogicCMPDD = new File( getConfig().descriptorDir, ddPrefix + WL_CMP_DD ); + + if( weblogicCMPDD.exists() ) + { + ejbFiles.put( META_DIR + WL_CMP_DD, + weblogicCMPDD ); + } + } + else + { + // now that we have the weblogic descriptor, we parse the file + // to find other descriptors needed to deploy the bean. + // this could be the weblogic-cmp-rdbms.xml or any other O/R + // mapping tool descriptors. + try + { + File ejbDescriptor = ( File )ejbFiles.get( META_DIR + EJB_DD ); + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setValidating( true ); + SAXParser saxParser = saxParserFactory.newSAXParser(); + DescriptorHandler handler = getWeblogicDescriptorHandler( ejbDescriptor.getParentFile() ); + saxParser.parse( new InputSource + ( new FileInputStream + ( weblogicDD ) ), + handler ); + + Hashtable ht = handler.getFiles(); + Enumeration e = ht.keys(); + while( e.hasMoreElements() ) + { + String key = ( String )e.nextElement(); + ejbFiles.put( key, ht.get( key ) ); + } + } + catch( Exception e ) + { + String msg = "Exception while adding Vendor specific files: " + e.toString(); + throw new BuildException( msg, e ); + } + } + } + + protected void registerKnownDTDs( DescriptorHandler handler ) + { + // register all the known DTDs + handler.registerDTD( PUBLICID_EJB11, DEFAULT_WL51_EJB11_DTD_LOCATION ); + handler.registerDTD( PUBLICID_EJB11, DEFAULT_WL60_EJB11_DTD_LOCATION ); + handler.registerDTD( PUBLICID_EJB11, ejb11DTD ); + handler.registerDTD( PUBLICID_EJB20, DEFAULT_WL60_EJB20_DTD_LOCATION ); + } + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarFile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + protected void writeJar( String baseName, File jarFile, Hashtable files, + String publicId ) + throws BuildException + { + // need to create a generic jar first. + File genericJarFile = super.getVendorOutputJarFile( baseName ); + super.writeJar( baseName, genericJarFile, files, publicId ); + + if( alwaysRebuild || isRebuildRequired( genericJarFile, jarFile ) ) + { + buildWeblogicJar( genericJarFile, jarFile, publicId ); + } + if( !keepGeneric ) + { + log( "deleting generic jar " + genericJarFile.toString(), + Project.MSG_VERBOSE ); + genericJarFile.delete(); + } + } + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( getDestDir(), baseName + jarSuffix ); + } + + /** + * Helper method invoked by execute() for each WebLogic jar to be built. + * Encapsulates the logic of constructing a java task for calling + * weblogic.ejbc and executing it. + * + * @param sourceJar java.io.File representing the source (EJB1.1) jarfile. + * @param destJar java.io.File representing the destination, WebLogic + * jarfile. + * @param publicId Description of Parameter + */ + private void buildWeblogicJar( File sourceJar, File destJar, String publicId ) + { + org.apache.tools.ant.taskdefs.Java javaTask = null; + + if( noEJBC ) + { + try + { + getTask().getProject().copyFile( sourceJar, destJar ); + if( !keepgenerated ) + { + sourceJar.delete(); + } + return; + } + catch( IOException e ) + { + throw new BuildException( "Unable to write EJB jar", e ); + } + } + + String ejbcClassName = ejbcClass; + + try + { + javaTask = ( Java )getTask().getProject().createTask( "java" ); + javaTask.setTaskName( "ejbc" ); + + if( getJvmDebugLevel() != null ) + { + javaTask.createJvmarg().setLine( " -Dweblogic.StdoutSeverityLevel=" + jvmDebugLevel ); + } + + if( ejbcClassName == null ) + { + // try to determine it from publicId + if( PUBLICID_EJB11.equals( publicId ) ) + { + ejbcClassName = COMPILER_EJB11; + } + else if( PUBLICID_EJB20.equals( publicId ) ) + { + ejbcClassName = COMPILER_EJB20; + } + else + { + log( "Unrecognized publicId " + publicId + " - using EJB 1.1 compiler", Project.MSG_WARN ); + ejbcClassName = COMPILER_EJB11; + } + } + + javaTask.setClassname( ejbcClassName ); + javaTask.createArg().setLine( additionalArgs ); + if( keepgenerated ) + { + javaTask.createArg().setValue( "-keepgenerated" ); + } + if( compiler == null ) + { + // try to use the compiler specified by build.compiler. Right now we are just going + // to allow Jikes + String buildCompiler = getTask().getProject().getProperty( "build.compiler" ); + if( buildCompiler != null && buildCompiler.equals( "jikes" ) ) + { + javaTask.createArg().setValue( "-compiler" ); + javaTask.createArg().setValue( "jikes" ); + } + } + else + { + if( !compiler.equals( DEFAULT_COMPILER ) ) + { + javaTask.createArg().setValue( "-compiler" ); + javaTask.createArg().setLine( compiler ); + } + } + javaTask.createArg().setValue( sourceJar.getPath() ); + javaTask.createArg().setValue( destJar.getPath() ); + + Path classpath = wlClasspath; + if( classpath == null ) + { + classpath = getCombinedClasspath(); + } + + javaTask.setFork( true ); + if( classpath != null ) + { + javaTask.setClasspath( classpath ); + } + + log( "Calling " + ejbcClassName + " for " + sourceJar.toString(), + Project.MSG_VERBOSE ); + + if( javaTask.executeJava() != 0 ) + { + throw new BuildException( "Ejbc reported an error" ); + } + } + catch( Exception e ) + { + // Have to catch this because of the semantics of calling main() + String msg = "Exception while calling " + ejbcClassName + ". Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java new file mode 100644 index 000000000..3fab8bd54 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +public class WeblogicTOPLinkDeploymentTool extends WeblogicDeploymentTool +{ + + private final static String TL_DTD_LOC = "http://www.objectpeople.com/tlwl/dtd/toplink-cmp_2_5_1.dtd"; + private String toplinkDTD; + + private String toplinkDescriptor; + + /** + * Setter used to store the name of the toplink descriptor. + * + * @param inString the string to use as the descriptor name. + */ + public void setToplinkdescriptor( String inString ) + { + this.toplinkDescriptor = inString; + } + + /** + * Setter used to store the location of the toplink DTD file. This is + * expected to be an URL (file or otherwise). If running this on NT using a + * file URL, the safest thing would be to not use a drive spec in the URL + * and make sure the file resides on the drive that ANT is running from. + * This will keep the setting in the build XML platform independent. + * + * @param inString the string to use as the DTD location. + */ + public void setToplinkdtd( String inString ) + { + this.toplinkDTD = inString; + } + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + public void validateConfigured() + throws BuildException + { + super.validateConfigured(); + if( toplinkDescriptor == null ) + { + throw new BuildException( "The toplinkdescriptor attribute must be specified" ); + } + } + + protected DescriptorHandler getDescriptorHandler( File srcDir ) + { + DescriptorHandler handler = super.getDescriptorHandler( srcDir ); + if( toplinkDTD != null ) + { + handler.registerDTD( "-//The Object People, Inc.//DTD TOPLink for WebLogic CMP 2.5.1//EN", + toplinkDTD ); + } + else + { + handler.registerDTD( "-//The Object People, Inc.//DTD TOPLink for WebLogic CMP 2.5.1//EN", + TL_DTD_LOC ); + } + return handler; + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + super.addVendorFiles( ejbFiles, ddPrefix ); + // Then the toplink deployment descriptor + + // Setup a naming standard here?. + + + File toplinkDD = new File( getConfig().descriptorDir, ddPrefix + toplinkDescriptor ); + + if( toplinkDD.exists() ) + { + ejbFiles.put( META_DIR + toplinkDescriptor, + toplinkDD ); + } + else + { + log( "Unable to locate toplink deployment descriptor. It was expected to be in " + + toplinkDD.getPath(), Project.MSG_WARN ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java new file mode 100644 index 000000000..8272c3e20 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java @@ -0,0 +1,1633 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.jar.*; +import javax.xml.parsers.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.taskdefs.*; +import org.apache.tools.ant.taskdefs.ExecTask; +import org.apache.tools.ant.types.*; +import org.xml.sax.*; + + +/** + * Websphere deployment tool that augments the ejbjar task. + * + * @author Maneesh Sahu + */ + +public class WebsphereDeploymentTool extends GenericDeploymentTool +{ + + + + public final static String PUBLICID_EJB11 + + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"; + + public final static String PUBLICID_EJB20 + + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"; + + protected final static String SCHEMA_DIR = "Schema/"; + + + + protected final static String WAS_EXT = "ibm-ejb-jar-ext.xmi"; + + protected final static String WAS_BND = "ibm-ejb-jar-bnd.xmi"; + + protected final static String WAS_CMP_MAP = "Map.mapxmi"; + + protected final static String WAS_CMP_SCHEMA = "Schema.dbxmi"; + + + + /** + * Instance variable that stores the suffix for the websphere jarfile. + */ + + private String jarSuffix = ".jar"; + + + + /** + * Instance variable that determines whether generic ejb jars are kept. + */ + + + + private boolean keepgenerated = false; + + + + private String additionalArgs = ""; + + + + private boolean keepGeneric = false; + + + + private String compiler = null; + + + + private boolean alwaysRebuild = true; + + + + private boolean ejbdeploy = true; + + + + /** + * Indicates if the old CMP location convention is to be used. + */ + + private boolean newCMP = false; + + + + /** + * The classpath to the websphere classes. + */ + + private Path wasClasspath = null; + + + + /** + * true - Only output error messages, suppress informational messages + */ + + private boolean quiet = true; + + + + /** + * the scratchdir for the ejbdeploy operation + */ + + private String tempdir = "_ejbdeploy_temp"; + + + + /** + * true - Only generate the deployment code, do not run RMIC or Javac + */ + + private boolean codegen; + + + + /** + * The name of the database to create. (For top-down mapping only) + */ + + private String dbName; + + + + /** + * The name of the schema to create. (For top-down mappings only) + */ + + private String dbSchema; + + + + /** + * The DB Vendor name, the EJB is persisted against + */ + + private String dbVendor; + + + + /** + * Instance variable that stores the location of the ejb 1.1 DTD file. + */ + + private String ejb11DTD; + + + + /** + * true - Disable informational messages + */ + + private boolean noinform; + + + + /** + * true - Disable the validation steps + */ + + private boolean novalidate; + + + + /** + * true - Disable warning and informational messages + */ + + private boolean nowarn; + + + + /** + * Additional options for RMIC + */ + + private String rmicOptions; + + + + /** + * true - Enable internal tracing + */ + + private boolean trace; + + + + /** + * true- Use the WebSphere 3.5 compatible mapping rules + */ + + private boolean use35MappingRules; + + + + /** + * sets some additional args to send to ejbdeploy. + * + * @param args The new Args value + */ + + public void setArgs( String args ) + { + + this.additionalArgs = args; + + } + + + + /** + * (true) Only generate the deployment code, do not run RMIC or Javac + * + * @param codegen The new Codegen value + */ + + public void setCodegen( boolean codegen ) + { + + this.codegen = codegen; + + } + + + + /** + * The compiler (switch -compiler) to use + * + * @param compiler The new Compiler value + */ + + public void setCompiler( String compiler ) + { + + this.compiler = compiler; + + } + + + + /** + * Sets the name of the Database to create + * + * @param dbName The new Dbname value + */ + + public void setDbname( String dbName ) + { + + this.dbName = dbName; + + } + + + + /** + * Sets the name of the schema to create + * + * @param dbSchema The new Dbschema value + */ + + public void setDbschema( String dbSchema ) + { + + this.dbSchema = dbSchema; + + } + + + + /** + * Sets the DB Vendor for the Entity Bean mapping + * + * @param dbvendor The new Dbvendor value + */ + + public void setDbvendor( DBVendor dbvendor ) + { + + this.dbVendor = dbvendor.getValue(); + + } + + + + /** + * Setter used to store the location of the Sun's Generic EJB DTD. This can + * be a file on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + + public void setEJBdtd( String inString ) + { + + this.ejb11DTD = inString; + + } + + + + /** + * Decide, wether ejbdeploy should be called or not + * + * @param ejbdeploy + */ + + public void setEjbdeploy( boolean ejbdeploy ) + { + + this.ejbdeploy = ejbdeploy; + + } + + + + /** + * Sets whether -keepgenerated is passed to ejbdeploy (that is, the .java + * source files are kept). + * + * @param inValue either 'true' or 'false' + */ + + public void setKeepgenerated( String inValue ) + { + + this.keepgenerated = Boolean.valueOf( inValue ).booleanValue(); + + } + + + + /** + * Setter used to store the value of keepGeneric + * + * @param inValue a string, either 'true' or 'false'. + */ + + public void setKeepgeneric( boolean inValue ) + { + + this.keepGeneric = inValue; + + } + + + + /** + * Set the value of the newCMP scheme. The old CMP scheme locates the + * websphere CMP descriptor based on the naming convention where the + * websphere CMP file is expected to be named with the bean name as the + * prefix. Under this scheme the name of the CMP descriptor does not match + * the name actually used in the main websphere EJB descriptor. Also, + * descriptors which contain multiple CMP references could not be used. + * + * @param newCMP The new NewCMP value + */ + + public void setNewCMP( boolean newCMP ) + { + + this.newCMP = newCMP; + + } + + + + /** + * (true) Disable informational messages + * + * @param noinfom The new Noinform value + */ + + public void setNoinform( boolean noinfom ) + { + + this.noinform = noinform; + + } + + + + /** + * (true) Disable the validation steps + * + * @param novalidate The new Novalidate value + */ + + public void setNovalidate( boolean novalidate ) + { + + this.novalidate = novalidate; + + } + + + + /** + * (true) Disable warning and informational messages + * + * @param nowarn The new Nowarn value + */ + + public void setNowarn( boolean nowarn ) + { + + this.nowarn = nowarn; + + } + + + + /** + * Set the value of the oldCMP scheme. This is an antonym for newCMP + * + * @param oldCMP The new OldCMP value + */ + + public void setOldCMP( boolean oldCMP ) + { + + this.newCMP = !oldCMP; + + } + + + + /** + * (true) Only output error messages, suppress informational messages + * + * @param quiet The new Quiet value + */ + + public void setQuiet( boolean quiet ) + { + + this.quiet = quiet; + + } + + + + /** + * Set the rebuild flag to false to only update changes in the jar rather + * than rerunning ejbdeploy + * + * @param rebuild The new Rebuild value + */ + + public void setRebuild( boolean rebuild ) + { + + this.alwaysRebuild = rebuild; + + } + + + + + + /** + * Setter used to store the suffix for the generated websphere jar file. + * + * @param inString the string to use as the suffix. + */ + + public void setSuffix( String inString ) + { + + this.jarSuffix = inString; + + } + + + + /** + * Sets the temporary directory for the ejbdeploy task + * + * @param tempdir The new Tempdir value + */ + + public void setTempdir( String tempdir ) + { + + this.tempdir = tempdir; + + } + + + + /** + * (true) Enable internal tracing + * + * @param trace The new Trace value + */ + + public void setTrace( boolean trace ) + { + + this.trace = trace; + + } + + + + /** + * (true) Use the WebSphere 3.5 compatible mapping rules + * + * @param attr The new Use35 value + */ + + public void setUse35( boolean attr ) + { + + use35MappingRules = attr; + + } + + + + public void setWASClasspath( Path wasClasspath ) + { + + this.wasClasspath = wasClasspath; + + } + + + + /** + * Get the classpath to the websphere classpaths + * + * @return Description of the Returned Value + */ + + public Path createWASClasspath() + { + + if( wasClasspath == null ) + { + + wasClasspath = new Path( getTask().getProject() ); + + } + + return wasClasspath.createPath(); + + } + + + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + + public void validateConfigured() + throws BuildException + { + + super.validateConfigured(); + + } + + + + /** + * Helper method invoked by isRebuildRequired to get a ClassLoader for a Jar + * File passed to it. + * + * @param classjar java.io.File representing jar file to get classes from. + * @return The ClassLoaderFromJar value + * @exception IOException Description of Exception + */ + + protected ClassLoader getClassLoaderFromJar( File classjar ) + throws IOException + { + + Path lookupPath = new Path( getTask().getProject() ); + + lookupPath.setLocation( classjar ); + + + + Path classpath = getCombinedClasspath(); + + if( classpath != null ) + { + + lookupPath.append( classpath ); + + } + + + + return new AntClassLoader( getTask().getProject(), lookupPath ); + + } + + + + protected DescriptorHandler getDescriptorHandler( File srcDir ) + { + + DescriptorHandler handler = new DescriptorHandler( getTask(), srcDir ); + + // register all the DTDs, both the ones that are known and + + + // any supplied by the user + + handler.registerDTD( PUBLICID_EJB11, ejb11DTD ); + + + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + + } + + + + return handler; + + } + + + + /** + * Gets the options for the EJB Deploy operation + * + * @return String + */ + + protected String getOptions() + { + + // Set the options + + + StringBuffer options = new StringBuffer(); + + if( dbVendor != null ) + { + + options.append( " -dbvendor " ).append( dbVendor ); + + } + + if( dbName != null ) + { + + options.append( " -dbname \"" ).append( dbName ).append( "\"" ); + + } + + + + if( dbSchema != null ) + { + + options.append( " -dbschema \"" ).append( dbSchema ).append( "\"" ); + + } + + + + if( codegen ) + { + + options.append( " -codegen" ); + + } + + + + if( quiet ) + { + + options.append( " -quiet" ); + + } + + + + if( novalidate ) + { + + options.append( " -novalidate" ); + + } + + + + if( nowarn ) + { + + options.append( " -nowarn" ); + + } + + + + if( noinform ) + { + + options.append( " -noinform" ); + + } + + + + if( trace ) + { + + options.append( " -trace" ); + + } + + + + if( use35MappingRules ) + { + + options.append( " -35" ); + + } + + + + if( rmicOptions != null ) + { + + options.append( " -rmic \"" ).append( rmicOptions ).append( "\"" ); + + } + + + + return options.toString(); + + } + + + + protected DescriptorHandler getWebsphereDescriptorHandler( final File srcDir ) + { + + DescriptorHandler handler = + new DescriptorHandler( getTask(), srcDir ) + { + + protected void processElement() { } + + }; + + + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + + } + + return handler; + + } + + + + + + /** + * Helper method to check to see if a websphere EBJ1.1 jar needs to be + * rebuilt using ejbdeploy. Called from writeJar it sees if the "Bean" + * classes are the only thing that needs to be updated and either updates + * the Jar with the Bean classfile or returns true, saying that the whole + * websphere jar needs to be regened with ejbdeploy. This allows faster + * build times for working developers.

      + * + * The way websphere ejbdeploy works is it creates wrappers for the publicly + * defined methods as they are exposed in the remote interface. If the + * actual bean changes without changing the the method signatures then only + * the bean classfile needs to be updated and the rest of the websphere jar + * file can remain the same. If the Interfaces, ie. the method signatures + * change or if the xml deployment dicriptors changed, the whole jar needs + * to be rebuilt with ejbdeploy. This is not strictly true for the xml + * files. If the JNDI name changes then the jar doesnt have to be rebuild, + * but if the resources references change then it does. At this point the + * websphere jar gets rebuilt if the xml files change at all. + * + * @param genericJarFile java.io.File The generic jar file. + * @param websphereJarFile java.io.File The websphere jar file to check to + * see if it needs to be rebuilt. + * @return The RebuildRequired value + */ + + protected boolean isRebuildRequired( File genericJarFile, File websphereJarFile ) + { + + boolean rebuild = false; + + + + JarFile genericJar = null; + + JarFile wasJar = null; + + File newwasJarFile = null; + + JarOutputStream newJarStream = null; + + + + try + { + + log( "Checking if websphere Jar needs to be rebuilt for jar " + websphereJarFile.getName(), + + Project.MSG_VERBOSE ); + + // Only go forward if the generic and the websphere file both exist + + + if( genericJarFile.exists() && genericJarFile.isFile() + + && websphereJarFile.exists() && websphereJarFile.isFile() ) + { + + //open jar files + + + genericJar = new JarFile( genericJarFile ); + + wasJar = new JarFile( websphereJarFile ); + + + + Hashtable genericEntries = new Hashtable(); + + Hashtable wasEntries = new Hashtable(); + + Hashtable replaceEntries = new Hashtable(); + + + + //get the list of generic jar entries + + for( Enumeration e = genericJar.entries(); e.hasMoreElements(); ) + { + + JarEntry je = ( JarEntry )e.nextElement(); + + genericEntries.put( je.getName().replace( '\\', '/' ), je ); + + } + + //get the list of websphere jar entries + + + for( Enumeration e = wasJar.entries(); e.hasMoreElements(); ) + { + + JarEntry je = ( JarEntry )e.nextElement(); + + wasEntries.put( je.getName(), je ); + + } + + + + //Cycle Through generic and make sure its in websphere + + ClassLoader genericLoader = getClassLoaderFromJar( genericJarFile ); + + for( Enumeration e = genericEntries.keys(); e.hasMoreElements(); ) + { + + String filepath = ( String )e.nextElement(); + + if( wasEntries.containsKey( filepath ) ) + {// File name/path match + + + // Check files see if same + + JarEntry genericEntry = ( JarEntry )genericEntries.get( filepath ); + + JarEntry wasEntry = ( JarEntry )wasEntries.get( filepath ); + + if( ( genericEntry.getCrc() != wasEntry.getCrc() ) || // Crc's Match + + ( genericEntry.getSize() != wasEntry.getSize() ) ) + {// Size Match + + + if( genericEntry.getName().endsWith( ".class" ) ) + { + + //File are different see if its an object or an interface + + + String classname = genericEntry.getName().replace( File.separatorChar, '.' ); + + classname = classname.substring( 0, classname.lastIndexOf( ".class" ) ); + + Class genclass = genericLoader.loadClass( classname ); + + if( genclass.isInterface() ) + { + + //Interface changed rebuild jar. + + + log( "Interface " + genclass.getName() + " has changed", Project.MSG_VERBOSE ); + + rebuild = true; + + break; + + } + + else + { + + //Object class Changed update it. + + + replaceEntries.put( filepath, genericEntry ); + + } + + } + + else + { + + // is it the manifest. If so ignore it + + + if( !genericEntry.getName().equals( "META-INF/MANIFEST.MF" ) ) + { + + //File other then class changed rebuild + + + log( "Non class file " + genericEntry.getName() + " has changed", Project.MSG_VERBOSE ); + + rebuild = true; + + } + + break; + + } + + } + + } + + else + {// a file doesnt exist rebuild + + + log( "File " + filepath + " not present in websphere jar", Project.MSG_VERBOSE ); + + rebuild = true; + + break; + + } + + } + + + + if( !rebuild ) + { + + log( "No rebuild needed - updating jar", Project.MSG_VERBOSE ); + + newwasJarFile = new File( websphereJarFile.getAbsolutePath() + ".temp" ); + + if( newwasJarFile.exists() ) + { + + newwasJarFile.delete(); + + } + + + + newJarStream = new JarOutputStream( new FileOutputStream( newwasJarFile ) ); + + newJarStream.setLevel( 0 ); + + + + //Copy files from old websphere jar + + for( Enumeration e = wasEntries.elements(); e.hasMoreElements(); ) + { + + byte[] buffer = new byte[1024]; + + int bytesRead; + + InputStream is; + + JarEntry je = ( JarEntry )e.nextElement(); + + if( je.getCompressedSize() == -1 || + + je.getCompressedSize() == je.getSize() ) + { + + newJarStream.setLevel( 0 ); + + } + + else + { + + newJarStream.setLevel( 9 ); + + } + + + + // Update with changed Bean class + + if( replaceEntries.containsKey( je.getName() ) ) + { + + log( "Updating Bean class from generic Jar " + je.getName(), + + Project.MSG_VERBOSE ); + + // Use the entry from the generic jar + + + je = ( JarEntry )replaceEntries.get( je.getName() ); + + is = genericJar.getInputStream( je ); + + } + + else + {//use fle from original websphere jar + + + is = wasJar.getInputStream( je ); + + } + + newJarStream.putNextEntry( new JarEntry( je.getName() ) ); + + + + while( ( bytesRead = is.read( buffer ) ) != -1 ) + { + + newJarStream.write( buffer, 0, bytesRead ); + + } + + is.close(); + + } + + } + + else + { + + log( "websphere Jar rebuild needed due to changed interface or XML", Project.MSG_VERBOSE ); + + } + + } + + else + { + + rebuild = true; + + } + + } + + catch( ClassNotFoundException cnfe ) + { + + String cnfmsg = "ClassNotFoundException while processing ejb-jar file" + + + ". Details: " + + + cnfe.getMessage(); + + throw new BuildException( cnfmsg, cnfe ); + + } + + catch( IOException ioe ) + { + + String msg = "IOException while processing ejb-jar file " + + + ". Details: " + + + ioe.getMessage(); + + throw new BuildException( msg, ioe ); + + } + + finally + { + + // need to close files and perhaps rename output + + + if( genericJar != null ) + { + + try + { + + genericJar.close(); + + } + + catch( IOException closeException ) + {} + + } + + + + if( wasJar != null ) + { + + try + { + + wasJar.close(); + + } + + catch( IOException closeException ) + {} + + } + + + + if( newJarStream != null ) + { + + try + { + + newJarStream.close(); + + } + + catch( IOException closeException ) + {} + + + + websphereJarFile.delete(); + + newwasJarFile.renameTo( websphereJarFile ); + + if( !websphereJarFile.exists() ) + { + + rebuild = true; + + } + + } + + } + + + + return rebuild; + + } + + + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param baseName The feature to be added to the VendorFiles attribute + */ + + protected void addVendorFiles( Hashtable ejbFiles, String baseName ) + { + + + + String ddPrefix = ( usingBaseJarName() ? "" : baseName ); + + String dbPrefix = ( dbVendor == null ) ? "" : dbVendor + "-"; + + + + // Get the Extensions document + + File websphereEXT = new File( getConfig().descriptorDir, ddPrefix + WAS_EXT ); + + if( websphereEXT.exists() ) + { + + ejbFiles.put( META_DIR + WAS_EXT, + + websphereEXT ); + + } + else + { + + log( "Unable to locate websphere extensions. It was expected to be in " + + + websphereEXT.getPath(), Project.MSG_VERBOSE ); + + } + + + + File websphereBND = new File( getConfig().descriptorDir, ddPrefix + WAS_BND ); + + if( websphereBND.exists() ) + { + + ejbFiles.put( META_DIR + WAS_BND, + + websphereBND ); + + } + else + { + + log( "Unable to locate websphere bindings. It was expected to be in " + + + websphereBND.getPath(), Project.MSG_VERBOSE ); + + } + + + + if( !newCMP ) + { + + log( "The old method for locating CMP files has been DEPRECATED.", Project.MSG_VERBOSE ); + + log( "Please adjust your websphere descriptor and set newCMP=\"true\" " + + + "to use the new CMP descriptor inclusion mechanism. ", Project.MSG_VERBOSE ); + + } + + else + { + + // We attempt to put in the MAP and Schema files of CMP beans + + + try + { + + // Add the Map file + + + File websphereMAP = new File( getConfig().descriptorDir, + + ddPrefix + dbPrefix + WAS_CMP_MAP ); + + if( websphereMAP.exists() ) + { + + ejbFiles.put( META_DIR + WAS_CMP_MAP, + + websphereMAP ); + + } + else + { + + log( "Unable to locate the websphere Map: " + + + websphereMAP.getPath(), Project.MSG_VERBOSE ); + + } + + File websphereSchema = new File( getConfig().descriptorDir, + + ddPrefix + dbPrefix + WAS_CMP_SCHEMA ); + + if( websphereSchema.exists() ) + { + + ejbFiles.put( META_DIR + SCHEMA_DIR + WAS_CMP_SCHEMA, + + websphereSchema ); + + } + else + { + + log( "Unable to locate the websphere Schema: " + + + websphereSchema.getPath(), Project.MSG_VERBOSE ); + + } + + // Theres nothing else to see here...keep moving sonny + + + } + + catch( Exception e ) + { + + String msg = "Exception while adding Vendor specific files: " + + + e.toString(); + + throw new BuildException( msg, e ); + + } + + } + + } + + + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarFile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + + protected void writeJar( String baseName, File jarFile, Hashtable files, String publicId ) + + throws BuildException + { + + if( ejbdeploy ) + { + + // create the -generic.jar, if required + + + File genericJarFile = super.getVendorOutputJarFile( baseName ); + + super.writeJar( baseName, genericJarFile, files, publicId ); + + + + // create the output .jar, if required + + if( alwaysRebuild || isRebuildRequired( genericJarFile, jarFile ) ) + { + + buildWebsphereJar( genericJarFile, jarFile ); + + } + + if( !keepGeneric ) + { + + log( "deleting generic jar " + genericJarFile.toString(), + + Project.MSG_VERBOSE ); + + genericJarFile.delete(); + + } + + } + + else + { + + // create the "undeployed" output .jar, if required + + + super.writeJar( baseName, jarFile, files, publicId ); + + } + + /* + * / need to create a generic jar first. + * File genericJarFile = super.getVendorOutputJarFile(baseName); + * super.writeJar(baseName, genericJarFile, files, publicId); + * if (alwaysRebuild || isRebuildRequired(genericJarFile, jarFile)) { + * buildWebsphereJar(genericJarFile, jarFile); + * } + * if (!keepGeneric) { + * log("deleting generic jar " + genericJarFile.toString(), + * Project.MSG_VERBOSE); + * genericJarFile.delete(); + * } + */ + + } + + + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + + File getVendorOutputJarFile( String baseName ) + { + + return new File( getDestDir(), baseName + jarSuffix ); + + }// end getOptions + + + + /** + * Helper method invoked by execute() for each websphere jar to be built. + * Encapsulates the logic of constructing a java task for calling + * websphere.ejbdeploy and executing it. + * + * @param sourceJar java.io.File representing the source (EJB1.1) jarfile. + * @param destJar java.io.File representing the destination, websphere + * jarfile. + */ + + private void buildWebsphereJar( File sourceJar, File destJar ) + { + + try + { + + if( ejbdeploy ) + { + + String args = + + " " + sourceJar.getPath() + + + " " + tempdir + + + " " + destJar.getPath() + + + " " + getOptions(); + + + + if( getCombinedClasspath() != null && getCombinedClasspath().toString().length() > 0 ) + + args += " -cp " + getCombinedClasspath(); + + + + // Why do my ""'s get stripped away??? + + log( "EJB Deploy Options: " + args, Project.MSG_VERBOSE ); + + + + Java javaTask = ( Java )getTask().getProject().createTask( "java" ); + + // Set the JvmArgs + + + javaTask.createJvmarg().setValue( "-Xms64m" ); + + javaTask.createJvmarg().setValue( "-Xmx128m" ); + + + + // Set the Environment variable + + Environment.Variable var = new Environment.Variable(); + + var.setKey( "websphere.lib.dir" ); + + var.setValue( getTask().getProject().getProperty( "websphere.home" ) + "/lib" ); + + javaTask.addSysproperty( var ); + + + + // Set the working directory + + javaTask.setDir( new File( getTask().getProject().getProperty( "websphere.home" ) ) ); + + + + // Set the Java class name + + javaTask.setTaskName( "ejbdeploy" ); + + javaTask.setClassname( "com.ibm.etools.ejbdeploy.EJBDeploy" ); + + + + Commandline.Argument arguments = javaTask.createArg(); + + arguments.setLine( args ); + + + + Path classpath = wasClasspath; + + if( classpath == null ) + { + + classpath = getCombinedClasspath(); + + } + + + + if( classpath != null ) + { + + javaTask.setClasspath( classpath ); + + javaTask.setFork( true ); + + } + + else + { + + javaTask.setFork( true ); + + } + + + + log( "Calling websphere.ejbdeploy for " + sourceJar.toString(), + + Project.MSG_VERBOSE ); + + + + javaTask.execute(); + + } + + } + + catch( Exception e ) + { + + // Have to catch this because of the semantics of calling main() + + + String msg = "Exception while calling ejbdeploy. Details: " + e.toString(); + + throw new BuildException( msg, e ); + + } + + } + + + /** + * Enumerated attribute with the values for the database vendor types + * + * @author RT + */ + + public static class DBVendor extends EnumeratedAttribute + { + + public String[] getValues() + { + + return new String[]{ + + "SQL92", "SQL99", "DB2UDBWIN_V71", "DB2UDBOS390_V6", "DB2UDBAS400_V4R5", + + "ORACLE_V8", "INFORMIX_V92", "SYBASE_V1192", "MSSQLSERVER_V7", "MYSQL_V323" + + }; + + } + + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java new file mode 100644 index 000000000..ddd1d9e6e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java @@ -0,0 +1,645 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.i18n; +import java.io.*; +import java.util.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.*; +import org.apache.tools.ant.util.*; + +/** + * Translates text embedded in files using Resource Bundle files. + * + * @author Magesh Umasankar + */ +public class Translate extends MatchingTask +{ + /** + * Vector to hold source file sets. + */ + private Vector filesets = new Vector(); + /** + * Holds key value pairs loaded from resource bundle file + */ + private Hashtable resourceMap = new Hashtable(); + /** + * Used to resolve file names. + */ + private FileUtils fileUtils = FileUtils.newFileUtils(); + /** + * Last Modified Timestamp of resource bundle file being used. + */ + private long[] bundleLastModified = new long[7]; + /** + * Has at least one file from the bundle been loaded? + */ + private boolean loaded = false; + + /** + * Family name of resource bundle + */ + private String bundle; + /** + * Locale specific country of the resource bundle + */ + private String bundleCountry; + /** + * Resource Bundle file encoding scheme, defaults to srcEncoding + */ + private String bundleEncoding; + /** + * Locale specific language of the resource bundle + */ + private String bundleLanguage; + /** + * Locale specific variant of the resource bundle + */ + private String bundleVariant; + /** + * Destination file encoding scheme + */ + private String destEncoding; + /** + * Last Modified Timestamp of destination file being used. + */ + private long destLastModified; + /** + * Ending token to identify keys + */ + private String endToken; + /** + * Create new destination file? Defaults to false. + */ + private boolean forceOverwrite; + /** + * Generated locale based on user attributes + */ + private Locale locale; + /** + * Source file encoding scheme + */ + private String srcEncoding; + /** + * Last Modified Timestamp of source file being used. + */ + private long srcLastModified; + /** + * Starting token to identify keys + */ + private String startToken; + /** + * Destination directory + */ + private File toDir; + + /** + * Sets Family name of resource bundle + * + * @param bundle The new Bundle value + */ + public void setBundle( String bundle ) + { + this.bundle = bundle; + } + + /** + * Sets locale specific country of resource bundle + * + * @param bundleCountry The new BundleCountry value + */ + public void setBundleCountry( String bundleCountry ) + { + this.bundleCountry = bundleCountry; + } + + /** + * Sets Resource Bundle file encoding scheme + * + * @param bundleEncoding The new BundleEncoding value + */ + public void setBundleEncoding( String bundleEncoding ) + { + this.bundleEncoding = bundleEncoding; + } + + /** + * Sets locale specific language of resource bundle + * + * @param bundleLanguage The new BundleLanguage value + */ + public void setBundleLanguage( String bundleLanguage ) + { + this.bundleLanguage = bundleLanguage; + } + + /** + * Sets locale specific variant of resource bundle + * + * @param bundleVariant The new BundleVariant value + */ + public void setBundleVariant( String bundleVariant ) + { + this.bundleVariant = bundleVariant; + } + + /** + * Sets destination file encoding scheme. Defaults to source file encoding + * + * @param destEncoding The new DestEncoding value + */ + public void setDestEncoding( String destEncoding ) + { + this.destEncoding = destEncoding; + } + + /** + * Sets ending token to identify keys + * + * @param endToken The new EndToken value + */ + public void setEndToken( String endToken ) + { + this.endToken = endToken; + } + + /** + * Overwrite existing file irrespective of whether it is newer than the + * source file as well as the resource bundle file? Defaults to false. + * + * @param forceOverwrite The new ForceOverwrite value + */ + public void setForceOverwrite( boolean forceOverwrite ) + { + this.forceOverwrite = forceOverwrite; + } + + /** + * Sets source file encoding scheme + * + * @param srcEncoding The new SrcEncoding value + */ + public void setSrcEncoding( String srcEncoding ) + { + this.srcEncoding = srcEncoding; + } + + /** + * Sets starting token to identify keys + * + * @param startToken The new StartToken value + */ + public void setStartToken( String startToken ) + { + this.startToken = startToken; + } + + /** + * Sets Destination directory + * + * @param toDir The new ToDir value + */ + public void setToDir( File toDir ) + { + this.toDir = toDir; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Check attributes values, load resource map and translate + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( bundle == null ) + { + throw new BuildException( "The bundle attribute must be set.", + location ); + } + + if( startToken == null ) + { + throw new BuildException( "The starttoken attribute must be set.", + location ); + } + + if( startToken.length() != 1 ) + { + throw new BuildException( + "The starttoken attribute must be a single character.", + location ); + } + + if( endToken == null ) + { + throw new BuildException( "The endtoken attribute must be set.", + location ); + } + + if( endToken.length() != 1 ) + { + throw new BuildException( + "The endtoken attribute must be a single character.", + location ); + } + + if( bundleLanguage == null ) + { + Locale l = Locale.getDefault(); + bundleLanguage = l.getLanguage(); + } + + if( bundleCountry == null ) + { + bundleCountry = Locale.getDefault().getCountry(); + } + + locale = new Locale( bundleLanguage, bundleCountry ); + + if( bundleVariant == null ) + { + Locale l = new Locale( bundleLanguage, bundleCountry ); + bundleVariant = l.getVariant(); + } + + if( toDir == null ) + { + throw new BuildException( "The todir attribute must be set.", + location ); + } + + if( !toDir.exists() ) + { + toDir.mkdirs(); + } + else + { + if( toDir.isFile() ) + { + throw new BuildException( toDir + " is not a directory" ); + } + } + + if( srcEncoding == null ) + { + srcEncoding = System.getProperty( "file.encoding" ); + } + + if( destEncoding == null ) + { + destEncoding = srcEncoding; + } + + if( bundleEncoding == null ) + { + bundleEncoding = srcEncoding; + } + + loadResourceMaps(); + + translate(); + } + + /** + * Load resourceMap with key value pairs. Values of existing keys are not + * overwritten. Bundle's encoding scheme is used. + * + * @param ins Description of Parameter + * @exception BuildException Description of Exception + */ + private void loadResourceMap( FileInputStream ins ) + throws BuildException + { + try + { + BufferedReader in = null; + InputStreamReader isr = new InputStreamReader( ins, bundleEncoding ); + in = new BufferedReader( isr ); + String line = null; + while( ( line = in.readLine() ) != null ) + { + //So long as the line isn't empty and isn't a comment... + if( line.trim().length() > 1 && + ( '#' != line.charAt( 0 ) || '!' != line.charAt( 0 ) ) ) + { + //Legal Key-Value separators are :, = and white space. + int sepIndex = line.indexOf( '=' ); + if( -1 == sepIndex ) + { + sepIndex = line.indexOf( ':' ); + } + if( -1 == sepIndex ) + { + for( int k = 0; k < line.length(); k++ ) + { + if( Character.isSpaceChar( line.charAt( k ) ) ) + { + sepIndex = k; + break; + } + } + } + //Only if we do have a key is there going to be a value + if( -1 != sepIndex ) + { + String key = line.substring( 0, sepIndex ).trim(); + String value = line.substring( sepIndex + 1 ).trim(); + //Handle line continuations, if any + while( value.endsWith( "\\" ) ) + { + value = value.substring( 0, value.length() - 1 ); + if( ( line = in.readLine() ) != null ) + { + value = value + line.trim(); + } + else + { + break; + } + } + if( key.length() > 0 ) + { + //Has key already been loaded into resourceMap? + if( resourceMap.get( key ) == null ) + { + resourceMap.put( key, value ); + } + } + } + } + } + if( in != null ) + { + in.close(); + } + } + catch( IOException ioe ) + { + throw new BuildException( ioe.getMessage(), location ); + } + } + + /** + * Load resource maps based on resource bundle encoding scheme. The resource + * bundle lookup searches for resource files with various suffixes on the + * basis of (1) the desired locale and (2) the default locale + * (basebundlename), in the following order from lower-level (more specific) + * to parent-level (less specific): basebundlename + "_" + language1 + "_" + + * country1 + "_" + variant1 basebundlename + "_" + language1 + "_" + + * country1 basebundlename + "_" + language1 basebundlename basebundlename + + * "_" + language2 + "_" + country2 + "_" + variant2 basebundlename + "_" + + * language2 + "_" + country2 basebundlename + "_" + language2 To the + * generated name, a ".properties" string is appeneded and once this file is + * located, it is treated just like a properties file but with bundle + * encoding also considered while loading. + * + * @exception BuildException Description of Exception + */ + private void loadResourceMaps() + throws BuildException + { + Locale locale = new Locale( bundleLanguage, + bundleCountry, + bundleVariant ); + String language = locale.getLanguage().length() > 0 ? + "_" + locale.getLanguage() : + ""; + String country = locale.getCountry().length() > 0 ? + "_" + locale.getCountry() : + ""; + String variant = locale.getVariant().length() > 0 ? + "_" + locale.getVariant() : + ""; + String bundleFile = bundle + language + country + variant; + processBundle( bundleFile, 0, false ); + + bundleFile = bundle + language + country; + processBundle( bundleFile, 1, false ); + + bundleFile = bundle + language; + processBundle( bundleFile, 2, false ); + + bundleFile = bundle; + processBundle( bundleFile, 3, false ); + + //Load default locale bundle files + //using default file encoding scheme. + locale = Locale.getDefault(); + + language = locale.getLanguage().length() > 0 ? + "_" + locale.getLanguage() : + ""; + country = locale.getCountry().length() > 0 ? + "_" + locale.getCountry() : + ""; + variant = locale.getVariant().length() > 0 ? + "_" + locale.getVariant() : + ""; + bundleEncoding = System.getProperty( "file.encoding" ); + + bundleFile = bundle + language + country + variant; + processBundle( bundleFile, 4, false ); + + bundleFile = bundle + language + country; + processBundle( bundleFile, 5, false ); + + bundleFile = bundle + language; + processBundle( bundleFile, 6, true ); + } + + /** + * Process each file that makes up this bundle. + * + * @param bundleFile Description of Parameter + * @param i Description of Parameter + * @param checkLoaded Description of Parameter + * @exception BuildException Description of Exception + */ + private void processBundle( String bundleFile, int i, + boolean checkLoaded ) + throws BuildException + { + bundleFile += ".properties"; + FileInputStream ins = null; + try + { + ins = new FileInputStream( bundleFile ); + loaded = true; + bundleLastModified[i] = new File( bundleFile ).lastModified(); + log( "Using " + bundleFile, Project.MSG_DEBUG ); + loadResourceMap( ins ); + } + catch( IOException ioe ) + { + log( bundleFile + " not found.", Project.MSG_DEBUG ); + //if all resource files associated with this bundle + //have been scanned for and still not able to + //find a single resrouce file, throw exception + if( !loaded && checkLoaded ) + { + throw new BuildException( ioe.getMessage(), location ); + } + } + } + + /** + * Reads source file line by line using the source encoding and searches for + * keys that are sandwiched between the startToken and endToken. The values + * for these keys are looked up from the hashtable and substituted. If the + * hashtable doesn't contain the key, they key itself is used as the value. + * Detination files and directories are created as needed. The destination + * file is overwritten only if the forceoverwritten attribute is set to true + * if the source file or any associated bundle resource file is newer than + * the destination file. + * + * @exception BuildException Description of Exception + */ + private void translate() + throws BuildException + { + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] srcFiles = ds.getIncludedFiles(); + for( int j = 0; j < srcFiles.length; j++ ) + { + try + { + File dest = fileUtils.resolveFile( toDir, srcFiles[j] ); + //Make sure parent dirs exist, else, create them. + try + { + File destDir = new File( dest.getParent() ); + if( !destDir.exists() ) + { + destDir.mkdirs(); + } + } + catch( Exception e ) + { + log( "Exception occured while trying to check/create " + + " parent directory. " + e.getMessage(), + Project.MSG_DEBUG ); + } + destLastModified = dest.lastModified(); + srcLastModified = new File( srcFiles[i] ).lastModified(); + //Check to see if dest file has to be recreated + if( forceOverwrite + || destLastModified < srcLastModified + || destLastModified < bundleLastModified[0] + || destLastModified < bundleLastModified[1] + || destLastModified < bundleLastModified[2] + || destLastModified < bundleLastModified[3] + || destLastModified < bundleLastModified[4] + || destLastModified < bundleLastModified[5] + || destLastModified < bundleLastModified[6] ) + { + log( "Processing " + srcFiles[j], + Project.MSG_DEBUG ); + FileOutputStream fos = new FileOutputStream( dest ); + BufferedWriter out = new BufferedWriter( + new OutputStreamWriter( fos, + destEncoding ) ); + FileInputStream fis = new FileInputStream( srcFiles[j] ); + BufferedReader in = new BufferedReader( + new InputStreamReader( fis, + srcEncoding ) ); + String line; + while( ( line = in.readLine() ) != null ) + { + StringBuffer newline = new StringBuffer( line ); + int startIndex = -1; + int endIndex = -1; + outer : + while( true ) + { + startIndex = line.indexOf( startToken, endIndex + 1 ); + if( startIndex < 0 || + startIndex + 1 >= line.length() ) + { + break; + } + endIndex = line.indexOf( endToken, startIndex + 1 ); + if( endIndex < 0 ) + { + break; + } + String matches = line.substring( startIndex + 1, + endIndex ); + //If there is a white space or = or :, then + //it isn't to be treated as a valid key. + for( int k = 0; k < matches.length(); k++ ) + { + char c = matches.charAt( k ); + if( c == ':' || + c == '=' || + Character.isSpaceChar( c ) ) + { + endIndex = endIndex - 1; + continue outer; + } + } + String replace = null; + replace = ( String )resourceMap.get( matches ); + //If the key hasn't been loaded into resourceMap, + //use the key itself as the value also. + if( replace == null ) + { + log( "Warning: The key: " + matches + + " hasn't been defined.", + Project.MSG_DEBUG ); + replace = matches; + } + line = line.substring( 0, startIndex ) + + replace + + line.substring( endIndex + 1 ); + endIndex = startIndex + replace.length() + 1; + if( endIndex + 1 >= line.length() ) + { + break; + } + } + out.write( line ); + out.newLine(); + } + if( in != null ) + { + in.close(); + } + if( out != null ) + { + out.close(); + } + } + else + { + log( "Skipping " + srcFiles[j] + + " as destination file is up to date", + Project.MSG_VERBOSE ); + } + } + catch( IOException ioe ) + { + throw new BuildException( ioe.getMessage(), location ); + } + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java new file mode 100644 index 000000000..1526cd393 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import com.ibm.ivj.util.base.Project; +import com.ibm.ivj.util.base.ToolData; +import org.apache.tools.ant.BuildException; + + +/** + * This class is the equivalent to org.apache.tools.ant.Main for the VAJ tool + * environment. It's main is called when the user selects Tools->Ant Build from + * the VAJ project menu. Additionally this class provides methods to save build + * info for a project in the repository and load it from the repository + * + * @author RT + * @author: Wolf Siberski + */ +public class VAJAntTool +{ + private final static String TOOL_DATA_KEY = "AntTool"; + + + /** + * Loads the BuildInfo for the specified VAJ project from the tool data for + * this project. If there is no build info stored for that project, a new + * default BuildInfo is returned + * + * @param projectName String project name + * @return BuildInfo buildInfo build info for the specified project + */ + public static VAJBuildInfo loadBuildData( String projectName ) + { + VAJBuildInfo result = null; + try + { + Project project = + VAJLocalUtil.getWorkspace().loadedProjectNamed( projectName ); + if( project.testToolRepositoryData( TOOL_DATA_KEY ) ) + { + ToolData td = project.getToolRepositoryData( TOOL_DATA_KEY ); + String data = ( String )td.getData(); + result = VAJBuildInfo.parse( data ); + } + else + { + result = new VAJBuildInfo(); + } + result.setVAJProjectName( projectName ); + } + catch( Throwable t ) + { + throw new BuildException( "BuildInfo for Project " + + projectName + " could not be loaded" + t ); + } + return result; + } + + + /** + * Starts the application. + * + * @param args an array of command-line arguments. VAJ puts the VAJ project + * name into args[1] when starting the tool from the project context + * menu + */ + public static void main( java.lang.String[] args ) + { + try + { + VAJBuildInfo info; + if( args.length >= 2 && args[1] instanceof String ) + { + String projectName = ( String )args[1]; + info = loadBuildData( projectName ); + } + else + { + info = new VAJBuildInfo(); + } + + VAJAntToolGUI mainFrame = new VAJAntToolGUI( info ); + mainFrame.show(); + } + catch( Throwable t ) + { + // if all error handling fails, output at least + // something on the console + t.printStackTrace(); + } + } + + + /** + * Saves the BuildInfo for a project in the VAJ repository. + * + * @param info BuildInfo build info to save + */ + public static void saveBuildData( VAJBuildInfo info ) + { + String data = info.asDataString(); + try + { + ToolData td = new ToolData( TOOL_DATA_KEY, data ); + VAJLocalUtil.getWorkspace().loadedProjectNamed( + info.getVAJProjectName() ).setToolRepositoryData( td ); + } + catch( Throwable t ) + { + throw new BuildException( "BuildInfo for Project " + + info.getVAJProjectName() + " could not be saved", t ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java new file mode 100644 index 000000000..7aa8916e0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java @@ -0,0 +1,1803 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.awt.BorderLayout; +import java.awt.Button; +import java.awt.Choice; +import java.awt.Dialog; +import java.awt.FileDialog; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Label; +import java.awt.List; +import java.awt.Menu; +import java.awt.MenuBar; +import java.awt.MenuItem; +import java.awt.Panel; +import java.awt.SystemColor; +import java.awt.TextArea; +import java.awt.TextField; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.TextEvent; +import java.awt.event.TextListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.beans.PropertyChangeListener; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Vector; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.StringUtils; + +/** + * This is a simple grafical user interface to provide the information needed by + * ANT and to start the build-process within IBM VisualAge for Java.

      + * + * I was using AWT to make it independent from the JDK-version. Please don't ask + * me for a Swing-version:I am very familiar with Swing and I really think that + * it's not necessary for such a simple gui!

      + * + * It is completely developed in VAJ using the visual composition editor. About + * 90% of the code is generated by VAJ, but in fact I did a lot of + * code-beautification ;-).

      + * + * + * + * @author RT + * @version 1.0 h + * @author: Christoph Wilhelms, TUI Infotec GmbH + */ +public class VAJAntToolGUI extends Frame +{ + private final static String lineSeparator = "\r\n"; + /** + * Members + */ + private VAJBuildLogger logger = new VAJBuildLogger(); + private PrivateEventHandler iEventHandler = new PrivateEventHandler(); + + /** + * Members of the main-window + */ + // main model + private VAJBuildInfo iBuildInfo = null; + // Menue + private MenuBar iAntMakeMenuBar = null; + private Menu iFileMenu = null; + private MenuItem iSaveMenuItem = null; + private MenuItem iMenuSeparator = null; + private MenuItem iShowLogMenuItem = null; + private Menu iHelpMenu = null; + private MenuItem iAboutMenuItem = null; + // Container + private Panel iContentsPane = null; + private Panel iOptionenPanel = null; + private Panel iCommandButtonPanel = null; + private FlowLayout iCommandButtonPanelFlowLayout = null; + // Project name + private Label iProjectLabel = null; + private Label iProjectText = null; + // XML-file + private Label iBuildFileLabel = null; + private TextField iBuildFileTextField = null; + private boolean iConnPtoP2Aligning = false; + private Button iBrowseButton = null; + private FileDialog iFileDialog = null; + // Options + private Choice iMessageOutputLevelChoice = null; + private Label iMessageOutputLevelLabel = null; + private Label iTargetLabel = null; + private List iTargetList = null; + // Command-buttons + private Button iBuildButton = null; + private Button iReloadButton = null; + private Button iCloseButton = null; + /** + * log-Window + */ + // Container + private Frame iMessageFrame = null; + private Panel iMessageCommandPanel = null; + private Panel iMessageContentPanel = null; + // Components + private TextArea iMessageTextArea = null; + private Button iMessageOkButton = null; + private Button iMessageClearLogButton = null; + /** + * About-dialog + */ + // Container + private Dialog iAboutDialog = null; + private Panel iAboutDialogContentPanel = null; + private Panel iAboutInfoPanel = null; + private Panel iAboutCommandPanel = null; + // Labels + private Label iAboutTitleLabel = null; + private Label iAboutDevLabel = null; + private Label iAboutContactLabel = null; + // Buttons + private Button iAboutOkButton = null; + + private Button iStopButton = null; + + /** + * AntMake constructor called by VAJAntTool integration. + * + * @param newBuildInfo Description of Parameter + */ + + public VAJAntToolGUI( VAJBuildInfo newBuildInfo ) + { + super(); + setBuildInfo( newBuildInfo ); + initialize(); + } + + /** + * AntMake default-constructor. + */ + private VAJAntToolGUI() + { + super(); + initialize(); + } + + /** + * This method is used to center dialogs. + * + * @param dialog Description of Parameter + */ + public static void centerDialog( Dialog dialog ) + { + dialog.setLocation( ( Toolkit.getDefaultToolkit().getScreenSize().width / 2 ) - ( dialog.getSize().width / 2 ), ( java.awt.Toolkit.getDefaultToolkit().getScreenSize().height / 2 ) - ( dialog.getSize().height / 2 ) ); + } + + /** + * Copied from DefaultLogger to provide the same time-format. + * + * @param millis Description of Parameter + * @return Description of the Returned Value + */ + public static String formatTime( long millis ) + { + long seconds = millis / 1000; + long minutes = seconds / 60; + + if( minutes > 0 ) + { + return Long.toString( minutes ) + " minute" + + ( minutes == 1 ? " " : "s " ) + + Long.toString( seconds % 60 ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + else + { + return Long.toString( seconds ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + } + + /** + * Set the BuildInfo to a new value. + * + * @param newValue org.apache.tools.ant.taskdefs.optional.vaj.VAJBuildInfo + */ + private void setBuildInfo( VAJBuildInfo newValue ) + { + if( iBuildInfo != newValue ) + { + try + { + /* + * Stop listening for events from the current object + */ + if( iBuildInfo != null ) + { + iBuildInfo.removePropertyChangeListener( iEventHandler ); + } + iBuildInfo = newValue; + + /* + * Listen for events from the new object + */ + if( iBuildInfo != null ) + { + iBuildInfo.addPropertyChangeListener( iEventHandler ); + } + connectProjectNameToLabel(); + connectBuildFileNameToTextField(); + + // Select the log-level given by BuildInfo + getMessageOutputLevelChoice().select( iBuildInfo.getOutputMessageLevel() ); + fillList(); + // BuildInfo can conly be saved to a VAJ project if tool API is called via the projects context-menu + if( ( iBuildInfo.getVAJProjectName() == null ) || ( iBuildInfo.getVAJProjectName().equals( "" ) ) ) + { + getSaveMenuItem().setEnabled( false ); + } + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + } + + /** + * Return the AboutCommandPanel property value. + * + * @return java.awt.Panel + */ + private Panel getAboutCommandPanel() + { + if( iAboutCommandPanel == null ) + { + try + { + iAboutCommandPanel = new Panel(); + iAboutCommandPanel.setName( "AboutCommandPanel" ); + iAboutCommandPanel.setLayout( new java.awt.FlowLayout() ); + getAboutCommandPanel().add( getAboutOkButton(), getAboutOkButton().getName() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutCommandPanel; + } + + /** + * Return the AboutContactLabel property value. + * + * @return java.awt.Label + */ + private Label getAboutContactLabel() + { + if( iAboutContactLabel == null ) + { + try + { + iAboutContactLabel = new Label(); + iAboutContactLabel.setName( "AboutContactLabel" ); + iAboutContactLabel.setAlignment( java.awt.Label.CENTER ); + iAboutContactLabel.setText( "contact: wolf.siberski@tui.de or christoph.wilhelms@tui.de" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutContactLabel; + } + + /** + * Return the AboutDevLabel property value. + * + * @return java.awt.Label + */ + private Label getAboutDevLabel() + { + if( iAboutDevLabel == null ) + { + try + { + iAboutDevLabel = new Label(); + iAboutDevLabel.setName( "AboutDevLabel" ); + iAboutDevLabel.setAlignment( java.awt.Label.CENTER ); + iAboutDevLabel.setText( "developed by Wolf Siberski & Christoph Wilhelms" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutDevLabel; + } + + /** + * Return the AboutDialog property value. + * + * @return java.awt.Dialog + */ + private Dialog getAboutDialog() + { + if( iAboutDialog == null ) + { + try + { + iAboutDialog = new Dialog( this ); + iAboutDialog.setName( "AboutDialog" ); + iAboutDialog.setResizable( false ); + iAboutDialog.setLayout( new java.awt.BorderLayout() ); + iAboutDialog.setBounds( 550, 14, 383, 142 ); + iAboutDialog.setModal( true ); + iAboutDialog.setTitle( "About..." ); + getAboutDialog().add( getAboutDialogContentPanel(), "Center" ); + iAboutDialog.pack(); + centerDialog( iAboutDialog ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutDialog; + } + + /** + * Return the AboutDialogContentPanel property value. + * + * @return java.awt.Panel + */ + private Panel getAboutDialogContentPanel() + { + if( iAboutDialogContentPanel == null ) + { + try + { + iAboutDialogContentPanel = new Panel(); + iAboutDialogContentPanel.setName( "AboutDialogContentPanel" ); + iAboutDialogContentPanel.setLayout( new java.awt.BorderLayout() ); + getAboutDialogContentPanel().add( getAboutCommandPanel(), "South" ); + getAboutDialogContentPanel().add( getAboutInfoPanel(), "Center" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutDialogContentPanel; + } + + /** + * Return the AboutInfoPanel property value. + * + * @return java.awt.Panel + */ + private Panel getAboutInfoPanel() + { + if( iAboutInfoPanel == null ) + { + try + { + iAboutInfoPanel = new Panel(); + iAboutInfoPanel.setName( "AboutInfoPanel" ); + iAboutInfoPanel.setLayout( new GridBagLayout() ); + + GridBagConstraints constraintsAboutTitleLabel = new GridBagConstraints(); + constraintsAboutTitleLabel.gridx = 0; + constraintsAboutTitleLabel.gridy = 0; + constraintsAboutTitleLabel.fill = GridBagConstraints.HORIZONTAL; + constraintsAboutTitleLabel.weightx = 1.0; + constraintsAboutTitleLabel.weighty = 1.0; + constraintsAboutTitleLabel.insets = new Insets( 4, 0, 4, 0 ); + getAboutInfoPanel().add( getAboutTitleLabel(), constraintsAboutTitleLabel ); + + GridBagConstraints constraintsAboutDevLabel = new GridBagConstraints(); + constraintsAboutDevLabel.gridx = 0; + constraintsAboutDevLabel.gridy = 1; + constraintsAboutDevLabel.fill = GridBagConstraints.HORIZONTAL; + constraintsAboutDevLabel.weightx = 1.0; + constraintsAboutDevLabel.insets = new Insets( 4, 0, 0, 0 ); + getAboutInfoPanel().add( getAboutDevLabel(), constraintsAboutDevLabel ); + + GridBagConstraints constraintsAboutContactLabel = new GridBagConstraints(); + constraintsAboutContactLabel.gridx = 0; + constraintsAboutContactLabel.gridy = 2; + constraintsAboutContactLabel.fill = GridBagConstraints.HORIZONTAL; + constraintsAboutContactLabel.weightx = 1.0; + constraintsAboutContactLabel.insets = new Insets( 2, 0, 4, 0 ); + getAboutInfoPanel().add( getAboutContactLabel(), constraintsAboutContactLabel ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutInfoPanel; + } + + /** + * Return the AboutMenuItem property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getAboutMenuItem() + { + if( iAboutMenuItem == null ) + { + try + { + iAboutMenuItem = new MenuItem(); + iAboutMenuItem.setLabel( "About..." ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutMenuItem; + } + + /** + * Return the AboutOkButton property value. + * + * @return java.awt.Button + */ + private Button getAboutOkButton() + { + if( iAboutOkButton == null ) + { + try + { + iAboutOkButton = new Button(); + iAboutOkButton.setName( "AboutOkButton" ); + iAboutOkButton.setLabel( "OK" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutOkButton; + } + + /** + * Return the AboutTitleLabel property value. + * + * @return java.awt.Label + */ + private Label getAboutTitleLabel() + { + if( iAboutTitleLabel == null ) + { + try + { + iAboutTitleLabel = new Label(); + iAboutTitleLabel.setName( "AboutTitleLabel" ); + iAboutTitleLabel.setFont( new Font( "Arial", 1, 12 ) ); + iAboutTitleLabel.setAlignment( Label.CENTER ); + iAboutTitleLabel.setText( "Ant VisualAge for Java Tool-Integration" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutTitleLabel; + } + + /** + * Return the AntMakeMenuBar property value. + * + * @return java.awt.MenuBar + */ + private MenuBar getAntMakeMenuBar() + { + if( iAntMakeMenuBar == null ) + { + try + { + iAntMakeMenuBar = new MenuBar(); + iAntMakeMenuBar.add( getFileMenu() ); + iAntMakeMenuBar.add( getHelpMenu() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAntMakeMenuBar; + } + + /** + * Return the BrowseButton property value. + * + * @return Button + */ + private Button getBrowseButton() + { + if( iBrowseButton == null ) + { + try + { + iBrowseButton = new Button(); + iBrowseButton.setName( "BrowseButton" ); + iBrowseButton.setLabel( "..." ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBrowseButton; + } + + /** + * Return the BuildButton property value. + * + * @return java.awt.Button + */ + private Button getBuildButton() + { + if( iBuildButton == null ) + { + try + { + iBuildButton = new Button(); + iBuildButton.setName( "BuildButton" ); + iBuildButton.setLabel( "Execute" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBuildButton; + } + + /** + * Return the BuildFileLabel property value. + * + * @return java.awt.Label + */ + private Label getBuildFileLabel() + { + if( iBuildFileLabel == null ) + { + try + { + iBuildFileLabel = new Label(); + iBuildFileLabel.setName( "BuildFileLabel" ); + iBuildFileLabel.setText( "Ant-Buildfile:" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBuildFileLabel; + } + + /** + * Return the BuildFileTextField property value. + * + * @return java.awt.TextField + */ + private TextField getBuildFileTextField() + { + if( iBuildFileTextField == null ) + { + try + { + iBuildFileTextField = new TextField(); + iBuildFileTextField.setName( "BuildFileTextField" ); + iBuildFileTextField.setBackground( SystemColor.textHighlightText ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBuildFileTextField; + } + + /** + * Return the BuildInfo property value. + * + * @return org.apache.tools.ant.taskdefs.optional.ide.VAJBuildInfo + */ + private VAJBuildInfo getBuildInfo() + { + return iBuildInfo; + } + + /** + * Return the CloseButton property value. + * + * @return java.awt.Button + */ + private Button getCloseButton() + { + if( iCloseButton == null ) + { + try + { + iCloseButton = new Button(); + iCloseButton.setName( "CloseButton" ); + iCloseButton.setLabel( "Close" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iCloseButton; + } + + /** + * Return the CommandButtonPanel property value. + * + * @return java.awt.Panel + */ + private Panel getCommandButtonPanel() + { + if( iCommandButtonPanel == null ) + { + try + { + iCommandButtonPanel = new Panel(); + iCommandButtonPanel.setName( "CommandButtonPanel" ); + iCommandButtonPanel.setLayout( getCommandButtonPanelFlowLayout() ); + iCommandButtonPanel.setBackground( SystemColor.control ); + iCommandButtonPanel.add( getReloadButton() ); + iCommandButtonPanel.add( getBuildButton() ); + iCommandButtonPanel.add( getStopButton() ); + iCommandButtonPanel.add( getCloseButton() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iCommandButtonPanel; + } + + /** + * Return the CommandButtonPanelFlowLayout property value. + * + * @return java.awt.FlowLayout + */ + private FlowLayout getCommandButtonPanelFlowLayout() + { + FlowLayout iCommandButtonPanelFlowLayout = null; + try + { + /* + * Create part + */ + iCommandButtonPanelFlowLayout = new FlowLayout(); + iCommandButtonPanelFlowLayout.setAlignment( FlowLayout.RIGHT ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + ; + return iCommandButtonPanelFlowLayout; + } + + /** + * Return the ContentsPane property value. + * + * @return java.awt.Panel + */ + private Panel getContentsPane() + { + if( iContentsPane == null ) + { + try + { + iContentsPane = new Panel(); + iContentsPane.setName( "ContentsPane" ); + iContentsPane.setLayout( new BorderLayout() ); + getContentsPane().add( getCommandButtonPanel(), "South" ); + getContentsPane().add( getOptionenPanel(), "Center" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iContentsPane; + } + + /** + * Return the FileDialog property value. + * + * @return java.awt.FileDialog + */ + private FileDialog getFileDialog() + { + if( iFileDialog == null ) + { + try + { + iFileDialog = new FileDialog( this ); + iFileDialog.setName( "FileDialog" ); + iFileDialog.setLayout( null ); + centerDialog( iFileDialog ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iFileDialog; + } + + /** + * Return the FileMenu property value. + * + * @return java.awt.Menu + */ + private Menu getFileMenu() + { + if( iFileMenu == null ) + { + try + { + iFileMenu = new Menu(); + iFileMenu.setLabel( "File" ); + iFileMenu.add( getSaveMenuItem() ); + iFileMenu.add( getMenuSeparator() ); + iFileMenu.add( getShowLogMenuItem() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iFileMenu; + } + + /** + * Return the HelpMenu property value. + * + * @return java.awt.Menu + */ + private Menu getHelpMenu() + { + if( iHelpMenu == null ) + { + try + { + iHelpMenu = new Menu(); + iHelpMenu.setLabel( "Help" ); + iHelpMenu.add( getAboutMenuItem() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iHelpMenu; + } + + /** + * Return the MenuSeparator1 property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getMenuSeparator() + { + if( iMenuSeparator == null ) + { + try + { + iMenuSeparator = new MenuItem(); + iMenuSeparator.setLabel( "-" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMenuSeparator; + } + + /** + * Return the MessageClearLogButton property value. + * + * @return java.awt.Button + */ + private Button getMessageClearLogButton() + { + if( iMessageClearLogButton == null ) + { + try + { + iMessageClearLogButton = new Button(); + iMessageClearLogButton.setName( "MessageClearLogButton" ); + iMessageClearLogButton.setLabel( "Clear Log" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageClearLogButton; + } + + /** + * Return the MessageCommandPanel property value. + * + * @return java.awt.Panel + */ + private Panel getMessageCommandPanel() + { + if( iMessageCommandPanel == null ) + { + try + { + iMessageCommandPanel = new Panel(); + iMessageCommandPanel.setName( "MessageCommandPanel" ); + iMessageCommandPanel.setLayout( new FlowLayout() ); + getMessageCommandPanel().add( getMessageClearLogButton(), getMessageClearLogButton().getName() ); + getMessageCommandPanel().add( getMessageOkButton(), getMessageOkButton().getName() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageCommandPanel; + } + + /** + * Return the MessageContentPanel property value. + * + * @return java.awt.Panel + */ + private Panel getMessageContentPanel() + { + if( iMessageContentPanel == null ) + { + try + { + iMessageContentPanel = new Panel(); + iMessageContentPanel.setName( "MessageContentPanel" ); + iMessageContentPanel.setLayout( new BorderLayout() ); + iMessageContentPanel.setBackground( SystemColor.control ); + getMessageContentPanel().add( getMessageTextArea(), "Center" ); + getMessageContentPanel().add( getMessageCommandPanel(), "South" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageContentPanel; + } + + /** + * Return the MessageFrame property value. + * + * @return java.awt.Frame + */ + private Frame getMessageFrame() + { + if( iMessageFrame == null ) + { + try + { + iMessageFrame = new Frame(); + iMessageFrame.setName( "MessageFrame" ); + iMessageFrame.setLayout( new BorderLayout() ); + iMessageFrame.setBounds( 0, 0, 750, 250 ); + iMessageFrame.setTitle( "Message Log" ); + iMessageFrame.add( getMessageContentPanel(), "Center" ); + iMessageFrame.setLocation( ( Toolkit.getDefaultToolkit().getScreenSize().width / 2 ) - ( iMessageFrame.getSize().width / 2 ), ( java.awt.Toolkit.getDefaultToolkit().getScreenSize().height / 2 ) ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageFrame; + } + + /** + * Return the MessageOkButton property value. + * + * @return java.awt.Button + */ + private Button getMessageOkButton() + { + if( iMessageOkButton == null ) + { + try + { + iMessageOkButton = new Button(); + iMessageOkButton.setName( "MessageOkButton" ); + iMessageOkButton.setLabel( "Close" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageOkButton; + } + + /** + * Return the MessageOutputLevelChoice property value. + * + * @return java.awt.Choice + */ + private Choice getMessageOutputLevelChoice() + { + if( iMessageOutputLevelChoice == null ) + { + try + { + iMessageOutputLevelChoice = new Choice(); + iMessageOutputLevelChoice.setName( "MessageOutputLevelChoice" ); + iMessageOutputLevelChoice.add( "Error" ); + iMessageOutputLevelChoice.add( "Warning" ); + iMessageOutputLevelChoice.add( "Info" ); + iMessageOutputLevelChoice.add( "Verbose" ); + iMessageOutputLevelChoice.add( "Debug" ); + iMessageOutputLevelChoice.select( 2 ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageOutputLevelChoice; + } + + /** + * Return the MessageOutputLevelLabel property value. + * + * @return java.awt.Label + */ + private Label getMessageOutputLevelLabel() + { + if( iMessageOutputLevelLabel == null ) + { + try + { + iMessageOutputLevelLabel = new Label(); + iMessageOutputLevelLabel.setName( "MessageOutputLevelLabel" ); + iMessageOutputLevelLabel.setText( "Message Level:" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageOutputLevelLabel; + } + + /** + * Return the MessageTextArea property value. + * + * @return java.awt.TextArea + */ + private TextArea getMessageTextArea() + { + if( iMessageTextArea == null ) + { + try + { + iMessageTextArea = new TextArea(); + iMessageTextArea.setName( "MessageTextArea" ); + iMessageTextArea.setFont( new Font( "monospaced", 0, 12 ) ); + iMessageTextArea.setText( "" ); + iMessageTextArea.setEditable( false ); + iMessageTextArea.setEnabled( true ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageTextArea; + } + + /** + * Return the Panel1 property value. + * + * @return java.awt.Panel + */ + private Panel getOptionenPanel() + { + if( iOptionenPanel == null ) + { + try + { + iOptionenPanel = new Panel(); + iOptionenPanel.setName( "OptionenPanel" ); + iOptionenPanel.setLayout( new GridBagLayout() ); + iOptionenPanel.setBackground( SystemColor.control ); + + GridBagConstraints constraintsProjectLabel = new GridBagConstraints(); + constraintsProjectLabel.gridx = 0; + constraintsProjectLabel.gridy = 0; + constraintsProjectLabel.anchor = GridBagConstraints.WEST; + constraintsProjectLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getProjectLabel(), constraintsProjectLabel ); + + GridBagConstraints constraintsBuildFileLabel = new GridBagConstraints(); + constraintsBuildFileLabel.gridx = 0; + constraintsBuildFileLabel.gridy = 1; + constraintsBuildFileLabel.anchor = GridBagConstraints.WEST; + constraintsBuildFileLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getBuildFileLabel(), constraintsBuildFileLabel ); + + GridBagConstraints constraintsTargetLabel = new GridBagConstraints(); + constraintsTargetLabel.gridx = 0; + constraintsTargetLabel.gridy = 2; + constraintsTargetLabel.anchor = GridBagConstraints.NORTHWEST; + constraintsTargetLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getTargetLabel(), constraintsTargetLabel ); + + GridBagConstraints constraintsProjectText = new GridBagConstraints(); + constraintsProjectText.gridx = 1; + constraintsProjectText.gridy = 0; + constraintsProjectText.gridwidth = 2; + constraintsProjectText.fill = GridBagConstraints.HORIZONTAL; + constraintsProjectText.anchor = GridBagConstraints.WEST; + constraintsProjectText.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getProjectText(), constraintsProjectText ); + + GridBagConstraints constraintsBuildFileTextField = new GridBagConstraints(); + constraintsBuildFileTextField.gridx = 1; + constraintsBuildFileTextField.gridy = 1; + constraintsBuildFileTextField.fill = GridBagConstraints.HORIZONTAL; + constraintsBuildFileTextField.anchor = GridBagConstraints.WEST; + constraintsBuildFileTextField.weightx = 1.0; + constraintsBuildFileTextField.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getBuildFileTextField(), constraintsBuildFileTextField ); + + GridBagConstraints constraintsBrowseButton = new GridBagConstraints(); + constraintsBrowseButton.gridx = 2; + constraintsBrowseButton.gridy = 1; + constraintsBrowseButton.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getBrowseButton(), constraintsBrowseButton ); + + GridBagConstraints constraintsTargetList = new GridBagConstraints(); + constraintsTargetList.gridx = 1; + constraintsTargetList.gridy = 2; + constraintsTargetList.gridheight = 2; + constraintsTargetList.fill = GridBagConstraints.BOTH; + constraintsTargetList.weightx = 1.0; + constraintsTargetList.weighty = 1.0; + constraintsTargetList.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getTargetList(), constraintsTargetList ); + + GridBagConstraints constraintsMessageOutputLevelLabel = new GridBagConstraints(); + constraintsMessageOutputLevelLabel.gridx = 0; + constraintsMessageOutputLevelLabel.gridy = 4; + constraintsMessageOutputLevelLabel.anchor = GridBagConstraints.WEST; + constraintsMessageOutputLevelLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getMessageOutputLevelLabel(), constraintsMessageOutputLevelLabel ); + + GridBagConstraints constraintsMessageOutputLevelChoice = new GridBagConstraints(); + constraintsMessageOutputLevelChoice.gridx = 1; + constraintsMessageOutputLevelChoice.gridy = 4; + constraintsMessageOutputLevelChoice.fill = GridBagConstraints.HORIZONTAL; + constraintsMessageOutputLevelChoice.anchor = GridBagConstraints.WEST; + constraintsMessageOutputLevelChoice.weightx = 1.0; + constraintsMessageOutputLevelChoice.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getMessageOutputLevelChoice(), constraintsMessageOutputLevelChoice ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iOptionenPanel; + } + + /** + * Return the ProjectLabel property value. + * + * @return java.awt.Label + */ + private Label getProjectLabel() + { + if( iProjectLabel == null ) + { + try + { + iProjectLabel = new Label(); + iProjectLabel.setName( "ProjectLabel" ); + iProjectLabel.setText( "Projectname:" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iProjectLabel; + } + + /** + * Return the ProjectText property value. + * + * @return java.awt.Label + */ + private Label getProjectText() + { + if( iProjectText == null ) + { + try + { + iProjectText = new Label(); + iProjectText.setName( "ProjectText" ); + iProjectText.setText( " " ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iProjectText; + } + + /** + * Return the ReloadButton property value. + * + * @return java.awt.Button + */ + private Button getReloadButton() + { + if( iReloadButton == null ) + { + try + { + iReloadButton = new Button(); + iReloadButton.setName( "ReloadButton" ); + iReloadButton.setLabel( "(Re)Load" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iReloadButton; + } + + /** + * Return the SaveMenuItem property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getSaveMenuItem() + { + if( iSaveMenuItem == null ) + { + try + { + iSaveMenuItem = new MenuItem(); + iSaveMenuItem.setLabel( "Save BuildInfo To Repository" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iSaveMenuItem; + } + + /** + * Return the ShowLogMenuItem property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getShowLogMenuItem() + { + if( iShowLogMenuItem == null ) + { + try + { + iShowLogMenuItem = new MenuItem(); + iShowLogMenuItem.setLabel( "Log" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iShowLogMenuItem; + } + + /** + * Return the StopButton property value. + * + * @return java.awt.Button + */ + private Button getStopButton() + { + if( iStopButton == null ) + { + try + { + iStopButton = new Button(); + iStopButton.setName( "StopButton" ); + iStopButton.setLabel( "Stop" ); + iStopButton.setEnabled( false ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iStopButton; + } + + /** + * Return the TargetLabel property value. + * + * @return java.awt.Label + */ + private Label getTargetLabel() + { + if( iTargetLabel == null ) + { + try + { + iTargetLabel = new Label(); + iTargetLabel.setName( "TargetLabel" ); + iTargetLabel.setText( "Target:" ); + iTargetLabel.setEnabled( true ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iTargetLabel; + } + + /** + * Return the TargetList property value. + * + * @return java.awt.List + */ + private List getTargetList() + { + if( iTargetList == null ) + { + try + { + iTargetList = new List(); + iTargetList.setName( "TargetList" ); + iTargetList.setEnabled( true ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iTargetList; + } + + /** + * connectBuildFileNameToTextField: (BuildInfo.buildFileName <--> + * BuildFileTextField.text) + */ + private void connectBuildFileNameToTextField() + { + /* + * Set the target from the source + */ + try + { + if( iConnPtoP2Aligning == false ) + { + iConnPtoP2Aligning = true; + if( ( getBuildInfo() != null ) ) + { + getBuildFileTextField().setText( getBuildInfo().getBuildFileName() ); + } + iConnPtoP2Aligning = false; + } + } + catch( Throwable iExc ) + { + iConnPtoP2Aligning = false; + handleException( iExc ); + } + } + + /** + * connectProjectNameToLabel: (BuildInfo.vajProjectName <--> + * ProjectText.text) + */ + private void connectProjectNameToLabel() + { + /* + * Set the target from the source + */ + try + { + if( ( getBuildInfo() != null ) ) + { + getProjectText().setText( getBuildInfo().getVAJProjectName() ); + } + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + + /** + * connectTextFieldToBuildFileName: (BuildInfo.buildFileName <--> + * BuildFileTextField.text) + */ + private void connectTextFieldToBuildFileName() + { + /* + * Set the source from the target + */ + try + { + if( iConnPtoP2Aligning == false ) + { + iConnPtoP2Aligning = true; + if( ( getBuildInfo() != null ) ) + { + getBuildInfo().setBuildFileName( getBuildFileTextField().getText() ); + } + iConnPtoP2Aligning = false; + } + } + catch( Throwable iExc ) + { + iConnPtoP2Aligning = false; + handleException( iExc ); + } + } + + /** + * external build of a .jar-file + */ + private void executeTarget() + { + try + { + getMessageFrame().show(); + getBuildInfo().executeProject( logger ); + } + catch( Throwable exc ) + { + logger.logException( exc ); + } + return; + } + + /** + * Fills the taget-list with project-targets + */ + private void fillList() + { + getTargetList().removeAll(); + Vector targets = getBuildInfo().getProjectTargets(); + for( int i = 0; i < targets.size(); i++ ) + { + getTargetList().add( targets.elementAt( i ).toString() ); + } + getTargetList().select( iBuildInfo.getProjectTargets().indexOf( iBuildInfo.getTarget() ) ); + if( getTargetList().getSelectedIndex() >= 0 ) + { + getBuildButton().setEnabled( true ); + } + } + + /** + * Called whenever the part throws an exception. + * + * @param exception Throwable + */ + private void handleException( Throwable exception ) + { + // Write exceptions to the log-window + String trace = StringUtils.getStackTrace( exception ); + + getMessageTextArea().append( lineSeparator + lineSeparator + trace ); + getMessageFrame().show(); + + } + + /** + * Initializes connections + * + * @exception Exception The exception description. + */ + private void initConnections() + throws Exception + { + this.addWindowListener( iEventHandler ); + getBrowseButton().addActionListener( iEventHandler ); + getCloseButton().addActionListener( iEventHandler ); + getBuildButton().addActionListener( iEventHandler ); + getStopButton().addActionListener( iEventHandler ); + getSaveMenuItem().addActionListener( iEventHandler ); + getAboutOkButton().addActionListener( iEventHandler ); + getAboutMenuItem().addActionListener( iEventHandler ); + getMessageOkButton().addActionListener( iEventHandler ); + getMessageClearLogButton().addActionListener( iEventHandler ); + getMessageOkButton().addActionListener( iEventHandler ); + getShowLogMenuItem().addActionListener( iEventHandler ); + getAboutDialog().addWindowListener( iEventHandler ); + getMessageFrame().addWindowListener( iEventHandler ); + getReloadButton().addActionListener( iEventHandler ); + getTargetList().addItemListener( iEventHandler ); + getMessageOutputLevelChoice().addItemListener( iEventHandler ); + getBuildFileTextField().addTextListener( iEventHandler ); + connectProjectNameToLabel(); + connectBuildFileNameToTextField(); + } + + /** + * Initialize the class. + */ + private void initialize() + { + try + { + setName( "AntMake" ); + setMenuBar( getAntMakeMenuBar() ); + setLayout( new java.awt.BorderLayout() ); + setSize( 389, 222 ); + setTitle( "Ant VisualAge for Java Tool-Integration" ); + add( getContentsPane(), "Center" ); + initConnections(); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + setLocation( ( Toolkit.getDefaultToolkit().getScreenSize().width / 2 ) - ( getSize().width / 2 ), ( java.awt.Toolkit.getDefaultToolkit().getScreenSize().height / 2 ) - ( getSize().height ) ); + if( ( getTargetList().getItemCount() == 0 ) || ( getTargetList().getSelectedIndex() < 0 ) ) + { + getBuildButton().setEnabled( false ); + } + } + + /** + * Saves the build-informations to repository + */ + private void saveBuildInfo() + { + try + { + VAJAntTool.saveBuildData( getBuildInfo() ); + } + catch( Throwable exc ) + { + // This Exception occurs when you try to write into a versioned project + handleException( exc ); + } + return; + } + + /** + * Eventhandler to handle all AWT-events + * + * @author RT + */ + private class PrivateEventHandler implements ActionListener, ItemListener, TextListener, WindowListener, PropertyChangeListener + { + /** + * ActionListener method + * + * @param e Description of Parameter + */ + public void actionPerformed( ActionEvent e ) + { + try + { + /* + * #### Main App-Frame #### + */ + // browse XML-File with filechooser + if( e.getSource() == VAJAntToolGUI.this.getBrowseButton() ) + { + getFileDialog().setDirectory( getBuildFileTextField().getText().substring( 0, getBuildFileTextField().getText().lastIndexOf( '\\' ) + 1 ) ); + getFileDialog().setFile( "*.xml" ); + getFileDialog().show(); + if( !getFileDialog().getFile().equals( "" ) ) + { + getBuildFileTextField().setText( getFileDialog().getDirectory() + getFileDialog().getFile() ); + } + } + // dispose and exit application + if( e.getSource() == VAJAntToolGUI.this.getCloseButton() ) + { + dispose(); + System.exit( 0 ); + } + // start build-process + if( e.getSource() == VAJAntToolGUI.this.getBuildButton() ) + { + executeTarget(); + } + if( e.getSource() == VAJAntToolGUI.this.getStopButton() ) + { + getBuildInfo().cancelBuild(); + } + if( e.getSource() == VAJAntToolGUI.this.getReloadButton() ) + { + try + { + getBuildInfo().updateTargetList(); + fillList(); + } + catch( Throwable fileNotFound ) + { + handleException( fileNotFound ); + getTargetList().removeAll(); + getBuildButton().setEnabled( false ); + } + } + // MenuItems + if( e.getSource() == VAJAntToolGUI.this.getSaveMenuItem() ) + saveBuildInfo(); + if( e.getSource() == VAJAntToolGUI.this.getAboutMenuItem() ) + getAboutDialog().show(); + if( e.getSource() == VAJAntToolGUI.this.getShowLogMenuItem() ) + getMessageFrame().show(); + /* + * #### About dialog #### + */ + if( e.getSource() == VAJAntToolGUI.this.getAboutOkButton() ) + getAboutDialog().dispose(); + /* + * #### Log frame #### + */ + if( e.getSource() == VAJAntToolGUI.this.getMessageOkButton() ) + getMessageFrame().dispose(); + if( e.getSource() == VAJAntToolGUI.this.getMessageClearLogButton() ) + getMessageTextArea().setText( "" ); + if( e.getSource() == VAJAntToolGUI.this.getMessageOkButton() ) + getMessageFrame().dispose(); + } + catch( Throwable exc ) + { + handleException( exc ); + } + } + + /** + * ItemListener method + * + * @param e Description of Parameter + */ + public void itemStateChanged( ItemEvent e ) + { + try + { + if( e.getSource() == VAJAntToolGUI.this.getTargetList() ) + getBuildButton().setEnabled( true ); + if( e.getSource() == VAJAntToolGUI.this.getMessageOutputLevelChoice() ) + getBuildInfo().setOutputMessageLevel( getMessageOutputLevelChoice().getSelectedIndex() ); + if( e.getSource() == VAJAntToolGUI.this.getTargetList() ) + getBuildInfo().setTarget( getTargetList().getSelectedItem() ); + } + catch( Throwable exc ) + { + handleException( exc ); + } + } + + /** + * PropertyChangeListener method + * + * @param evt Description of Parameter + */ + public void propertyChange( java.beans.PropertyChangeEvent evt ) + { + if( evt.getSource() == VAJAntToolGUI.this.getBuildInfo() && ( evt.getPropertyName().equals( "projectName" ) ) ) + connectProjectNameToLabel(); + if( evt.getSource() == VAJAntToolGUI.this.getBuildInfo() && ( evt.getPropertyName().equals( "buildFileName" ) ) ) + connectBuildFileNameToTextField(); + } + + /** + * TextListener method + * + * @param e Description of Parameter + */ + public void textValueChanged( TextEvent e ) + { + if( e.getSource() == VAJAntToolGUI.this.getBuildFileTextField() ) + connectTextFieldToBuildFileName(); + } + + public void windowActivated( WindowEvent e ) { } + + public void windowClosed( WindowEvent e ) { } + + /** + * WindowListener methods + * + * @param e Description of Parameter + */ + public void windowClosing( WindowEvent e ) + { + try + { + if( e.getSource() == VAJAntToolGUI.this ) + { + dispose(); + System.exit( 0 ); + } + if( e.getSource() == VAJAntToolGUI.this.getAboutDialog() ) + getAboutDialog().dispose(); + if( e.getSource() == VAJAntToolGUI.this.getMessageFrame() ) + getMessageFrame().dispose(); + } + catch( Throwable exc ) + { + handleException( exc ); + } + } + + public void windowDeactivated( WindowEvent e ) { } + + public void windowDeiconified( WindowEvent e ) { } + + public void windowIconified( WindowEvent e ) { } + + public void windowOpened( WindowEvent e ) { } + } + + /** + * This internal BuildLogger, to be honest, is just a BuildListener. It does + * nearly the same as the DefaultLogger, but uses the Loggin-Window for + * output. + * + * @author RT + */ + private class VAJBuildLogger implements BuildListener + { + private long startTime = System.currentTimeMillis(); + + /** + * VAJBuildLogger constructor comment. + */ + public VAJBuildLogger() + { + super(); + } + + /** + * Fired after the last target has finished. This event will still be + * thrown if an error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + public void buildFinished( BuildEvent event ) + { + getStopButton().setEnabled( false ); + getBuildButton().setEnabled( true ); + getBuildButton().requestFocus(); + + Throwable error = event.getException(); + + if( error == null ) + { + getMessageTextArea().append( lineSeparator + "BUILD SUCCESSFUL" ); + } + else + { + logException( error ); + } + + getMessageTextArea().append( lineSeparator + "Total time: " + formatTime( System.currentTimeMillis() - startTime ) ); + } + + /** + * Fired before any targets are started. + * + * @param event Description of Parameter + */ + public void buildStarted( BuildEvent event ) + { + getStopButton().setEnabled( true ); + getBuildButton().setEnabled( false ); + getStopButton().requestFocus(); + + startTime = System.currentTimeMillis(); + getMessageTextArea().append( lineSeparator ); + } + + + /** + * Outputs an exception. + * + * @param error Description of Parameter + */ + public void logException( Throwable error ) + { + getMessageTextArea().append( lineSeparator + "BUILD FAILED" + lineSeparator ); + + if( error instanceof BuildException ) + { + getMessageTextArea().append( error.toString() ); + + Throwable nested = ( ( BuildException )error ).getCause(); + if( nested != null ) + { + nested.printStackTrace( System.err ); + } + } + else + { + error.printStackTrace( System.err ); + } + } + + /** + * Fired whenever a message is logged. + * + * @param event Description of Parameter + * @see BuildEvent#getMessage() + * @see BuildEvent#getPriority() + */ + public void messageLogged( BuildEvent event ) + { + if( event.getPriority() <= getBuildInfo().getOutputMessageLevel() ) + { + String msg = ""; + if( event.getTask() != null ) + msg = "[" + event.getTask().getTaskName() + "] "; + getMessageTextArea().append( lineSeparator + msg + event.getMessage() ); + } + } + + /** + * Fired when a target has finished. This event will still be thrown if + * an error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + public void targetFinished( BuildEvent event ) { } + + /** + * Fired when a target is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTarget() + */ + public void targetStarted( BuildEvent event ) + { + if( getBuildInfo().getOutputMessageLevel() <= Project.MSG_INFO ) + { + getMessageTextArea().append( lineSeparator + event.getTarget().getName() + ":" ); + } + } + + /** + * Fired when a task has finished. This event will still be throw if an + * error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + public void taskFinished( BuildEvent event ) { } + + /** + * Fired when a task is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTask() + */ + public void taskStarted( BuildEvent event ) { } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java new file mode 100644 index 000000000..a504426bb --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java @@ -0,0 +1,585 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.File; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Target; + +/** + * This class wraps the Ant project information needed to start Ant from Visual + * Age. It serves the following purposes: - acts as model for AntMakeFrame - + * converts itself to/from String (to store the information as ToolData in the + * VA repository) - wraps Project functions for the GUI (get target list, + * execute target) - manages a seperate thread for Ant project execution this + * allows interrupting a running build from a GUI + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +class VAJBuildInfo implements Runnable +{ + + // name of the VA project this BuildInfo belongs to + private String vajProjectName = ""; + + // name of the Ant build file + private String buildFileName = ""; + + // main targets found in the build file + private Vector projectTargets = new Vector(); + + // target selected for execution + private java.lang.String target = ""; + + // log level + private int outputMessageLevel = Project.MSG_INFO; + + // is true if Project initialization was successful + private transient boolean projectInitialized = false; + + // Support for bound properties + protected transient PropertyChangeSupport propertyChange; + + // thread for Ant build execution + private Thread buildThread; + + // Ant Project created from build file + private transient Project project; + + // the listener used to log output. + private BuildListener projectLogger; + + /** + * Creates a BuildInfo object from a String The String must be in the format + * outputMessageLevel'|'buildFileName'|'defaultTarget'|'(project target'|')* + * + * @param data java.lang.String + * @return org.apache.tools.ant.taskdefs.optional.vaj.BuildInfo + */ + public static VAJBuildInfo parse( String data ) + { + VAJBuildInfo result = new VAJBuildInfo(); + + try + { + StringTokenizer tok = new StringTokenizer( data, "|" ); + result.setOutputMessageLevel( tok.nextToken() ); + result.setBuildFileName( tok.nextToken() ); + result.setTarget( tok.nextToken() ); + while( tok.hasMoreTokens() ) + { + result.projectTargets.addElement( tok.nextToken() ); + } + } + catch( Throwable t ) + { + // if parsing the info fails, just return + // an empty VAJBuildInfo + } + return result; + } + + /** + * Search for the insert position to keep names a sorted list of Strings + * This method has been copied from org.apache.tools.ant.Main + * + * @param names Description of Parameter + * @param name Description of Parameter + * @return Description of the Returned Value + */ + private static int findTargetPosition( Vector names, String name ) + { + int res = names.size(); + for( int i = 0; i < names.size() && res == names.size(); i++ ) + { + if( name.compareTo( ( String )names.elementAt( i ) ) < 0 ) + { + res = i; + } + } + return res; + } + + /** + * Sets the build file name + * + * @param newBuildFileName The new BuildFileName value + */ + public void setBuildFileName( String newBuildFileName ) + { + String oldValue = buildFileName; + buildFileName = newBuildFileName; + setProjectInitialized( false ); + firePropertyChange( "buildFileName", oldValue, buildFileName ); + } + + /** + * Sets the log level (value must be one of the constants in Project) + * + * @param newOutputMessageLevel The new OutputMessageLevel value + */ + public void setOutputMessageLevel( int newOutputMessageLevel ) + { + int oldValue = outputMessageLevel; + outputMessageLevel = newOutputMessageLevel; + firePropertyChange( "outputMessageLevel", + new Integer( oldValue ), new Integer( outputMessageLevel ) ); + } + + /** + * Sets the target to execute when executeBuild is called + * + * @param newTarget build target + */ + public void setTarget( String newTarget ) + { + String oldValue = target; + target = newTarget; + firePropertyChange( "target", oldValue, target ); + } + + /** + * Sets the name of the Visual Age for Java project where this BuildInfo + * belongs to + * + * @param newVAJProjectName The new VAJProjectName value + */ + public void setVAJProjectName( String newVAJProjectName ) + { + String oldValue = vajProjectName; + vajProjectName = newVAJProjectName; + firePropertyChange( "VAJProjectName", oldValue, vajProjectName ); + } + + /** + * Returns the build file name. + * + * @return build file name. + */ + public String getBuildFileName() + { + return buildFileName; + } + + /** + * Returns the log level + * + * @return log level. + */ + public int getOutputMessageLevel() + { + return outputMessageLevel; + } + + /** + * return a list of all targets in the current buildfile + * + * @return The ProjectTargets value + */ + public Vector getProjectTargets() + { + return projectTargets; + } + + /** + * returns the selected target. + * + * @return The Target value + */ + public java.lang.String getTarget() + { + return target; + } + + /** + * returns the VA project name + * + * @return The VAJProjectName value + */ + public String getVAJProjectName() + { + return vajProjectName; + } + + /** + * Returns true, if the Ant project is initialized (i.e. buildfile loaded) + * + * @return The ProjectInitialized value + */ + public boolean isProjectInitialized() + { + return projectInitialized; + } + + + /** + * The addPropertyChangeListener method was generated to support the + * propertyChange field. + * + * @param listener The feature to be added to the PropertyChangeListener + * attribute + */ + public synchronized void addPropertyChangeListener( PropertyChangeListener listener ) + { + getPropertyChange().addPropertyChangeListener( listener ); + } + + /** + * Returns the BuildInfo information as String. The BuildInfo can be rebuilt + * from that String by calling parse(). + * + * @return java.lang.String + */ + public String asDataString() + { + String result = getOutputMessageLevel() + "|" + getBuildFileName() + + "|" + getTarget(); + for( Enumeration e = getProjectTargets().elements(); + e.hasMoreElements(); ) + { + result = result + "|" + e.nextElement(); + } + + return result; + } + + + /** + * cancels a build. + */ + public void cancelBuild() + { + buildThread.interrupt(); + } + + /** + * Executes the target set by setTarget(). + * + * @param logger Description of Parameter + */ + public void executeProject( BuildListener logger ) + { + Throwable error; + projectLogger = logger; + try + { + buildThread = new Thread( this ); + buildThread.setPriority( Thread.MIN_PRIORITY ); + buildThread.start(); + } + catch( RuntimeException exc ) + { + error = exc; + throw exc; + } + catch( Error err ) + { + error = err; + throw err; + } + } + + /** + * The firePropertyChange method was generated to support the propertyChange + * field. + * + * @param propertyName Description of Parameter + * @param oldValue Description of Parameter + * @param newValue Description of Parameter + */ + public void firePropertyChange( java.lang.String propertyName, java.lang.Object oldValue, java.lang.Object newValue ) + { + getPropertyChange().firePropertyChange( propertyName, oldValue, newValue ); + } + + /** + * The removePropertyChangeListener method was generated to support the + * propertyChange field. + * + * @param listener Description of Parameter + */ + public synchronized void removePropertyChangeListener( PropertyChangeListener listener ) + { + getPropertyChange().removePropertyChangeListener( listener ); + } + + /** + * Executes a build. This method is executed by the Ant execution thread + */ + public void run() + { + try + { + InterruptedChecker ic = new InterruptedChecker( projectLogger ); + BuildEvent e = new BuildEvent( getProject() ); + try + { + ic.buildStarted( e ); + + if( !isProjectInitialized() ) + { + initProject(); + } + + project.addBuildListener( ic ); + project.executeTarget( target ); + + ic.buildFinished( e ); + } + catch( Throwable t ) + { + e.setException( t ); + ic.buildFinished( e ); + } + finally + { + project.removeBuildListener( ic ); + } + } + catch( Throwable t2 ) + { + System.out.println( "unexpected exception!" ); + t2.printStackTrace(); + } + } + + /** + * reloads the build file and updates the target list + */ + public void updateTargetList() + { + project = new Project(); + initProject(); + projectTargets.removeAllElements(); + Enumeration ptargets = project.getTargets().elements(); + while( ptargets.hasMoreElements() ) + { + Target currentTarget = ( Target )ptargets.nextElement(); + if( currentTarget.getDescription() != null ) + { + String targetName = currentTarget.getName(); + int pos = findTargetPosition( projectTargets, targetName ); + projectTargets.insertElementAt( targetName, pos ); + } + } + } + + /** + * Accessor for the propertyChange field. + * + * @return The PropertyChange value + */ + protected PropertyChangeSupport getPropertyChange() + { + if( propertyChange == null ) + { + propertyChange = new PropertyChangeSupport( this ); + } + return propertyChange; + } + + /** + * Sets the log level (value must be one of the constants in Project) + * + * @param outputMessageLevel log level as String. + */ + private void setOutputMessageLevel( String outputMessageLevel ) + { + int level = Integer.parseInt( outputMessageLevel ); + setOutputMessageLevel( level ); + } + + /** + * sets the initialized flag + * + * @param initialized The new ProjectInitialized value + */ + private void setProjectInitialized( boolean initialized ) + { + Boolean oldValue = new Boolean( projectInitialized ); + projectInitialized = initialized; + firePropertyChange( "projectInitialized", oldValue, new Boolean( projectInitialized ) ); + } + + /** + * Returns the Ant project + * + * @return org.apache.tools.ant.Project + */ + private Project getProject() + { + if( project == null ) + { + project = new Project(); + } + return project; + } + + /** + * Initializes the Ant project. Assumes that the project attribute is + * already set. + */ + private void initProject() + { + try + { + project.init(); + File buildFile = new File( getBuildFileName() ); + project.setUserProperty( "ant.file", buildFile.getAbsolutePath() ); + ProjectHelper.configureProject( project, buildFile ); + setProjectInitialized( true ); + } + catch( RuntimeException exc ) + { + setProjectInitialized( false ); + throw exc; + } + catch( Error err ) + { + setProjectInitialized( false ); + throw err; + } + } + + /** + * This exception is thrown when a build is interrupted + * + * @author RT + */ + public static class BuildInterruptedException extends BuildException + { + public String toString() + { + return "BUILD INTERRUPTED"; + } + } + + /** + * BuildListener which checks for interruption and throws Exception if build + * process is interrupted. This class is a wrapper around a 'real' listener. + * + * @author RT + */ + private class InterruptedChecker implements BuildListener + { + // the real listener + BuildListener wrappedListener; + + /** + * Can only be constructed as wrapper around a real listener + * + * @param listener the real listener + */ + public InterruptedChecker( BuildListener listener ) + { + super(); + wrappedListener = listener; + } + + /** + * Fired after the last target has finished. This event will still be + * thrown if an error occured during the build. + * + * @param event Description of Parameter + */ + public void buildFinished( BuildEvent event ) + { + wrappedListener.buildFinished( event ); + checkInterrupted(); + } + + /** + * Fired before any targets are started. + * + * @param event Description of Parameter + */ + public void buildStarted( BuildEvent event ) + { + wrappedListener.buildStarted( event ); + checkInterrupted(); + } + + /** + * Fired whenever a message is logged. + * + * @param event Description of Parameter + */ + public void messageLogged( BuildEvent event ) + { + wrappedListener.messageLogged( event ); + checkInterrupted(); + } + + /** + * Fired when a target has finished. This event will still be thrown if + * an error occured during the build. + * + * @param event Description of Parameter + */ + public void targetFinished( BuildEvent event ) + { + wrappedListener.targetFinished( event ); + checkInterrupted(); + } + + /** + * Fired when a target is started. + * + * @param event Description of Parameter + */ + public void targetStarted( BuildEvent event ) + { + wrappedListener.targetStarted( event ); + checkInterrupted(); + } + + /** + * Fired when a task has finished. This event will still be throw if an + * error occured during the build. + * + * @param event Description of Parameter + */ + public void taskFinished( BuildEvent event ) + { + wrappedListener.taskFinished( event ); + checkInterrupted(); + } + + /** + * Fired when a task is started. + * + * @param event Description of Parameter + */ + public void taskStarted( BuildEvent event ) + { + wrappedListener.taskStarted( event ); + checkInterrupted(); + } + + /** + * checks if the thread was interrupted. When an interrupt occured, + * throw an Exception to stop the execution. + */ + protected void checkInterrupted() + { + if( buildThread.isInterrupted() ) + { + throw new BuildInterruptedException(); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java new file mode 100644 index 000000000..4d49c923d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.PatternSet; + +/** + * Export packages from the Visual Age for Java workspace. The packages are + * specified similar to all other MatchingTasks. Since the VA Workspace is not + * file based, this task is simulating a directory hierarchy for the workspace: + * The 'root' contains all project 'dir's, and the projects contain their + * respective package 'dir's. Example:

      <vajexport + * destdir="C:/builddir/source">  <include + * name="/MyVAProject/org/foo/subsystem1/**" />  <exclude + * name="/MyVAProject/org/foo/subsystem1/test/**"/> </vajexport> + *
      exports all packages in the project MyVAProject which start + * with 'org.foo.subsystem1' except of these starting with + * 'org.foo.subsystem1.test'. There are flags to choose which items to export: + * exportSources: export Java sources exportResources: export project resources + * exportClasses: export class files exportDebugInfo: export class files with + * debug info (use with exportClasses) default is exporting Java files and + * resources. + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +public class VAJExport extends VAJTask +{ + protected boolean exportSources = true; + protected boolean exportResources = true; + protected boolean exportClasses = false; + protected boolean exportDebugInfo = false; + protected boolean useDefaultExcludes = true; + protected boolean overwrite = true; + + protected PatternSet patternSet = new PatternSet(); + //set set... method comments for description + protected File destDir; + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + /** + * Set the destination directory into which the selected items should be + * exported + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. Currently only patterns denoting packages are supported + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + patternSet.setExcludes( excludes ); + } + + /** + * if exportClasses is set, class files are exported + * + * @param doExport The new ExportClasses value + */ + public void setExportClasses( boolean doExport ) + { + exportClasses = doExport; + } + + /** + * if exportDebugInfo is set, the exported class files contain debug info + * + * @param doExport The new ExportDebugInfo value + */ + public void setExportDebugInfo( boolean doExport ) + { + exportDebugInfo = doExport; + } + + /** + * if exportResources is set, resource file will be exported + * + * @param doExport The new ExportResources value + */ + public void setExportResources( boolean doExport ) + { + exportResources = doExport; + } + + /** + * if exportSources is set, java files will be exported + * + * @param doExport The new ExportSources value + */ + public void setExportSources( boolean doExport ) + { + exportSources = doExport; + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space.Currently only patterns denoting packages are supported + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + patternSet.setIncludes( includes ); + } + + /** + * if Overwrite is set, files will be overwritten during export + * + * @param doOverwrite The new Overwrite value + */ + public void setOverwrite( boolean doOverwrite ) + { + overwrite = doOverwrite; + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + return patternSet.createExclude(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + return patternSet.createInclude(); + } + + /** + * do the export + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a destdir + if( destDir == null ) + { + throw new BuildException( "destdir attribute must be set!" ); + } + + // delegate the export to the VAJUtil object. + getUtil().exportPackages( destDir, + patternSet.getIncludePatterns( getProject() ), + patternSet.getExcludePatterns( getProject() ), + exportClasses, exportDebugInfo, + exportResources, exportSources, + useDefaultExcludes, overwrite ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java new file mode 100644 index 000000000..a78a51efc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; + +/** + * A Remote Access to Tools Servlet to extract package sets from the Workbench + * to the local file system. The following table describes the servlet + * parameters. + * + * + * + * + * + * Parameter + * + * + * + * Values + * + * + * + * Description + * + * + * + * + * + * + * + * dir + * + * + * + * Any valid directory name on the server. + * + * + * + * The directory to export the files to on the machine where the servlet + * is being run. If the directory doesn't exist, it will be created.

      + * + * Relative paths are relative to IBMVJava/ide/tools/com-ibm-ivj-toolserver, + * where IBMVJava is the VisualAge for Java installation directory. + * + * + * + * + * + * + * + * include + * + * + * + * See below. + * + * + * + * The pattern used to indicate which projects and packages to export. + * + * + * + * + * + * + * + * + * exclude + * + * + * + * See below + * + * + * + * The pattern used to indicate which projects and packages not + * to export. + * + * + * + * + * + * + * + * cls + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Export class files. Defaults to "no". + * + * + * + * + * + * + * + * src + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Export source files. Defaults to "yes". + * + * + * + * + * + * + * + * res + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Export resource files associated with the included project(s). Defaults + * to "yes". + * + * + * + * + * + * + * + * dex + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Use the default exclusion patterns. Defaults to "yes". See below for an + * explanation of default excludes. + * + * + * + * + * + * + * + * owr + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Overwrite any existing files. Defaults to "yes". + * + * + * + * + * + *

      + * + * The vajexport servlet uses include and exclude parameters to form the + * criteria for selecting packages to export. The parameter is broken up into + * ProjectName/packageNameSegments, where ProjectName is what you expect, and + * packageNameSegments is a partial (or complete) package name, separated by + * forward slashes, rather than periods. Each packageNameSegment can have + * wildcard characters.

      + * + * + * + * + * + * Wildcard Characters + * + * + * + * Description + * + * + * + * + * + * + * + * * + * + * + * + * Match zero or more characters in that segment. + * + * + * + * + * + * + * + * ? + * + * + * + * Match one character in that segment. + * + * + * + * + * + * + * + * ** + * + * + * + * Matches all characters in zero or more segments. + * + * + * + * + * + * + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public class VAJExportServlet extends VAJToolsServlet +{ + // constants for servlet param names + public final static String WITH_DEBUG_INFO = "deb"; + public final static String OVERWRITE_PARAM = "owr"; + + /** + * Respond to a request to export packages from the Workbench. + */ + protected void executeRequest() + { + getUtil().exportPackages( + new File( getFirstParamValueString( DIR_PARAM ) ), + getParamValues( INCLUDE_PARAM ), + getParamValues( EXCLUDE_PARAM ), + getBooleanParam( CLASSES_PARAM, false ), + getBooleanParam( WITH_DEBUG_INFO, false ), + getBooleanParam( RESOURCES_PARAM, true ), + getBooleanParam( SOURCES_PARAM, true ), + getBooleanParam( DEFAULT_EXCLUDES_PARAM, true ), + getBooleanParam( OVERWRITE_PARAM, true ) + ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java new file mode 100644 index 000000000..36d63bbf8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.lang.reflect.Field; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.types.FileSet; + +/** + * Import source, class files, and resources to the Visual Age for Java + * workspace using FileSets.

      + * + * Example:

      + * <vajimport project="MyVAProject">
      + *   <fileset dir="src">
      + *     <include name="org/foo/subsystem1/**" />
      + *     <exclude name="/org/foo/subsystem1/test/**" />
      + *  </fileset>
      + * </vajexport>
      + * 
      import all source and resource files from the "src" directory which + * start with 'org.foo.subsystem1', except of these starting with + * 'org.foo.subsystem1.test' into the project MyVAProject.

      + * + * If MyVAProject isn't loaded into the Workspace, a new edition is created in + * the repository and automatically loaded into the Workspace. There has to be + * at least one nested FileSet element.

      + * + * There are attributes to choose which items to export: + * + * + * + * + * + * Attribute + * + * + * + * Description + * + * + * + * Required + * + * + * + * + * + * + * + * project + * + * + * + * the name of the Project to import to + * + * + * + * Yes + * + * + * + * + * + * + * + * importSources + * + * + * + * import Java sources, defaults to "yes" + * + * + * + * No + * + * + * + * + * + * + * + * importResources + * + * + * + * import resource files (anything that doesn't end with .java or .class), + * defaults to "yes" + * + * + * + * No + * + * + * + * + * + * + * + * importClasses + * + * + * + * import class files, defaults to "no" + * + * + * + * No + * + * + * + * + * + * + * + * @author RT + * @author: Glenn McAllister, inspired by a similar task written by Peter Kelley + */ +public class VAJImport extends VAJTask +{ + protected Vector filesets = new Vector(); + protected boolean importSources = true; + protected boolean importResources = true; + protected boolean importClasses = false; + protected String importProject = null; + protected boolean useDefaultExcludes = true; + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + /** + * Import .class files. + * + * @param importClasses The new ImportClasses value + */ + public void setImportClasses( boolean importClasses ) + { + this.importClasses = importClasses; + } + + /** + * Import resource files (anything that doesn't end in .class or .java) + * + * @param importResources The new ImportResources value + */ + public void setImportResources( boolean importResources ) + { + this.importResources = importResources; + } + + /** + * Import .java files + * + * @param importSources The new ImportSources value + */ + public void setImportSources( boolean importSources ) + { + this.importSources = importSources; + } + + + /** + * The VisualAge for Java Project name to import into. + * + * @param projectName The new Project value + */ + public void setProject( String projectName ) + { + this.importProject = projectName; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Do the import. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( filesets.size() == 0 ) + { + throw new BuildException( "At least one fileset is required!" ); + } + + if( importProject == null || "".equals( importProject ) ) + { + throw new BuildException( "The VisualAge for Java Project name is required!" ); + } + + for( Enumeration e = filesets.elements(); e.hasMoreElements(); ) + { + importFileset( ( FileSet )e.nextElement() ); + } + } + + /** + * Import all files from the fileset into the Project in the Workspace. + * + * @param fileset Description of Parameter + */ + protected void importFileset( FileSet fileset ) + { + DirectoryScanner ds = fileset.getDirectoryScanner( this.project ); + if( ds.getIncludedFiles().length == 0 ) + { + return; + } + + String[] includes = null; + String[] excludes = null; + + // Hack to get includes and excludes. We could also use getIncludedFiles, + // but that would result in very long HTTP-requests. + // Therefore we want to send the patterns only to the remote tool server + // and let him figure out the files. + try + { + Class directoryScanner = ds.getClass(); + + Field includesField = directoryScanner.getDeclaredField( "includes" ); + includesField.setAccessible( true ); + includes = ( String[] )includesField.get( ds ); + + Field excludesField = directoryScanner.getDeclaredField( "excludes" ); + excludesField.setAccessible( true ); + excludes = ( String[] )excludesField.get( ds ); + } + catch( NoSuchFieldException nsfe ) + { + throw new BuildException( + "DirectoryScanner.includes or .excludes missing" + nsfe.getMessage() ); + } + catch( IllegalAccessException iae ) + { + throw new BuildException( + "Access to DirectoryScanner.includes or .excludes not allowed" ); + } + + getUtil().importFiles( importProject, ds.getBasedir(), + includes, excludes, + importClasses, importResources, importSources, + useDefaultExcludes ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java new file mode 100644 index 000000000..9ea01067e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; + + +/** + * A Remote Access to Tools Servlet to import a Project from files into the + * Repository. The following table describes the servlet parameters. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      + * Parameter + * + * Description + *
      + * project + * + * The name of the project where you want the imported items to go. + *
      + * dir + * + * The directory you want to import from. + *
      + * + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public class VAJImportServlet extends VAJToolsServlet +{ + /** + * Respond to a request to import files to the Repository + */ + protected void executeRequest() + { + getUtil().importFiles( + getFirstParamValueString( PROJECT_NAME_PARAM ), + new File( getFirstParamValueString( DIR_PARAM ) ), + getParamValues( INCLUDE_PARAM ), + getParamValues( EXCLUDE_PARAM ), + getBooleanParam( CLASSES_PARAM, false ), + getBooleanParam( RESOURCES_PARAM, true ), + getBooleanParam( SOURCES_PARAM, true ), + false// no default excludes, because they + // are already added on client side + // getBooleanParam(DEFAULT_EXCLUDES_PARAM, true) + ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java new file mode 100644 index 000000000..d42912f1f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.util.Vector; + +/** + * Load specific project versions into the Visual Age for Java workspace. Each + * project and version name has to be specified completely. Example: + *

      <vajload>  <project name="MyVAProject" + * version="2.1"/>  <project name="Apache Xerces" version="1.2.0"/> + * </vajload>
      + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +public class VAJLoad extends VAJTask +{ + Vector projectDescriptions = new Vector(); + + /** + * Add a project description entry on the project list. + * + * @return Description of the Returned Value + */ + public VAJProjectDescription createVAJProject() + { + VAJProjectDescription d = new VAJProjectDescription(); + projectDescriptions.addElement( d ); + return d; + } + + /** + * Load specified projects. + */ + public void execute() + { + getUtil().loadProjects( projectDescriptions ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java new file mode 100644 index 000000000..86c9f8a79 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; + + + + + + +/** + * This is only there for backward compatibility with the default task list and + * will be removed soon + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +public class VAJLoadProjects extends VAJLoad +{ +} + + + + + + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java new file mode 100644 index 000000000..1fca4d63d --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.util.Vector; + +/** + * A Remote Access to Tools Servlet to load a Project from the Repository into + * the Workbench. The following table describes the servlet parameters. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      + * Parameter + * + * Description + *
      + * project + * + * The name of the Project you want to load into the Workbench. + *
      + * version + * + * The version of the package you want to load into the Workbench. + *
      + * + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public class VAJLoadServlet extends VAJToolsServlet +{ + + // constants for servlet param names + public final static String VERSION_PARAM = "version"; + + /** + * Respond to a request to load a project from the Repository into the + * Workbench. + */ + protected void executeRequest() + { + String[] projectNames = getParamValues( PROJECT_NAME_PARAM ); + String[] versionNames = getParamValues( VERSION_PARAM ); + + Vector projectDescriptions = new Vector( projectNames.length ); + for( int i = 0; i < projectNames.length && i < versionNames.length; i++ ) + { + VAJProjectDescription desc = new VAJProjectDescription(); + desc.setName( projectNames[i] ); + desc.setVersion( versionNames[i] ); + projectDescriptions.addElement( desc ); + } + + util.loadProjects( projectDescriptions ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java new file mode 100644 index 000000000..247c2427a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java @@ -0,0 +1,530 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import com.ibm.ivj.util.base.ExportCodeSpec; +import com.ibm.ivj.util.base.ImportCodeSpec; +import com.ibm.ivj.util.base.IvjException; +import com.ibm.ivj.util.base.Package; +import com.ibm.ivj.util.base.Project; +import com.ibm.ivj.util.base.ProjectEdition; +import com.ibm.ivj.util.base.ToolEnv; +import com.ibm.ivj.util.base.Type; +import com.ibm.ivj.util.base.Workspace; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; + + +/** + * Helper class for VAJ tasks. Holds Workspace singleton and wraps IvjExceptions + * into BuildExceptions + * + * @author Wolf Siberski, TUI Infotec GmbH + */ +abstract class VAJLocalUtil implements VAJUtil +{ + // singleton containing the VAJ workspace + private static Workspace workspace; + + /** + * get a project from the Workspace. + * + * @param importProject Description of Parameter + * @return The VAJProject value + */ + static Project getVAJProject( String importProject ) + { + Project found = null; + Project[] currentProjects = getWorkspace().getProjects(); + + for( int i = 0; i < currentProjects.length; i++ ) + { + Project p = currentProjects[i]; + if( p.getName().equals( importProject ) ) + { + found = p; + break; + } + } + + if( found == null ) + { + try + { + found = getWorkspace().createProject( importProject, true ); + } + catch( IvjException e ) + { + throw createBuildException( "Error while creating Project " + + importProject + ": ", e ); + } + } + + return found; + } + + /** + * returns the current VAJ workspace. + * + * @return com.ibm.ivj.util.base.Workspace + */ + static Workspace getWorkspace() + { + if( workspace == null ) + { + workspace = ToolEnv.connectToWorkspace(); + if( workspace == null ) + { + throw new BuildException( + "Unable to connect to Workspace! " + + "Make sure you are running in VisualAge for Java." ); + } + } + + return workspace; + } + + /** + * Wraps IvjException into a BuildException + * + * @param errMsg Additional error message + * @param e IvjException which is wrapped + * @return org.apache.tools.ant.BuildException + */ + static BuildException createBuildException( + String errMsg, IvjException e ) + { + errMsg = errMsg + "\n" + e.getMessage(); + String[] errors = e.getErrors(); + if( errors != null ) + { + for( int i = 0; i < errors.length; i++ ) + { + errMsg = errMsg + "\n" + errors[i]; + } + } + return new BuildException( errMsg, e ); + } + + + //----------------------------------------------------------- + // export + //----------------------------------------------------------- + + /** + * export packages + * + * @param dest Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param exportClasses Description of Parameter + * @param exportDebugInfo Description of Parameter + * @param exportResources Description of Parameter + * @param exportSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @param overwrite Description of Parameter + */ + public void exportPackages( + File dest, + String[] includePatterns, String[] excludePatterns, + boolean exportClasses, boolean exportDebugInfo, + boolean exportResources, boolean exportSources, + boolean useDefaultExcludes, boolean overwrite ) + { + if( includePatterns == null || includePatterns.length == 0 ) + { + log( "You must specify at least one include attribute. " + + "Not exporting", MSG_ERR ); + } + else + { + try + { + VAJWorkspaceScanner scanner = new VAJWorkspaceScanner(); + scanner.setIncludes( includePatterns ); + scanner.setExcludes( excludePatterns ); + if( useDefaultExcludes ) + { + scanner.addDefaultExcludes(); + } + scanner.scan(); + + Package[] packages = scanner.getIncludedPackages(); + + log( "Exporting " + packages.length + " package(s) to " + + dest, MSG_INFO ); + for( int i = 0; i < packages.length; i++ ) + { + log( " " + packages[i].getName(), MSG_VERBOSE ); + } + + ExportCodeSpec exportSpec = new ExportCodeSpec(); + exportSpec.setPackages( packages ); + exportSpec.includeJava( exportSources ); + exportSpec.includeClass( exportClasses ); + exportSpec.includeResources( exportResources ); + exportSpec.includeClassDebugInfo( exportDebugInfo ); + exportSpec.useSubdirectories( true ); + exportSpec.overwriteFiles( overwrite ); + exportSpec.setExportDirectory( dest.getAbsolutePath() ); + + getWorkspace().exportData( exportSpec ); + } + catch( IvjException ex ) + { + throw createBuildException( "Exporting failed!", ex ); + } + } + } + + + //----------------------------------------------------------- + // import + //----------------------------------------------------------- + + + /** + * Do the import. + * + * @param importProject Description of Parameter + * @param srcDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param importClasses Description of Parameter + * @param importResources Description of Parameter + * @param importSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @exception BuildException Description of Exception + */ + public void importFiles( + String importProject, File srcDir, + String[] includePatterns, String[] excludePatterns, + boolean importClasses, boolean importResources, + boolean importSources, boolean useDefaultExcludes ) + throws BuildException + { + + if( importProject == null || "".equals( importProject ) ) + { + throw new BuildException( "The VisualAge for Java project " + + "name is required!" ); + } + + ImportCodeSpec importSpec = new ImportCodeSpec(); + importSpec.setDefaultProject( getVAJProject( importProject ) ); + + DirectoryScanner ds = new DirectoryScanner(); + ds.setBasedir( srcDir ); + ds.setIncludes( includePatterns ); + ds.setExcludes( excludePatterns ); + if( useDefaultExcludes ) + { + ds.addDefaultExcludes(); + } + ds.scan(); + + Vector classes = new Vector(); + Vector sources = new Vector(); + Vector resources = new Vector(); + + scanForImport( srcDir, ds.getIncludedFiles(), classes, sources, resources ); + + StringBuffer summaryLog = new StringBuffer( "Importing " ); + addFilesToImport( importSpec, importClasses, classes, "Class", summaryLog ); + addFilesToImport( importSpec, importSources, sources, "Java", summaryLog ); + addFilesToImport( importSpec, importResources, resources, "Resource", summaryLog ); + importSpec.setResourcePath( srcDir.getAbsolutePath() ); + + summaryLog.append( " into the project '" ); + summaryLog.append( importProject ); + summaryLog.append( "'." ); + + log( summaryLog.toString(), MSG_INFO ); + + try + { + Type[] importedTypes = getWorkspace().importData( importSpec ); + if( importedTypes == null ) + { + throw new BuildException( "Unable to import into Workspace!" ); + } + else + { + log( importedTypes.length + " types imported", MSG_DEBUG ); + for( int i = 0; i < importedTypes.length; i++ ) + { + log( importedTypes[i].getPackage().getName() + + "." + importedTypes[i].getName() + + " into " + importedTypes[i].getProject().getName(), + MSG_DEBUG ); + } + } + } + catch( IvjException ivje ) + { + throw createBuildException( "Error while importing into workspace: ", + ivje ); + } + } + + + //----------------------------------------------------------- + // load + //----------------------------------------------------------- + + /** + * Load specified projects. + * + * @param projectDescriptions Description of Parameter + */ + public void loadProjects( Vector projectDescriptions ) + { + Vector expandedDescs = getExpandedDescriptions( projectDescriptions ); + + // output warnings for projects not found + for( Enumeration e = projectDescriptions.elements(); e.hasMoreElements(); ) + { + VAJProjectDescription d = ( VAJProjectDescription )e.nextElement(); + if( !d.projectFound() ) + { + log( "No Projects match the name " + d.getName(), MSG_WARN ); + } + } + + log( "Loading " + expandedDescs.size() + + " project(s) into workspace", MSG_INFO ); + + for( Enumeration e = expandedDescs.elements(); + e.hasMoreElements(); ) + { + VAJProjectDescription d = ( VAJProjectDescription )e.nextElement(); + + ProjectEdition pe = findProjectEdition( d.getName(), d.getVersion() ); + try + { + log( "Loading '" + d.getName() + "', Version '" + d.getVersion() + + "', into Workspace", MSG_VERBOSE ); + pe.loadIntoWorkspace(); + } + catch( IvjException ex ) + { + throw createBuildException( "Project '" + d.getName() + + "' could not be loaded.", ex ); + } + } + } + + + /** + * return project descriptions containing full project names instead of + * patterns with wildcards. + * + * @param projectDescs Description of Parameter + * @return The ExpandedDescriptions value + */ + private Vector getExpandedDescriptions( Vector projectDescs ) + { + Vector expandedDescs = new Vector( projectDescs.size() ); + try + { + String[] projectNames = + getWorkspace().getRepository().getProjectNames(); + for( int i = 0; i < projectNames.length; i++ ) + { + for( Enumeration e = projectDescs.elements(); + e.hasMoreElements(); ) + { + VAJProjectDescription d = ( VAJProjectDescription )e.nextElement(); + String pattern = d.getName(); + if( VAJWorkspaceScanner.match( pattern, projectNames[i] ) ) + { + d.setProjectFound(); + expandedDescs.addElement( new VAJProjectDescription( + projectNames[i], d.getVersion() ) ); + break; + } + } + } + } + catch( IvjException e ) + { + throw createBuildException( "VA Exception occured: ", e ); + } + + return expandedDescs; + } + + /** + * Adds files to an import specification. Helper method for importFiles() + * + * @param spec import specification + * @param doImport only add files if doImport is true + * @param files the files to add + * @param fileType type of files (Source/Class/Resource) + * @param summaryLog buffer for logging + */ + private void addFilesToImport( + ImportCodeSpec spec, boolean doImport, + Vector files, String fileType, + StringBuffer summaryLog ) + { + + if( doImport ) + { + String[] fileArr = new String[files.size()]; + files.copyInto( fileArr ); + try + { + // here it is assumed that fileType is one of the + // following strings: // "Java", "Class", "Resource" + String methodName = "set" + fileType + "Files"; + Class[] methodParams = new Class[]{fileArr.getClass()}; + java.lang.reflect.Method method = + spec.getClass().getDeclaredMethod( methodName, methodParams ); + method.invoke( spec, new Object[]{fileArr} ); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + if( files.size() > 0 ) + { + logFiles( files, fileType ); + summaryLog.append( files.size() ); + summaryLog.append( " " + fileType.toLowerCase() + " file" ); + summaryLog.append( files.size() > 1 ? "s, " : ", " ); + } + } + } + + /** + * returns a list of project names matching the given pattern + * + * @param pattern Description of Parameter + * @return Description of the Returned Value + */ + private Vector findMatchingProjects( String pattern ) + { + String[] projectNames; + try + { + projectNames = getWorkspace().getRepository().getProjectNames(); + } + catch( IvjException e ) + { + throw createBuildException( "VA Exception occured: ", e ); + } + + Vector matchingProjects = new Vector(); + for( int i = 0; i < projectNames.length; i++ ) + { + if( VAJWorkspaceScanner.match( pattern, projectNames[i] ) ) + { + matchingProjects.addElement( projectNames[i] ); + } + } + + return matchingProjects; + } + + /** + * Finds a specific project edition in the repository. + * + * @param name project name + * @param versionName project version name + * @return com.ibm.ivj.util.base.ProjectEdition the specified edition + */ + private ProjectEdition findProjectEdition( + String name, String versionName ) + { + try + { + ProjectEdition[] editions = null; + editions = getWorkspace().getRepository().getProjectEditions( name ); + + if( editions == null ) + { + throw new BuildException( "Project " + name + " doesn't exist" ); + } + + ProjectEdition pe = null; + for( int i = 0; i < editions.length && pe == null; i++ ) + { + if( versionName.equals( editions[i].getVersionName() ) ) + { + pe = editions[i]; + } + } + if( pe == null ) + { + throw new BuildException( "Version " + versionName + + " of Project " + name + " doesn't exist" ); + } + return pe; + } + catch( IvjException e ) + { + throw createBuildException( "VA Exception occured: ", e ); + } + + } + + /** + * Logs a list of file names to the message log + * + * @param fileNames java.util.Vector file names to be logged + * @param fileType Description of Parameter + */ + private void logFiles( Vector fileNames, String fileType ) + { + log( fileType + " files found for import:", MSG_VERBOSE ); + for( Enumeration e = fileNames.elements(); e.hasMoreElements(); ) + { + log( " " + e.nextElement(), MSG_VERBOSE ); + } + } + + + /** + * Sort the files into classes, sources, and resources. + * + * @param dir Description of Parameter + * @param files Description of Parameter + * @param classes Description of Parameter + * @param sources Description of Parameter + * @param resources Description of Parameter + */ + private void scanForImport( + File dir, + String[] files, + Vector classes, + Vector sources, + Vector resources ) + { + for( int i = 0; i < files.length; i++ ) + { + String file = ( new File( dir, files[i] ) ).getAbsolutePath(); + if( file.endsWith( ".java" ) || file.endsWith( ".JAVA" ) ) + { + sources.addElement( file ); + } + else + if( file.endsWith( ".class" ) || file.endsWith( ".CLASS" ) ) + { + classes.addElement( file ); + } + else + { + // for resources VA expects the path relative to the resource path + resources.addElement( files[i] ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java new file mode 100644 index 000000000..9e2105863 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import org.apache.tools.ant.BuildException; + +/** + * Type class. Holds information about a project edition. + * + * @author RT + * @author: Wolf Siberski + */ +public class VAJProjectDescription +{ + private String name; + private boolean projectFound; + private String version; + + public VAJProjectDescription() { } + + public VAJProjectDescription( String n, String v ) + { + name = n; + version = v; + } + + public void setName( String newName ) + { + if( newName == null || newName.equals( "" ) ) + { + throw new BuildException( "name attribute must be set" ); + } + name = newName; + } + + public void setProjectFound() + { + projectFound = true; + } + + public void setVersion( String newVersion ) + { + if( newVersion == null || newVersion.equals( "" ) ) + { + throw new BuildException( "version attribute must be set" ); + } + version = newVersion; + } + + public String getName() + { + return name; + } + + public String getVersion() + { + return version; + } + + public boolean projectFound() + { + return projectFound; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java new file mode 100644 index 000000000..c14253574 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Helper class for VAJ tasks. Holds Workspace singleton and wraps IvjExceptions + * into BuildExceptions + * + * @author Wolf Siberski, TUI Infotec GmbH + */ +class VAJRemoteUtil implements VAJUtil +{ + // calling task + Task caller; + + // VAJ remote tool server + String remoteServer; + + public VAJRemoteUtil( Task caller, String remote ) + { + this.caller = caller; + this.remoteServer = remote; + } + + /** + * export the array of Packages + * + * @param destDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param exportClasses Description of Parameter + * @param exportDebugInfo Description of Parameter + * @param exportResources Description of Parameter + * @param exportSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @param overwrite Description of Parameter + */ + public void exportPackages( File destDir, + String[] includePatterns, String[] excludePatterns, + boolean exportClasses, boolean exportDebugInfo, boolean exportResources, + boolean exportSources, boolean useDefaultExcludes, boolean overwrite ) + { + try + { + String request = "http://" + remoteServer + "/servlet/vajexport?" + + VAJExportServlet.WITH_DEBUG_INFO + "=" + exportDebugInfo + "&" + + VAJExportServlet.OVERWRITE_PARAM + "=" + overwrite + "&" + + assembleImportExportParams( destDir, + includePatterns, excludePatterns, + exportClasses, exportResources, + exportSources, useDefaultExcludes ); + sendRequest( request ); + } + catch( Exception ex ) + { + throw new BuildException( ex ); + } + } + + /** + * Do the import. + * + * @param importProject Description of Parameter + * @param srcDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param importClasses Description of Parameter + * @param importResources Description of Parameter + * @param importSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + */ + public void importFiles( + String importProject, File srcDir, + String[] includePatterns, String[] excludePatterns, + boolean importClasses, boolean importResources, + boolean importSources, boolean useDefaultExcludes ) + { + try + { + String request = "http://" + remoteServer + "/servlet/vajimport?" + + VAJImportServlet.PROJECT_NAME_PARAM + "=" + + importProject + "&" + + assembleImportExportParams( srcDir, + includePatterns, excludePatterns, + importClasses, importResources, + importSources, useDefaultExcludes ); + sendRequest( request ); + } + catch( Exception ex ) + { + throw new BuildException( ex ); + } + + } + + /** + * Load specified projects. + * + * @param projectDescriptions Description of Parameter + */ + public void loadProjects( Vector projectDescriptions ) + { + try + { + String request = "http://" + remoteServer + "/servlet/vajload?"; + String delimiter = ""; + for( Enumeration e = projectDescriptions.elements(); e.hasMoreElements(); ) + { + VAJProjectDescription pd = ( VAJProjectDescription )e.nextElement(); + request = request + + delimiter + VAJLoadServlet.PROJECT_NAME_PARAM + + "=" + pd.getName().replace( ' ', '+' ) + + "&" + VAJLoadServlet.VERSION_PARAM + + "=" + pd.getVersion().replace( ' ', '+' ); + //the first param needs no delimiter, but all other + delimiter = "&"; + } + sendRequest( request ); + } + catch( Exception ex ) + { + throw new BuildException( ex ); + } + } + + /** + * logs a message. + * + * @param msg Description of Parameter + * @param level Description of Parameter + */ + public void log( String msg, int level ) + { + caller.log( msg, level ); + } + + /** + * Assemble string for parameters common for import and export Helper method + * to remove double code. + * + * @param dir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param includeClasses Description of Parameter + * @param includeResources Description of Parameter + * @param includeSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @return Description of the Returned Value + */ + private String assembleImportExportParams( + File dir, + String[] includePatterns, String[] excludePatterns, + boolean includeClasses, boolean includeResources, + boolean includeSources, boolean useDefaultExcludes ) + { + String result = + VAJToolsServlet.DIR_PARAM + "=" + + dir.getAbsolutePath().replace( '\\', '/' ) + "&" + + VAJToolsServlet.CLASSES_PARAM + "=" + includeClasses + "&" + + VAJToolsServlet.RESOURCES_PARAM + "=" + includeResources + "&" + + VAJToolsServlet.SOURCES_PARAM + "=" + includeSources + "&" + + VAJToolsServlet.DEFAULT_EXCLUDES_PARAM + "=" + useDefaultExcludes; + + if( includePatterns != null ) + { + for( int i = 0; i < includePatterns.length; i++ ) + { + result = result + "&" + VAJExportServlet.INCLUDE_PARAM + "=" + + includePatterns[i].replace( ' ', '+' ).replace( '\\', '/' ); + } + } + if( excludePatterns != null ) + { + for( int i = 0; i < excludePatterns.length; i++ ) + { + result = result + "&" + VAJExportServlet.EXCLUDE_PARAM + "=" + + excludePatterns[i].replace( ' ', '+' ).replace( '\\', '/' ); + } + } + + return result; + } + + /** + * Sends a servlet request. + * + * @param request Description of Parameter + */ + private void sendRequest( String request ) + { + boolean requestFailed = false; + try + { + log( "Request: " + request, MSG_DEBUG ); + + //must be HTTP connection + URL requestUrl = new URL( request ); + HttpURLConnection connection = + ( HttpURLConnection )requestUrl.openConnection(); + + InputStream is = null; + // retry three times + for( int i = 0; i < 3; i++ ) + { + try + { + is = connection.getInputStream(); + break; + } + catch( IOException ex ) + { + } + } + if( is == null ) + { + log( "Can't get " + request, MSG_ERR ); + throw new BuildException( "Couldn't execute " + request ); + } + + // log the response + BufferedReader br = new BufferedReader( new InputStreamReader( is ) ); + String line = br.readLine(); + while( line != null ) + { + int level = MSG_ERR; + try + { + // the first char of each line contains the log level + level = Integer.parseInt( line.substring( 0, 1 ) ); + if( level == MSG_ERR ) + { + requestFailed = true; + } + } + catch( Exception e ) + { + log( "Response line doesn't contain log level!", MSG_ERR ); + } + log( line.substring( 2 ), level ); + line = br.readLine(); + } + + } + catch( IOException ex ) + { + log( "Error sending tool request to VAJ" + ex, MSG_ERR ); + throw new BuildException( "Couldn't execute " + request ); + } + if( requestFailed ) + { + throw new BuildException( "VAJ tool request failed" ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java new file mode 100644 index 000000000..3c19052ec --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +/** + * Super class for all VAJ tasks. Contains common attributes (remoteServer) and + * util methods + * + * @author: Wolf Siberski + */ +import org.apache.tools.ant.Task; + + +public class VAJTask extends Task +{ + + // server name / port of VAJ remote tool api server + protected String remoteServer = null; + + // holds the appropriate VAJUtil implementation + private VAJUtil util = null; + + /** + * Set remote server attribute + * + * @param remoteServer The new Remote value + */ + public void setRemote( String remoteServer ) + { + this.remoteServer = remoteServer; + } + + + /** + * returns the VAJUtil implementation + * + * @return The Util value + */ + protected VAJUtil getUtil() + { + if( util == null ) + { + if( remoteServer == null ) + { + util = new VAJLocalToolUtil(); + } + else + { + util = new VAJRemoteUtil( this, remoteServer ); + } + } + return util; + } + + /** + * Adaption of VAJLocalUtil to Task context. + * + * @author RT + */ + class VAJLocalToolUtil extends VAJLocalUtil + { + public void log( String msg, int level ) + { + VAJTask.this.log( msg, level ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java new file mode 100644 index 000000000..c27e163b9 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.StringUtils; + +/** + * Abstract base class to provide common services for the VAJ tool API servlets + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public abstract class VAJToolsServlet extends HttpServlet +{ + + // constants for servlet param names + public final static String DIR_PARAM = "dir"; + public final static String INCLUDE_PARAM = "include"; + public final static String EXCLUDE_PARAM = "exclude"; + public final static String CLASSES_PARAM = "cls"; + public final static String SOURCES_PARAM = "src"; + public final static String RESOURCES_PARAM = "res"; + public final static String DEFAULT_EXCLUDES_PARAM = "dex"; + public final static String PROJECT_NAME_PARAM = "project"; + + // current request + HttpServletRequest request; + + // response to current request + HttpServletResponse response; + + // implementation of VAJUtil used by the servlet + VAJUtil util; + + /** + * Respond to a HTTP request. This method initializes the servlet and + * handles errors. The real work is done in the abstract method + * executeRequest() + * + * @param req Description of Parameter + * @param res Description of Parameter + * @exception ServletException Description of Exception + * @exception IOException Description of Exception + */ + public void doGet( HttpServletRequest req, HttpServletResponse res ) + throws ServletException, IOException + { + try + { + response = res; + request = req; + initRequest(); + executeRequest(); + } + catch( BuildException e ) + { + util.log( "Error occured: " + e.getMessage(), VAJUtil.MSG_ERR ); + } + catch( Exception e ) + { + try + { + if( !( e instanceof BuildException ) ) + { + String trace = StringUtils.getStackTrace( e ); + util.log( "Program error in " + this.getClass().getName() + + ":\n" + trace, VAJUtil.MSG_ERR ); + } + } + catch( Throwable t ) + { + t.printStackTrace(); + } + finally + { + if( !( e instanceof BuildException ) ) + { + throw new ServletException( e.getMessage() ); + } + } + } + } + + /** + * Get the boolean value of a parameter. + * + * @param param Description of Parameter + * @return The BooleanParam value + */ + protected boolean getBooleanParam( String param ) + { + return getBooleanParam( param, false ); + } + + /** + * Get the boolean value of a parameter, with a default value if the + * parameter hasn't been passed to the servlet. + * + * @param param Description of Parameter + * @param defaultValue Description of Parameter + * @return The BooleanParam value + */ + protected boolean getBooleanParam( String param, boolean defaultValue ) + { + String value = getFirstParamValueString( param ); + if( value != null ) + { + return toBoolean( value ); + } + else + { + return defaultValue; + } + } + + /** + * Returns the first encountered value for a parameter. + * + * @param param Description of Parameter + * @return The FirstParamValueString value + */ + protected String getFirstParamValueString( String param ) + { + String[] paramValuesArray = request.getParameterValues( param ); + if( paramValuesArray == null ) + { + return null; + } + return paramValuesArray[0]; + } + + /** + * Returns all values for a parameter. + * + * @param param Description of Parameter + * @return The ParamValues value + */ + protected String[] getParamValues( String param ) + { + return request.getParameterValues( param ); + } + + + /** + * Execute the request by calling the appropriate VAJ tool API methods. This + * method must be implemented by the concrete servlets + */ + protected abstract void executeRequest(); + + /** + * initialize the servlet. + * + * @exception IOException Description of Exception + */ + protected void initRequest() + throws IOException + { + response.setContentType( "text/ascii" ); + if( util == null ) + { + util = new VAJLocalServletUtil(); + } + } + + /** + * A utility method to translate the strings "yes", "true", and "ok" to + * boolean true, and everything else to false. + * + * @param string Description of Parameter + * @return Description of the Returned Value + */ + protected boolean toBoolean( String string ) + { + String lower = string.toLowerCase(); + return ( lower.equals( "yes" ) || lower.equals( "true" ) || lower.equals( "ok" ) ); + } + + /** + * Get the VAJUtil implementation + * + * @return The Util value + */ + VAJUtil getUtil() + { + return util; + } + + /** + * Adaptation of VAJUtil for servlet context. + * + * @author RT + */ + class VAJLocalServletUtil extends VAJLocalUtil + { + public void log( String msg, int level ) + { + try + { + if( msg != null ) + { + msg = msg.replace( '\r', ' ' ); + int i = 0; + while( i < msg.length() ) + { + int nlPos = msg.indexOf( '\n', i ); + if( nlPos == -1 ) + { + nlPos = msg.length(); + } + response.getWriter().println( Integer.toString( level ) + + " " + msg.substring( i, nlPos ) ); + i = nlPos + 1; + } + } + } + catch( IOException e ) + { + throw new BuildException( "logging failed. msg was: " + + e.getMessage() ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java new file mode 100644 index 000000000..203b97252 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; +import java.util.Vector; + +/** + * Helper interface for VAJ tasks. Encapsulates the interface to the VAJ tool + * API. + * + * @author Wolf Siberski, TUI Infotec GmbH + */ +interface VAJUtil +{ + // log levels + public final static int MSG_DEBUG = 4; + public final static int MSG_ERR = 0; + public final static int MSG_INFO = 2; + public final static int MSG_VERBOSE = 3; + public final static int MSG_WARN = 1; + + /** + * export the array of Packages + * + * @param dest Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param exportClasses Description of Parameter + * @param exportDebugInfo Description of Parameter + * @param exportResources Description of Parameter + * @param exportSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @param overwrite Description of Parameter + */ + void exportPackages( + File dest, + String[] includePatterns, String[] excludePatterns, + boolean exportClasses, boolean exportDebugInfo, + boolean exportResources, boolean exportSources, + boolean useDefaultExcludes, boolean overwrite ); + + /** + * Do the import. + * + * @param importProject Description of Parameter + * @param srcDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param importClasses Description of Parameter + * @param importResources Description of Parameter + * @param importSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + */ + void importFiles( + String importProject, File srcDir, + String[] includePatterns, String[] excludePatterns, + boolean importClasses, boolean importResources, + boolean importSources, boolean useDefaultExcludes ); + + /** + * Load specified projects. + * + * @param projectDescriptions Description of Parameter + */ + void loadProjects( Vector projectDescriptions ); + + /** + * Logs a message with the specified log level. + * + * @param msg Description of Parameter + * @param level Description of Parameter + */ + void log( String msg, int level ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java new file mode 100644 index 000000000..9624722af --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import com.ibm.ivj.util.base.IvjException; +import com.ibm.ivj.util.base.Package; +import com.ibm.ivj.util.base.Project; +import java.io.File; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.DirectoryScanner; + +/** + * Class for scanning a Visual Age for Java workspace for packages matching a + * certain criteria.

      + * + * These criteria consist of a set of include and exclude patterns. With these + * patterns, you can select which packages you want to have included, and which + * packages you want to have excluded. You can add patterns to be excluded by + * default with the addDefaultExcludes method. The patters that are excluded by + * default include + *

        + *
      • IBM*\**
      • + *
      • Java class libraries\**
      • + *
      • Sun class libraries*\**
      • + *
      • JSP Page Compile Generated Code\**
      • + *
      • VisualAge*\**
      • + *
      + *

      + * + * This class works like DirectoryScanner. + * + * @author Wolf Siberski, TUI Infotec (based on Arnout J. Kuipers + * DirectoryScanner) + * @see org.apache.tools.ant.DirectoryScanner + */ +class VAJWorkspaceScanner extends DirectoryScanner +{ + + // Patterns that should be excluded by default. + private final static String[] DEFAULTEXCLUDES = + { + "IBM*/**", + "Java class libraries/**", + "Sun class libraries*/**", + "JSP Page Compile Generated Code/**", + "VisualAge*/**", + }; + + // The packages that where found and matched at least + // one includes, and matched no excludes. + private Vector packagesIncluded = new Vector(); + + /** + * Matches a string against a pattern. The pattern contains two special + * characters: '*' which means zero or more characters, '?' which means one + * and only one character. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string that must be matched against the pattern + * @return true when the string matches against the pattern, + * false otherwise. + */ + protected static boolean match( String pattern, String str ) + { + return DirectoryScanner.match( pattern, str ); + } + + /** + * Get the names of the packages that matched at least one of the include + * patterns, and didn't match one of the exclude patterns. + * + * @return the matching packages + */ + public Package[] getIncludedPackages() + { + int count = packagesIncluded.size(); + Package[] packages = new Package[count]; + for( int i = 0; i < count; i++ ) + { + packages[i] = ( Package )packagesIncluded.elementAt( i ); + } + return packages; + } + + /** + * Adds the array with default exclusions to the current exclusions set. + */ + public void addDefaultExcludes() + { + int excludesLength = excludes == null ? 0 : excludes.length; + String[] newExcludes; + newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length]; + if( excludesLength > 0 ) + { + System.arraycopy( excludes, 0, newExcludes, 0, excludesLength ); + } + for( int i = 0; i < DEFAULTEXCLUDES.length; i++ ) + { + newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i]. + replace( '/', File.separatorChar ). + replace( '\\', File.separatorChar ); + } + excludes = newExcludes; + } + + /** + * Finds all Projects specified in include patterns. + * + * @return the projects + */ + public Vector findMatchingProjects() + { + Project[] projects = VAJLocalUtil.getWorkspace().getProjects(); + + Vector matchingProjects = new Vector(); + + boolean allProjectsMatch = false; + for( int i = 0; i < projects.length; i++ ) + { + Project project = projects[i]; + for( int j = 0; j < includes.length && !allProjectsMatch; j++ ) + { + StringTokenizer tok = + new StringTokenizer( includes[j], File.separator ); + String projectNamePattern = tok.nextToken(); + if( projectNamePattern.equals( "**" ) ) + { + // if an include pattern starts with '**', + // all projects match + allProjectsMatch = true; + } + else + if( match( projectNamePattern, project.getName() ) ) + { + matchingProjects.addElement( project ); + break; + } + } + } + + if( allProjectsMatch ) + { + matchingProjects = new Vector(); + for( int i = 0; i < projects.length; i++ ) + { + matchingProjects.addElement( projects[i] ); + } + } + + return matchingProjects; + } + + /** + * Scans the workspace for packages that match at least one include pattern, + * and don't match any exclude patterns. + */ + public void scan() + { + if( includes == null ) + { + // No includes supplied, so set it to 'matches all' + includes = new String[1]; + includes[0] = "**"; + } + if( excludes == null ) + { + excludes = new String[0]; + } + + // only scan projects which are included in at least one include pattern + Vector matchingProjects = findMatchingProjects(); + for( Enumeration e = matchingProjects.elements(); e.hasMoreElements(); ) + { + Project project = ( Project )e.nextElement(); + scanProject( project ); + } + } + + /** + * Scans a project for packages that match at least one include pattern, and + * don't match any exclude patterns. + * + * @param project Description of Parameter + */ + public void scanProject( Project project ) + { + try + { + Package[] packages = project.getPackages(); + if( packages != null ) + { + for( int i = 0; i < packages.length; i++ ) + { + Package item = packages[i]; + // replace '.' by file seperator because the patterns are + // using file seperator syntax (and we can use the match + // methods this way). + String name = + project.getName() + + File.separator + + item.getName().replace( '.', File.separatorChar ); + if( isIncluded( name ) && !isExcluded( name ) ) + { + packagesIncluded.addElement( item ); + } + } + } + } + catch( IvjException e ) + { + throw VAJLocalUtil.createBuildException( "VA Exception occured: ", e ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/default.ini b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/default.ini new file mode 100644 index 000000000..1ccb8944f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/ide/default.ini @@ -0,0 +1,4 @@ +Name=Ant +Version=0.1 +Help-Item=Ant Help,doc/VAJAntTool.html +Menu-Items=Ant Build,org.apache.tools.ant.taskdefs.optional.ide.VAJAntTool,-P; diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java new file mode 100644 index 000000000..912a18bb9 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.javacc; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Path; + +/** + * Taskdef for the JJTree compiler compiler. + * + * @author thomas.haas@softwired-inc.com + * @author Michael Saunders michael@amtec.com + * + */ +public class JJTree extends Task +{ + + // keys to optional attributes + private final static String BUILD_NODE_FILES = "BUILD_NODE_FILES"; + private final static String MULTI = "MULTI"; + private final static String NODE_DEFAULT_VOID = "NODE_DEFAULT_VOID"; + private final static String NODE_FACTORY = "NODE_FACTORY"; + private final static String NODE_SCOPE_HOOK = "NODE_SCOPE_HOOK"; + private final static String NODE_USES_PARSER = "NODE_USES_PARSER"; + private final static String STATIC = "STATIC"; + private final static String VISITOR = "VISITOR"; + + private final static String NODE_PACKAGE = "NODE_PACKAGE"; + private final static String VISITOR_EXCEPTION = "VISITOR_EXCEPTION"; + private final static String NODE_PREFIX = "NODE_PREFIX"; + + private final Hashtable optionalAttrs = new Hashtable(); + + // required attributes + private File outputDirectory = null; + private File target = null; + private File javaccHome = null; + + private CommandlineJava cmdl = new CommandlineJava(); + + public JJTree() + { + cmdl.setVm( "java" ); + cmdl.setClassname( "COM.sun.labs.jjtree.Main" ); + } + + + public void setBuildnodefiles( boolean buildNodeFiles ) + { + optionalAttrs.put( BUILD_NODE_FILES, new Boolean( buildNodeFiles ) ); + } + + public void setJavacchome( File javaccHome ) + { + this.javaccHome = javaccHome; + } + + public void setMulti( boolean multi ) + { + optionalAttrs.put( MULTI, new Boolean( multi ) ); + } + + public void setNodedefaultvoid( boolean nodeDefaultVoid ) + { + optionalAttrs.put( NODE_DEFAULT_VOID, new Boolean( nodeDefaultVoid ) ); + } + + public void setNodefactory( boolean nodeFactory ) + { + optionalAttrs.put( NODE_FACTORY, new Boolean( nodeFactory ) ); + } + + public void setNodepackage( String nodePackage ) + { + optionalAttrs.put( NODE_PACKAGE, new String( nodePackage ) ); + } + + public void setNodeprefix( String nodePrefix ) + { + optionalAttrs.put( NODE_PREFIX, new String( nodePrefix ) ); + } + + public void setNodescopehook( boolean nodeScopeHook ) + { + optionalAttrs.put( NODE_SCOPE_HOOK, new Boolean( nodeScopeHook ) ); + } + + public void setNodeusesparser( boolean nodeUsesParser ) + { + optionalAttrs.put( NODE_USES_PARSER, new Boolean( nodeUsesParser ) ); + } + + public void setOutputdirectory( File outputDirectory ) + { + this.outputDirectory = outputDirectory; + } + + public void setStatic( boolean staticParser ) + { + optionalAttrs.put( STATIC, new Boolean( staticParser ) ); + } + + public void setTarget( File target ) + { + this.target = target; + } + + public void setVisitor( boolean visitor ) + { + optionalAttrs.put( VISITOR, new Boolean( visitor ) ); + } + + public void setVisitorException( String visitorException ) + { + optionalAttrs.put( VISITOR_EXCEPTION, new String( visitorException ) ); + } + + public void execute() + throws BuildException + { + + // load command line with optional attributes + Enumeration iter = optionalAttrs.keys(); + while( iter.hasMoreElements() ) + { + String name = ( String )iter.nextElement(); + Object value = optionalAttrs.get( name ); + cmdl.createArgument().setValue( "-" + name + ":" + value.toString() ); + } + + if( target == null || !target.isFile() ) + { + throw new BuildException( "Invalid target: " + target ); + } + + // use the directory containing the target as the output directory + if( outputDirectory == null ) + { + outputDirectory = new File( target.getParent() ); + } + if( !outputDirectory.isDirectory() ) + { + throw new BuildException( "'outputdirectory' " + outputDirectory + " is not a directory." ); + } + // convert backslashes to slashes, otherwise jjtree will put this as + // comments and this seems to confuse javacc + cmdl.createArgument().setValue( + "-OUTPUT_DIRECTORY:" + outputDirectory.getAbsolutePath().replace( '\\', '/' ) ); + + String targetName = target.getName(); + final File javaFile = new File( outputDirectory, + targetName.substring( 0, targetName.indexOf( ".jjt" ) ) + ".jj" ); + if( javaFile.exists() && target.lastModified() < javaFile.lastModified() ) + { + project.log( "Target is already built - skipping (" + target + ")" ); + return; + } + cmdl.createArgument().setValue( target.getAbsolutePath() ); + + if( javaccHome == null || !javaccHome.isDirectory() ) + { + throw new BuildException( "Javacchome not set." ); + } + final Path classpath = cmdl.createClasspath( project ); + classpath.createPathElement().setPath( javaccHome.getAbsolutePath() + + "/JavaCC.zip" ); + classpath.addJavaRuntime(); + + final Commandline.Argument arg = cmdl.createVmArgument(); + arg.setValue( "-mx140M" ); + arg.setValue( "-Dinstall.root=" + javaccHome.getAbsolutePath() ); + + final Execute process = + new Execute( new LogStreamHandler( this, + Project.MSG_INFO, + Project.MSG_INFO ), + null ); + log( cmdl.toString(), Project.MSG_VERBOSE ); + process.setCommandline( cmdl.getCommandline() ); + + try + { + if( process.execute() != 0 ) + { + throw new BuildException( "JJTree failed." ); + } + } + catch( IOException e ) + { + throw new BuildException( "Failed to launch JJTree: " + e ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java new file mode 100644 index 000000000..f85e9089c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.javacc; +import java.io.File; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Path; + +/** + * Taskdef for the JavaCC compiler compiler. + * + * @author thomas.haas@softwired-inc.com + * @author Michael Saunders michael@amtec.com + * + */ +public class JavaCC extends Task +{ + + // keys to optional attributes + private final static String LOOKAHEAD = "LOOKAHEAD"; + private final static String CHOICE_AMBIGUITY_CHECK = "CHOICE_AMBIGUITY_CHECK"; + private final static String OTHER_AMBIGUITY_CHECK = "OTHER_AMBIGUITY_CHECK"; + + private final static String STATIC = "STATIC"; + private final static String DEBUG_PARSER = "DEBUG_PARSER"; + private final static String DEBUG_LOOKAHEAD = "DEBUG_LOOKAHEAD"; + private final static String DEBUG_TOKEN_MANAGER = "DEBUG_TOKEN_MANAGER"; + private final static String OPTIMIZE_TOKEN_MANAGER = "OPTIMIZE_TOKEN_MANAGER"; + private final static String ERROR_REPORTING = "ERROR_REPORTING"; + private final static String JAVA_UNICODE_ESCAPE = "JAVA_UNICODE_ESCAPE"; + private final static String UNICODE_INPUT = "UNICODE_INPUT"; + private final static String IGNORE_CASE = "IGNORE_CASE"; + private final static String COMMON_TOKEN_ACTION = "COMMON_TOKEN_ACTION"; + private final static String USER_TOKEN_MANAGER = "USER_TOKEN_MANAGER"; + private final static String USER_CHAR_STREAM = "USER_CHAR_STREAM"; + private final static String BUILD_PARSER = "BUILD_PARSER"; + private final static String BUILD_TOKEN_MANAGER = "BUILD_TOKEN_MANAGER"; + private final static String SANITY_CHECK = "SANITY_CHECK"; + private final static String FORCE_LA_CHECK = "FORCE_LA_CHECK"; + private final static String CACHE_TOKENS = "CACHE_TOKENS"; + + private final Hashtable optionalAttrs = new Hashtable(); + + // required attributes + private File outputDirectory = null; + private File target = null; + private File javaccHome = null; + + private CommandlineJava cmdl = new CommandlineJava(); + + public JavaCC() + { + cmdl.setVm( "java" ); + cmdl.setClassname( "COM.sun.labs.javacc.Main" ); + } + + public void setBuildparser( boolean buildParser ) + { + optionalAttrs.put( BUILD_PARSER, new Boolean( buildParser ) ); + } + + public void setBuildtokenmanager( boolean buildTokenManager ) + { + optionalAttrs.put( BUILD_TOKEN_MANAGER, new Boolean( buildTokenManager ) ); + } + + public void setCachetokens( boolean cacheTokens ) + { + optionalAttrs.put( CACHE_TOKENS, new Boolean( cacheTokens ) ); + } + + public void setChoiceambiguitycheck( int choiceAmbiguityCheck ) + { + optionalAttrs.put( CHOICE_AMBIGUITY_CHECK, new Integer( choiceAmbiguityCheck ) ); + } + + public void setCommontokenaction( boolean commonTokenAction ) + { + optionalAttrs.put( COMMON_TOKEN_ACTION, new Boolean( commonTokenAction ) ); + } + + public void setDebuglookahead( boolean debugLookahead ) + { + optionalAttrs.put( DEBUG_LOOKAHEAD, new Boolean( debugLookahead ) ); + } + + public void setDebugparser( boolean debugParser ) + { + optionalAttrs.put( DEBUG_PARSER, new Boolean( debugParser ) ); + } + + public void setDebugtokenmanager( boolean debugTokenManager ) + { + optionalAttrs.put( DEBUG_TOKEN_MANAGER, new Boolean( debugTokenManager ) ); + } + + public void setErrorreporting( boolean errorReporting ) + { + optionalAttrs.put( ERROR_REPORTING, new Boolean( errorReporting ) ); + } + + public void setForcelacheck( boolean forceLACheck ) + { + optionalAttrs.put( FORCE_LA_CHECK, new Boolean( forceLACheck ) ); + } + + public void setIgnorecase( boolean ignoreCase ) + { + optionalAttrs.put( IGNORE_CASE, new Boolean( ignoreCase ) ); + } + + public void setJavacchome( File javaccHome ) + { + this.javaccHome = javaccHome; + } + + public void setJavaunicodeescape( boolean javaUnicodeEscape ) + { + optionalAttrs.put( JAVA_UNICODE_ESCAPE, new Boolean( javaUnicodeEscape ) ); + } + + + public void setLookahead( int lookahead ) + { + optionalAttrs.put( LOOKAHEAD, new Integer( lookahead ) ); + } + + public void setOptimizetokenmanager( boolean optimizeTokenManager ) + { + optionalAttrs.put( OPTIMIZE_TOKEN_MANAGER, new Boolean( optimizeTokenManager ) ); + } + + public void setOtherambiguityCheck( int otherAmbiguityCheck ) + { + optionalAttrs.put( OTHER_AMBIGUITY_CHECK, new Integer( otherAmbiguityCheck ) ); + } + + public void setOutputdirectory( File outputDirectory ) + { + this.outputDirectory = outputDirectory; + } + + public void setSanitycheck( boolean sanityCheck ) + { + optionalAttrs.put( SANITY_CHECK, new Boolean( sanityCheck ) ); + } + + public void setStatic( boolean staticParser ) + { + optionalAttrs.put( STATIC, new Boolean( staticParser ) ); + } + + public void setTarget( File target ) + { + this.target = target; + } + + public void setUnicodeinput( boolean unicodeInput ) + { + optionalAttrs.put( UNICODE_INPUT, new Boolean( unicodeInput ) ); + } + + public void setUsercharstream( boolean userCharStream ) + { + optionalAttrs.put( USER_CHAR_STREAM, new Boolean( userCharStream ) ); + } + + public void setUsertokenmanager( boolean userTokenManager ) + { + optionalAttrs.put( USER_TOKEN_MANAGER, new Boolean( userTokenManager ) ); + } + + public void execute() + throws BuildException + { + + // load command line with optional attributes + Enumeration iter = optionalAttrs.keys(); + while( iter.hasMoreElements() ) + { + String name = ( String )iter.nextElement(); + Object value = optionalAttrs.get( name ); + cmdl.createArgument().setValue( "-" + name + ":" + value.toString() ); + } + + // check the target is a file + if( target == null || !target.isFile() ) + { + throw new BuildException( "Invalid target: " + target ); + } + + // use the directory containing the target as the output directory + if( outputDirectory == null ) + { + outputDirectory = new File( target.getParent() ); + } + else if( !outputDirectory.isDirectory() ) + { + throw new BuildException( "Outputdir not a directory." ); + } + cmdl.createArgument().setValue( + "-OUTPUT_DIRECTORY:" + outputDirectory.getAbsolutePath() ); + + // determine if the generated java file is up-to-date + final File javaFile = getOutputJavaFile( outputDirectory, target ); + if( javaFile.exists() && target.lastModified() < javaFile.lastModified() ) + { + log( "Target is already built - skipping (" + target + ")", Project.MSG_VERBOSE ); + return; + } + cmdl.createArgument().setValue( target.getAbsolutePath() ); + + if( javaccHome == null || !javaccHome.isDirectory() ) + { + throw new BuildException( "Javacchome not set." ); + } + final Path classpath = cmdl.createClasspath( project ); + classpath.createPathElement().setPath( javaccHome.getAbsolutePath() + + "/JavaCC.zip" ); + classpath.addJavaRuntime(); + + final Commandline.Argument arg = cmdl.createVmArgument(); + arg.setValue( "-mx140M" ); + arg.setValue( "-Dinstall.root=" + javaccHome.getAbsolutePath() ); + + Execute.runCommand( this, cmdl.getCommandline() ); + } + + /** + * Determines the output Java file to be generated by the given grammar + * file. + * + * @param outputdir Description of Parameter + * @param srcfile Description of Parameter + * @return The OutputJavaFile value + */ + private File getOutputJavaFile( File outputdir, File srcfile ) + { + String path = srcfile.getPath(); + + // Extract file's base-name + int startBasename = path.lastIndexOf( File.separator ); + if( startBasename != -1 ) + { + path = path.substring( startBasename + 1 ); + } + + // Replace the file's extension with '.java' + int startExtn = path.lastIndexOf( '.' ); + if( startExtn != -1 ) + { + path = path.substring( 0, startExtn ) + ".java"; + } + else + { + path += ".java"; + } + + // Change the directory + if( outputdir != null ) + { + path = outputdir + File.separator + path; + } + + return new File( path ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java new file mode 100644 index 000000000..c8e6da765 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java @@ -0,0 +1,453 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jdepend; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.PathTokenizer; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteWatchdog; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Ant task to run JDepend tests.

      + * + * JDepend is a tool to generate design quality metrics for each Java package. + * It has been initially created by Mike Clark. JDepend can be found at + * http://www.clarkware.com/software/JDepend.html . The current + * implementation spawn a new Java VM. + * + * @author Jerome Lacoste + * @author Rob Oxspring + */ +public class JDependTask extends Task +{ + + /** + * No problems with this test. + */ + private final static int SUCCESS = 0; + /** + * An error occured. + */ + private final static int ERRORS = 1; + private boolean _haltonerror = false; + private boolean _fork = false; + //private Integer _timeout = null; + + private String _jvm = null; + private String format = "text"; + private Path _compileClasspath; + private File _dir; + + // optional attributes + private File _outputFile; + //private CommandlineJava commandline = new CommandlineJava(); + + // required attributes + private Path _sourcesPath; + + public JDependTask() { } + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( _compileClasspath == null ) + { + _compileClasspath = classpath; + } + else + { + _compileClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * The directory to invoke the VM in. Ignored if no JVM is forked. + * + * @param dir the directory to invoke the JVM from. + * @see #setFork(boolean) + */ + public void setDir( File dir ) + { + _dir = dir; + } + + /** + * Tells whether a JVM should be forked for the task. Default: false. + * + * @param value true if a JVM should be forked, otherwise false + * + */ + public void setFork( boolean value ) + { + _fork = value; + } + + + public void setFormat( FormatAttribute ea ) + { + format = ea.getValue(); + } + + /** + * Halt on Failure? default: false. + * + * @param value The new Haltonerror value + */ + public void setHaltonerror( boolean value ) + { + _haltonerror = value; + } + + /** + * Set a new VM to execute the task. Default is java . Ignored if + * no JVM is forked. + * + * @param value the new VM to use instead of java + * @see #setFork(boolean) + */ + public void setJvm( String value ) + { + _jvm = value; + + } + + /* + * public void setTimeout(Integer value) { + * _timeout = value; + * } + * public Integer getTimeout() { + * return _timeout; + * } + */ + public void setOutputFile( File outputFile ) + { + _outputFile = outputFile; + } + + /** + * Gets the classpath to be used for this compilation. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return _compileClasspath; + } + + public File getDir() + { + return _dir; + } + + public boolean getFork() + { + return _fork; + } + + public boolean getHaltonerror() + { + return _haltonerror; + } + + public File getOutputFile() + { + return _outputFile; + } + + /** + * Gets the sourcepath. + * + * @return The Sourcespath value + */ + public Path getSourcespath() + { + return _sourcesPath; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( _compileClasspath == null ) + { + _compileClasspath = new Path( project ); + } + return _compileClasspath.createPath(); + } + + /** + * Create a new JVM argument. Ignored if no JVM is forked. + * + * @param commandline Description of Parameter + * @return create a new JVM argument so that any argument can be passed to + * the JVM. + * @see #setFork(boolean) + */ + public Commandline.Argument createJvmarg( CommandlineJava commandline ) + { + return commandline.createVmArgument(); + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createSourcespath() + { + if( _sourcesPath == null ) + { + _sourcesPath = new Path( project ); + } + return _sourcesPath.createPath(); + } + + public void execute() + throws BuildException + { + + CommandlineJava commandline = new CommandlineJava(); + + if( "text".equals( format ) ) + commandline.setClassname( "jdepend.textui.JDepend" ); + else + if( "xml".equals( format ) ) + commandline.setClassname( "jdepend.xmlui.JDepend" ); + + if( _jvm != null ) + commandline.setVm( _jvm ); + + if( getSourcespath() == null ) + throw new BuildException( "Missing Sourcepath required argument" ); + + // execute the test and get the return code + int exitValue = JDependTask.ERRORS; + boolean wasKilled = false; + if( !getFork() ) + { + exitValue = executeInVM( commandline ); + } + else + { + ExecuteWatchdog watchdog = createWatchdog(); + exitValue = executeAsForked( commandline, watchdog ); + // null watchdog means no timeout, you'd better not check with null + if( watchdog != null ) + { + //info will be used in later version do nothing for now + //wasKilled = watchdog.killedProcess(); + } + } + + // if there is an error/failure and that it should halt, stop everything otherwise + // just log a statement + boolean errorOccurred = exitValue == JDependTask.ERRORS; + + if( errorOccurred ) + { + if( getHaltonerror() ) + throw new BuildException( "JDepend failed", + location ); + else + log( "JDepend FAILED", Project.MSG_ERR ); + } + } + + + /** + * Execute the task by forking a new JVM. The command will block until it + * finishes. To know if the process was destroyed or not, use the + * killedProcess() method of the watchdog class. + * + * @param watchdog the watchdog in charge of cancelling the test if it + * exceeds a certain amount of time. Can be null , in this + * case the test could probably hang forever. + * @param commandline Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + // JL: comment extracted from JUnitTask (and slightly modified) + public int executeAsForked( CommandlineJava commandline, ExecuteWatchdog watchdog ) + throws BuildException + { + // if not set, auto-create the ClassPath from the project + createClasspath(); + + // not sure whether this test is needed but cost nothing to put. + // hope it will be reviewed by anybody competent + if( getClasspath().toString().length() > 0 ) + { + createJvmarg( commandline ).setValue( "-classpath" ); + createJvmarg( commandline ).setValue( getClasspath().toString() ); + } + + if( getOutputFile() != null ) + { + // having a space between the file and its path causes commandline to add quotes " + // around the argument thus making JDepend not taking it into account. Thus we split it in two + commandline.createArgument().setValue( "-file" ); + commandline.createArgument().setValue( _outputFile.getPath() ); + // we have to find a cleaner way to put this output + } + + PathTokenizer sourcesPath = new PathTokenizer( getSourcespath().toString() ); + while( sourcesPath.hasMoreTokens() ) + { + File f = new File( sourcesPath.nextToken() ); + + // not necessary as JDepend would fail, but why loose some time? + if( !f.exists() || !f.isDirectory() ) + throw new BuildException( "\"" + f.getPath() + "\" does not represent a valid directory. JDepend would fail." ); + commandline.createArgument().setValue( f.getPath() ); + } + + Execute execute = new Execute( new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ), watchdog ); + execute.setCommandline( commandline.getCommandline() ); + if( getDir() != null ) + { + execute.setWorkingDirectory( getDir() ); + execute.setAntRun( project ); + } + + if( getOutputFile() != null ) + log( "Output to be stored in " + getOutputFile().getPath() ); + log( "Executing: " + commandline.toString(), Project.MSG_VERBOSE ); + try + { + return execute.execute(); + } + catch( IOException e ) + { + throw new BuildException( "Process fork failed.", e, location ); + } + } + + + // this comment extract from JUnit Task may also apply here + // "in VM is not very nice since it could probably hang the + // whole build. IMHO this method should be avoided and it would be best + // to remove it in future versions. TBD. (SBa)" + + /** + * Execute inside VM. + * + * @param commandline Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public int executeInVM( CommandlineJava commandline ) + throws BuildException + { + jdepend.textui.JDepend jdepend; + + if( "xml".equals( format ) ) + jdepend = new jdepend.xmlui.JDepend(); + else + jdepend = new jdepend.textui.JDepend(); + + if( getOutputFile() != null ) + { + FileWriter fw; + try + { + fw = new FileWriter( getOutputFile().getPath() ); + } + catch( IOException e ) + { + String msg = "JDepend Failed when creating the output file: " + e.getMessage(); + log( msg ); + throw new BuildException( msg ); + } + jdepend.setWriter( new PrintWriter( fw ) ); + log( "Output to be stored in " + getOutputFile().getPath() ); + } + + PathTokenizer sourcesPath = new PathTokenizer( getSourcespath().toString() ); + while( sourcesPath.hasMoreTokens() ) + { + File f = new File( sourcesPath.nextToken() ); + + // not necessary as JDepend would fail, but why loose some time? + if( !f.exists() || !f.isDirectory() ) + { + String msg = "\"" + f.getPath() + "\" does not represent a valid directory. JDepend would fail."; + log( msg ); + throw new BuildException( msg ); + } + try + { + jdepend.addDirectory( f.getPath() ); + } + catch( IOException e ) + { + String msg = "JDepend Failed when adding a source directory: " + e.getMessage(); + log( msg ); + throw new BuildException( msg ); + } + } + jdepend.analyze(); + return SUCCESS; + } + + /** + * @return null if there is a timeout value, otherwise the watchdog + * instance. + * @exception BuildException Description of Exception + */ + protected ExecuteWatchdog createWatchdog() + throws BuildException + { + + return null; + /* + * if (getTimeout() == null){ + * return null; + * } + * return new ExecuteWatchdog(getTimeout().intValue()); + */ + } + + public static class FormatAttribute extends EnumeratedAttribute + { + private String[] formats = new String[]{"xml", "text"}; + + public String[] getValues() + { + return formats; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java new file mode 100644 index 000000000..b1a089810 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jlink; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Provides a quick and dirty way to determine the true name of a class given + * just an InputStream. Reads in just enough to perform this minimal task only. + * + * @author RT + */ +public class ClassNameReader extends Object +{ + + public static String getClassName( InputStream input ) + throws IOException + { + DataInputStream data = new DataInputStream( input ); + // verify this is a valid class file. + int cookie = data.readInt(); + if( cookie != 0xCAFEBABE ) + { + return null; + } + int version = data.readInt(); + // read the constant pool. + ConstantPool constants = new ConstantPool( data ); + Object[] values = constants.values; + // read access flags and class index. + int accessFlags = data.readUnsignedShort(); + int classIndex = data.readUnsignedShort(); + Integer stringIndex = ( Integer )values[classIndex]; + String className = ( String )values[stringIndex.intValue()]; + return className; + } + +} + +/** + * Reads just enough of a class file to determine the class' full name.

      + * + * Extremely minimal constant pool implementation, mainly to support extracting + * strings from a class file. + * + * @author Patrick C. Beard . + */ +class ConstantPool extends Object +{ + + + final static byte UTF8 = 1, UNUSED = 2, INTEGER = 3, FLOAT = 4, LONG = 5, DOUBLE = 6, + CLASS = 7, STRING = 8, FIELDREF = 9, METHODREF = 10, + INTERFACEMETHODREF = 11, NAMEANDTYPE = 12; + + byte[] types; + + Object[] values; + + ConstantPool( DataInput data ) + throws IOException + { + super(); + + int count = data.readUnsignedShort(); + types = new byte[count]; + values = new Object[count]; + // read in all constant pool entries. + for( int i = 1; i < count; i++ ) + { + byte type = data.readByte(); + types[i] = type; + switch ( type ) + { + case UTF8: + values[i] = data.readUTF(); + break; + case UNUSED: + break; + case INTEGER: + values[i] = new Integer( data.readInt() ); + break; + case FLOAT: + values[i] = new Float( data.readFloat() ); + break; + case LONG: + values[i] = new Long( data.readLong() ); + ++i; + break; + case DOUBLE: + values[i] = new Double( data.readDouble() ); + ++i; + break; + case CLASS: + case STRING: + values[i] = new Integer( data.readUnsignedShort() ); + break; + case FIELDREF: + case METHODREF: + case INTERFACEMETHODREF: + case NAMEANDTYPE: + values[i] = new Integer( data.readInt() ); + break; + } + } + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java new file mode 100644 index 000000000..94d642f0c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jlink; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Path; + +/** + * This class defines objects that can link together various jar and zip files. + *

      + * + * It is basically a wrapper for the jlink code written originally by Patrick Beard . The classes + * org.apache.tools.ant.taskdefs.optional.jlink.Jlink and + * org.apache.tools.ant.taskdefs.optional.jlink.ClassNameReader support this + * class.

      + * + * For example: + *

      + * <jlink compress="false" outfile="out.jar"/>
      + *   <mergefiles>
      + *     <pathelement path="${build.dir}/mergefoo.jar"/>
      + *     <pathelement path="${build.dir}/mergebar.jar"/>
      + *   </mergefiles>
      + *   <addfiles>
      + *     <pathelement path="${build.dir}/mac.jar"/>
      + *     <pathelement path="${build.dir}/pc.zip"/>
      + *   </addfiles>
      + * </jlink>
      + * 
      + * + * @author Matthew Kuperus Heun + * + */ +public class JlinkTask extends MatchingTask +{ + + private File outfile = null; + + private Path mergefiles = null; + + private Path addfiles = null; + + private boolean compress = false; + + private String ps = System.getProperty( "path.separator" ); + + /** + * Sets the files to be added into the output. + * + * @param addfiles The new Addfiles value + */ + public void setAddfiles( Path addfiles ) + { + if( this.addfiles == null ) + { + this.addfiles = addfiles; + } + else + { + this.addfiles.append( addfiles ); + } + } + + /** + * Defines whether or not the output should be compacted. + * + * @param compress The new Compress value + */ + public void setCompress( boolean compress ) + { + this.compress = compress; + } + + /** + * Sets the files to be merged into the output. + * + * @param mergefiles The new Mergefiles value + */ + public void setMergefiles( Path mergefiles ) + { + if( this.mergefiles == null ) + { + this.mergefiles = mergefiles; + } + else + { + this.mergefiles.append( mergefiles ); + } + } + + /** + * The output file for this run of jlink. Usually a jar or zip file. + * + * @param outfile The new Outfile value + */ + public void setOutfile( File outfile ) + { + this.outfile = outfile; + } + + /** + * Establishes the object that contains the files to be added to the output. + * + * @return Description of the Returned Value + */ + public Path createAddfiles() + { + if( this.addfiles == null ) + { + this.addfiles = new Path( getProject() ); + } + return this.addfiles.createPath(); + } + + /** + * Establishes the object that contains the files to be merged into the + * output. + * + * @return Description of the Returned Value + */ + public Path createMergefiles() + { + if( this.mergefiles == null ) + { + this.mergefiles = new Path( getProject() ); + } + return this.mergefiles.createPath(); + } + + /** + * Does the adding and merging. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + //Be sure everything has been set. + if( outfile == null ) + { + throw new BuildException( "outfile attribute is required! Please set." ); + } + if( !haveAddFiles() && !haveMergeFiles() ) + { + throw new BuildException( "addfiles or mergefiles required! Please set." ); + } + log( "linking: " + outfile.getPath() ); + log( "compression: " + compress, Project.MSG_VERBOSE ); + jlink linker = new jlink(); + linker.setOutfile( outfile.getPath() ); + linker.setCompression( compress ); + if( haveMergeFiles() ) + { + log( "merge files: " + mergefiles.toString(), Project.MSG_VERBOSE ); + linker.addMergeFiles( mergefiles.list() ); + } + if( haveAddFiles() ) + { + log( "add files: " + addfiles.toString(), Project.MSG_VERBOSE ); + linker.addAddFiles( addfiles.list() ); + } + try + { + linker.link(); + } + catch( Exception ex ) + { + throw new BuildException( ex); + } + } + + private boolean haveAddFiles() + { + return haveEntries( addfiles ); + } + + private boolean haveEntries( Path p ) + { + if( p == null ) + { + return false; + } + if( p.size() > 0 ) + { + return true; + } + return false; + } + + private boolean haveMergeFiles() + { + return haveEntries( mergefiles ); + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java new file mode 100644 index 000000000..cb9a3fbe8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java @@ -0,0 +1,464 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jlink; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Vector; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public class jlink extends Object +{ + + private String outfile = null; + + private Vector mergefiles = new Vector( 10 ); + + private Vector addfiles = new Vector( 10 ); + + private boolean compression = false; + + byte[] buffer = new byte[8192]; + + public static void main( String[] args ) + { + // jlink output input1 ... inputN + if( args.length < 2 ) + { + System.out.println( "usage: jlink output input1 ... inputN" ); + System.exit( 1 ); + } + jlink linker = new jlink(); + linker.setOutfile( args[0] ); + //To maintain compatibility with the command-line version, we will only add files to be merged. + for( int i = 1; i < args.length; i++ ) + { + linker.addMergeFile( args[i] ); + } + try + { + linker.link(); + } + catch( Exception ex ) + { + System.err.print( ex.getMessage() ); + } + } + + /** + * Determines whether output will be compressed. + * + * @param compress The new Compression value + */ + public void setCompression( boolean compress ) + { + this.compression = compress; + } + + /** + * The file that will be created by this instance of jlink. + * + * @param outfile The new Outfile value + */ + public void setOutfile( String outfile ) + { + if( outfile == null ) + { + return; + } + this.outfile = outfile; + } + + /** + * Adds a file to be added into the output. + * + * @param addfile The feature to be added to the AddFile attribute + */ + public void addAddFile( String addfile ) + { + if( addfile == null ) + { + return; + } + addfiles.addElement( addfile ); + } + + /** + * Adds several file to be added into the output. + * + * @param addfiles The feature to be added to the AddFiles attribute + */ + public void addAddFiles( String[] addfiles ) + { + if( addfiles == null ) + { + return; + } + for( int i = 0; i < addfiles.length; i++ ) + { + addAddFile( addfiles[i] ); + } + } + + /** + * Adds a file to be merged into the output. + * + * @param mergefile The feature to be added to the MergeFile attribute + */ + public void addMergeFile( String mergefile ) + { + if( mergefile == null ) + { + return; + } + mergefiles.addElement( mergefile ); + } + + /** + * Adds several files to be merged into the output. + * + * @param mergefiles The feature to be added to the MergeFiles attribute + */ + public void addMergeFiles( String[] mergefiles ) + { + if( mergefiles == null ) + { + return; + } + for( int i = 0; i < mergefiles.length; i++ ) + { + addMergeFile( mergefiles[i] ); + } + } + + /** + * Performs the linking of files. Addfiles are added to the output as-is. + * For example, a jar file is added to the output as a jar file. However, + * mergefiles are first examined for their type. If it is a jar or zip file, + * the contents will be extracted from the mergefile and entered into the + * output. If a zip or jar file is encountered in a subdirectory it will be + * added, not merged. If a directory is encountered, it becomes the root + * entry of all the files below it. Thus, you can provide multiple, disjoint + * directories, as addfiles: they will all be added in a rational manner to + * outfile. + * + * @exception Exception Description of Exception + */ + public void link() + throws Exception + { + ZipOutputStream output = new ZipOutputStream( new FileOutputStream( outfile ) ); + if( compression ) + { + output.setMethod( ZipOutputStream.DEFLATED ); + output.setLevel( Deflater.DEFAULT_COMPRESSION ); + } + else + { + output.setMethod( ZipOutputStream.STORED ); + } + Enumeration merges = mergefiles.elements(); + while( merges.hasMoreElements() ) + { + String path = ( String )merges.nextElement(); + File f = new File( path ); + if( f.getName().endsWith( ".jar" ) || f.getName().endsWith( ".zip" ) ) + { + //Do the merge + mergeZipJarContents( output, f ); + } + else + { + //Add this file to the addfiles Vector and add it + //later at the top level of the output file. + addAddFile( path ); + } + } + Enumeration adds = addfiles.elements(); + while( adds.hasMoreElements() ) + { + String name = ( String )adds.nextElement(); + File f = new File( name ); + if( f.isDirectory() ) + { + //System.out.println("in jlink: adding directory contents of " + f.getPath()); + addDirContents( output, f, f.getName() + '/', compression ); + } + else + { + addFile( output, f, "", compression ); + } + } + if( output != null ) + { + try + { + output.close(); + } + catch( IOException ioe ) + {} + } + } + + /* + * Gets the name of an entry in the file. This is the real name + * which for a class is the name of the package with the class + * name appended. + */ + private String getEntryName( File file, String prefix ) + { + String name = file.getName(); + if( !name.endsWith( ".class" ) ) + { + // see if the file is in fact a .class file, and determine its actual name. + try + { + InputStream input = new FileInputStream( file ); + String className = ClassNameReader.getClassName( input ); + input.close(); + if( className != null ) + { + return className.replace( '.', '/' ) + ".class"; + } + } + catch( IOException ioe ) + {} + } + System.out.println( "From " + file.getPath() + " and prefix " + prefix + ", creating entry " + prefix + name ); + return ( prefix + name ); + } + + /* + * Adds contents of a directory to the output. + */ + private void addDirContents( ZipOutputStream output, File dir, String prefix, boolean compress ) + throws IOException + { + String[] contents = dir.list(); + for( int i = 0; i < contents.length; ++i ) + { + String name = contents[i]; + File file = new File( dir, name ); + if( file.isDirectory() ) + { + addDirContents( output, file, prefix + name + '/', compress ); + } + else + { + addFile( output, file, prefix, compress ); + } + } + } + + /* + * Adds a file to the output stream. + */ + private void addFile( ZipOutputStream output, File file, String prefix, boolean compress ) + throws IOException + { + //Make sure file exists + long checksum = 0; + if( !file.exists() ) + { + return; + } + ZipEntry entry = new ZipEntry( getEntryName( file, prefix ) ); + entry.setTime( file.lastModified() ); + entry.setSize( file.length() ); + if( !compress ) + { + entry.setCrc( calcChecksum( file ) ); + } + FileInputStream input = new FileInputStream( file ); + addToOutputStream( output, input, entry ); + } + + /* + * A convenience method that several other methods might call. + */ + private void addToOutputStream( ZipOutputStream output, InputStream input, ZipEntry ze ) + throws IOException + { + try + { + output.putNextEntry( ze ); + } + catch( ZipException zipEx ) + { + //This entry already exists. So, go with the first one. + input.close(); + return; + } + int numBytes = -1; + while( ( numBytes = input.read( buffer ) ) > 0 ) + { + output.write( buffer, 0, numBytes ); + } + output.closeEntry(); + input.close(); + } + + /* + * Necessary in the case where you add a entry that + * is not compressed. + */ + private long calcChecksum( File f ) + throws IOException + { + BufferedInputStream in = new BufferedInputStream( new FileInputStream( f ) ); + return calcChecksum( in, f.length() ); + } + + /* + * Necessary in the case where you add a entry that + * is not compressed. + */ + private long calcChecksum( InputStream in, long size ) + throws IOException + { + CRC32 crc = new CRC32(); + int len = buffer.length; + int count = -1; + int haveRead = 0; + while( ( count = in.read( buffer, 0, len ) ) > 0 ) + { + haveRead += count; + crc.update( buffer, 0, count ); + } + in.close(); + return crc.getValue(); + } + + /* + * Actually performs the merging of f into the output. + * f should be a zip or jar file. + */ + private void mergeZipJarContents( ZipOutputStream output, File f ) + throws IOException + { + //Check to see that the file with name "name" exists. + if( !f.exists() ) + { + return; + } + ZipFile zipf = new ZipFile( f ); + Enumeration entries = zipf.entries(); + while( entries.hasMoreElements() ) + { + ZipEntry inputEntry = ( ZipEntry )entries.nextElement(); + //Ignore manifest entries. They're bound to cause conflicts between + //files that are being merged. User should supply their own + //manifest file when doing the merge. + String inputEntryName = inputEntry.getName(); + int index = inputEntryName.indexOf( "META-INF" ); + if( index < 0 ) + { + //META-INF not found in the name of the entry. Go ahead and process it. + try + { + output.putNextEntry( processEntry( zipf, inputEntry ) ); + } + catch( ZipException ex ) + { + //If we get here, it could be because we are trying to put a + //directory entry that already exists. + //For example, we're trying to write "com", but a previous + //entry from another mergefile was called "com". + //In that case, just ignore the error and go on to the + //next entry. + String mess = ex.getMessage(); + if( mess.indexOf( "duplicate" ) >= 0 ) + { + //It was the duplicate entry. + continue; + } + else + { + //I hate to admit it, but we don't know what happened here. Throw the Exception. + throw ex; + } + } + InputStream in = zipf.getInputStream( inputEntry ); + int len = buffer.length; + int count = -1; + while( ( count = in.read( buffer, 0, len ) ) > 0 ) + { + output.write( buffer, 0, count ); + } + in.close(); + output.closeEntry(); + } + } + zipf.close(); + } + + /* + * A method that does the work on a given entry in a mergefile. + * The big deal is to set the right parameters in the ZipEntry + * on the output stream. + */ + private ZipEntry processEntry( ZipFile zip, ZipEntry inputEntry ) + throws IOException + { + /* + * First, some notes. + * On MRJ 2.2.2, getting the size, compressed size, and CRC32 from the + * ZipInputStream does not work for compressed (deflated) files. Those calls return -1. + * For uncompressed (stored) files, those calls do work. + * However, using ZipFile.getEntries() works for both compressed and + * uncompressed files. + * Now, from some simple testing I did, it seems that the value of CRC-32 is + * independent of the compression setting. So, it should be easy to pass this + * information on to the output entry. + */ + String name = inputEntry.getName(); + if( !( inputEntry.isDirectory() || name.endsWith( ".class" ) ) ) + { + try + { + InputStream input = zip.getInputStream( zip.getEntry( name ) ); + String className = ClassNameReader.getClassName( input ); + input.close(); + if( className != null ) + { + name = className.replace( '.', '/' ) + ".class"; + } + } + catch( IOException ioe ) + {} + } + ZipEntry outputEntry = new ZipEntry( name ); + outputEntry.setTime( inputEntry.getTime() ); + outputEntry.setExtra( inputEntry.getExtra() ); + outputEntry.setComment( inputEntry.getComment() ); + outputEntry.setTime( inputEntry.getTime() ); + if( compression ) + { + outputEntry.setMethod( ZipEntry.DEFLATED ); + //Note, don't need to specify size or crc for compressed files. + } + else + { + outputEntry.setMethod( ZipEntry.STORED ); + outputEntry.setCrc( inputEntry.getCrc() ); + outputEntry.setSize( inputEntry.getSize() ); + } + return outputEntry; + } + +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java new file mode 100644 index 000000000..2d91db4ca --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java @@ -0,0 +1,491 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp; +import java.io.File; +import java.util.Date; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.optional.jsp.compilers.CompilerAdapter; +import org.apache.tools.ant.taskdefs.optional.jsp.compilers.CompilerAdapterFactory; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Ant task to run the jsp compiler.

      + * + * This task takes the given jsp files and compiles them into java files. It is + * then up to the user to compile the java files into classes.

      + * + * The task requires the srcdir and destdir attributes to be set. This Task is a + * MatchingTask, so the files to be compiled can be specified using + * includes/excludes attributes or nested include/exclude elements. Optional + * attributes are verbose (set the verbosity level passed to jasper), package + * (name of the destination package for generated java classes and classpath + * (the classpath to use when running the jsp compiler).

      + * + * This task supports the nested elements classpath (A Path) and classpathref (A + * Reference) which can be used in preference to the attribute classpath, if the + * jsp compiler is not already in the ant classpath.

      + * + *

      Notes

      + * + * At present, this task only supports the jasper compiler. In future, other + * compilers will be supported by setting the jsp.compiler property.

      + * + *

      Usage

      + * <jspc srcdir="${basedir}/src/war"
      + *       destdir="${basedir}/gensrc"
      + *       package="com.i3sp.jsp"
      + *       verbose="9">
      + *   <include name="**\/*.jsp" />
      + * </jspc>
      + * 
      + * + * @author Matthew Watson

      + * + * Large Amount of cutting and pasting from the Javac task... + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + * @version $Revision$ $Date$ + */ +public class JspC extends MatchingTask +{ + + private final static String FAIL_MSG + = "Compile failed, messages should have been provided."; + private int verbose = 0; + protected Vector compileList = new Vector(); + protected boolean failOnError = true; + /* + * ------------------------------------------------------------ + */ + private Path classpath; + private File destDir; + private String iepluginid; + private boolean mapped; + private String packageName; + private Path src; + + /** + * -uribase

      The uri directory compilations should be relative to + * (Default is "/") + */ + + private File uribase; + + /** + * -uriroot The root directory that uri files should be resolved + * against, + */ + private File uriroot; + + + /* + * ------------------------------------------------------------ + */ + /** + * Set the classpath to be used for this compilation + * + * @param cp The new Classpath value + */ + public void setClasspath( Path cp ) + { + if( classpath == null ) + classpath = cp; + else + classpath.append( cp ); + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the destination directory into which the JSP source files should be + * compiled. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Throw a BuildException if compilation fails + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Set the ieplugin id + * + * @param iepluginid_ The new Ieplugin value + */ + public void setIeplugin( String iepluginid_ ) + { + iepluginid = iepluginid_; + } + + /** + * set the mapped flag + * + * @param mapped_ The new Mapped value + */ + public void setMapped( boolean mapped_ ) + { + mapped = mapped_; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the name of the package the compiled jsp files should be in + * + * @param pkg The new Package value + */ + public void setPackage( String pkg ) + { + this.packageName = pkg; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the source dirs to find the source JSP files. + * + * @param srcDir The new Srcdir value + */ + public void setSrcdir( Path srcDir ) + { + if( src == null ) + { + src = srcDir; + } + else + { + src.append( srcDir ); + } + } + + /** + * -uribase. the uri context of relative URI references in the JSP pages. If + * it does not exist then it is derived from the location of the file + * relative to the declared or derived value of -uriroot. + * + * @param uribase The new Uribase value + */ + public void setUribase( File uribase ) + { + this.uribase = uribase; + } + + /** + * -uriroot The root directory that uri files should be resolved + * against, (Default is the directory jspc is invoked from) + * + * @param uriroot The new Uribase value + */ + public void setUriroot( File uriroot ) + { + this.uriroot = uriroot; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the verbose level of the compiler + * + * @param i The new Verbose value + */ + public void setVerbose( int i ) + { + verbose = i; + } + + public Path getClasspath() + { + return classpath; + } + + /* + * ------------------------------------------------------------ + */ + public Vector getCompileList() + { + return compileList; + } + + public File getDestdir() + { + return destDir; + } + + /** + * Gets the failonerror flag. + * + * @return The Failonerror value + */ + public boolean getFailonerror() + { + return failOnError; + } + + /* + * ------------------------------------------------------------ + */ + public String getIeplugin() + { + return iepluginid; + } + + public String getPackage() + { + return packageName; + } + + public Path getSrcDir() + { + return src; + } + + public File getUribase() + { + return uriroot; + } + + public File getUriroot() + { + return uriroot; + } + + public int getVerbose() + { + return verbose; + } + + /* + * ------------------------------------------------------------ + */ + public boolean isMapped() + { + return mapped; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + classpath = new Path( project ); + return classpath.createPath(); + } + + /* + * ------------------------------------------------------------ + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir + if( src == null ) + { + throw new BuildException( "srcdir attribute must be set!", + location ); + } + String[] list = src.list(); + if( list.length == 0 ) + { + throw new BuildException( "srcdir attribute must be set!", + location ); + } + + if( destDir != null && !destDir.isDirectory() ) + { + throw new + BuildException( "destination directory \"" + destDir + + "\" does not exist or is not a directory", + location ); + } + + // calculate where the files will end up: + File dest = null; + if( packageName == null ) + dest = destDir; + else + { + String path = destDir.getPath() + File.separatorChar + + packageName.replace( '.', File.separatorChar ); + dest = new File( path ); + } + + // scan source directories and dest directory to build up both copy + // lists and compile lists + resetFileLists(); + int filecount = 0; + for( int i = 0; i < list.length; i++ ) + { + File srcDir = ( File )project.resolveFile( list[i] ); + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir \"" + srcDir.getPath() + + "\" does not exist!", location ); + } + + DirectoryScanner ds = this.getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + filecount = files.length; + scanDir( srcDir, dest, files ); + } + + // compile the source files + + String compiler = project.getProperty( "jsp.compiler" ); + if( compiler == null ) + { + compiler = "jasper"; + } + log( "compiling " + compileList.size() + " files", Project.MSG_VERBOSE ); + + if( compileList.size() > 0 ) + { + + CompilerAdapter adapter = + CompilerAdapterFactory.getCompiler( compiler, this ); + log( "Compiling " + compileList.size() + + " source file" + + ( compileList.size() == 1 ? "" : "s" ) + + ( destDir != null ? " to " + destDir : "" ) ); + + // now we need to populate the compiler adapter + adapter.setJspc( this ); + + // finally, lets execute the compiler!! + if( !adapter.execute() ) + { + if( failOnError ) + { + throw new BuildException( FAIL_MSG, location ); + } + else + { + log( FAIL_MSG, Project.MSG_ERR ); + } + } + } + else + { + if( filecount == 0 ) + { + log( "there were no files to compile", Project.MSG_INFO ); + } + else + { + log( "all files are up to date", Project.MSG_VERBOSE ); + } + } + } + + /* + * ------------------------------------------------------------ + */ + /** + * Clear the list of files to be compiled and copied.. + */ + protected void resetFileLists() + { + compileList.removeAllElements(); + } + + /* + * ------------------------------------------------------------ + */ + /** + * Scans the directory looking for source files to be compiled. The results + * are returned in the class variable compileList + * + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param files Description of Parameter + */ + protected void scanDir( File srcDir, File destDir, String files[] ) + { + + long now = ( new Date() ).getTime(); + + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".jsp" ) ) + { + // drop leading path (if any) + int fileStart = + files[i].lastIndexOf( File.separatorChar ) + 1; + File javaFile = new File( destDir, files[i].substring( fileStart, + files[i].indexOf( ".jsp" ) ) + ".java" ); + + if( srcFile.lastModified() > now ) + { + log( "Warning: file modified in the future: " + + files[i], Project.MSG_WARN ); + } + + if( !javaFile.exists() || + srcFile.lastModified() > javaFile.lastModified() ) + { + if( !javaFile.exists() ) + { + log( "Compiling " + srcFile.getPath() + + " because java file " + + javaFile.getPath() + " does not exist", + Project.MSG_DEBUG ); + } + else + { + log( "Compiling " + srcFile.getPath() + + " because it is out of date with respect to " + + javaFile.getPath(), Project.MSG_DEBUG ); + } + compileList.addElement( srcFile.getAbsolutePath() ); + } + } + } + } + /* + * ------------------------------------------------------------ + */ +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java new file mode 100644 index 000000000..20d7a9176 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp;//java imports +import java.io.File; +import java.util.Date; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java;//apache/ant imports +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Path; + +/** + * Class to precompile JSP's using weblogic's jsp compiler (weblogic.jspc) + * + * @author Avik Sengupta + * http://www.webteksoftware.com Tested only on Weblogic 4.5.1 - NT4.0 and + * Solaris 5.7 required attributes src : root of source tree for JSP, ie, + * the document root for your weblogic server dest : root of destination + * directory, what you have set as WorkingDir in the weblogic properties + * package : start package name under which your JSP's would be compiled + * other attributes classpath A classpath should be set which contains the + * weblogic classes as well as all application classes referenced by the + * JSP. The system classpath is also appended when the jspc is called, so + * you may choose to put everything in the classpath while calling Ant. + * However, since presumably the JSP's will reference classes being build + * by Ant, it would be better to explicitly add the classpath in the task + * The task checks timestamps on the JSP's and the generated classes, and + * compiles only those files that have changed. It follows the weblogic + * naming convention of putting classes in _dirName/_fileName.class for + * dirname/fileName.jsp Limitation: It compiles the files thru the + * Classic compiler only. Limitation: Since it is my experience that + * weblogic jspc throws out of memory error on being given too many files + * at one go, it is called multiple times with one jsp file each.
      + * example
      + * <target name="jspcompile" depends="compile">
      + *   <wljspc src="c:\\weblogic\\myserver\\public_html" dest="c:\\weblogic\\myserver\\serverclasses" package="myapp.jsp">
      + *   <classpath>
      + *          <pathelement location="${weblogic.classpath}" />
      + *           <pathelement path="${compile.dest}" />
      + *      </classpath>
      + *
      + *   </wljspc>
      + * </target>
      + * 
      + */ + +public class WLJspc extends MatchingTask +{//classpath used to compile the jsp files. + //private String compilerPath; //fully qualified name for the compiler executable + + private String pathToPackage = ""; + private Vector filesToDo = new Vector();//package under which resultant classes will reside + private Path compileClasspath; + //TODO Test on other versions of weblogic + //TODO add more attributes to the task, to take care of all jspc options + //TODO Test on Unix + + private File destinationDirectory;// root of source files tree + private String destinationPackage;//root of compiled files tree + private File sourceDirectory; + + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( compileClasspath == null ) + { + compileClasspath = classpath; + } + else + { + compileClasspath.append( classpath ); + } + } + + /** + * Set the directory containing the source jsp's + * + * @param dirName the directory containg the source jsp's + */ + public void setDest( File dirName ) + { + + destinationDirectory = dirName; + } + + /** + * Set the package under which the compiled classes go + * + * @param packageName the package name for the clases + */ + public void setPackage( String packageName ) + { + + destinationPackage = packageName; + } + + /** + * Set the directory containing the source jsp's + * + * @param dirName the directory containg the source jsp's + */ + public void setSrc( File dirName ) + { + + sourceDirectory = dirName; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + return compileClasspath; + } + + public void execute() + throws BuildException + { + if( !destinationDirectory.isDirectory() ) + { + throw new BuildException( "destination directory " + destinationDirectory.getPath() + + " is not valid" ); + } + + if( !sourceDirectory.isDirectory() ) + { + throw new BuildException( "src directory " + sourceDirectory.getPath() + + " is not valid" ); + } + + if( destinationPackage == null ) + { + throw new BuildException( "package attribute must be present.", location ); + } + + String systemClassPath = System.getProperty( "java.class.path" ); + + pathToPackage = this.destinationPackage.replace( '.', File.separatorChar ); + // get all the files in the sourceDirectory + DirectoryScanner ds = super.getDirectoryScanner( sourceDirectory ); + + //use the systemclasspath as well, to include the ant jar + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + + compileClasspath.append( Path.systemClasspath ); + String[] files = ds.getIncludedFiles(); + + //Weblogic.jspc calls System.exit() ... have to fork + // Therefore, takes loads of time + // Can pass directories at a time (*.jsp) but easily runs out of memory on hefty dirs + // (even on a Sun) + Java helperTask = ( Java )project.createTask( "java" ); + helperTask.setFork( true ); + helperTask.setClassname( "weblogic.jspc" ); + helperTask.setTaskName( getTaskName() ); + String[] args = new String[12]; + + File jspFile = null; + String parents = ""; + String arg = ""; + int j = 0; + //XXX this array stuff is a remnant of prev trials.. gotta remove. + args[j++] = "-d"; + args[j++] = destinationDirectory.getAbsolutePath().trim(); + args[j++] = "-docroot"; + args[j++] = sourceDirectory.getAbsolutePath().trim(); + args[j++] = "-keepgenerated";//TODO: Parameterise ?? + //Call compiler as class... dont want to fork again + //Use classic compiler -- can be parameterised? + args[j++] = "-compilerclass"; + args[j++] = "sun.tools.javac.Main"; + //Weblogic jspc does not seem to work unless u explicitly set this... + // Does not take the classpath from the env.... + // Am i missing something about the Java task?? + args[j++] = "-classpath"; + args[j++] = compileClasspath.toString(); + + this.scanDir( files ); + log( "Compiling " + filesToDo.size() + " JSP files" ); + + for( int i = 0; i < filesToDo.size(); i++ ) + { + //XXX + // All this to get package according to weblogic standards + // Can be written better... this is too hacky! + // Careful.. similar code in scanDir , but slightly different!! + jspFile = new File( ( String )filesToDo.elementAt( i ) ); + args[j] = "-package"; + parents = jspFile.getParent(); + if( ( parents != null ) && ( !( "" ).equals( parents ) ) ) + { + parents = this.replaceString( parents, File.separator, "_." ); + args[j + 1] = destinationPackage + "." + "_" + parents; + } + else + { + args[j + 1] = destinationPackage; + } + + args[j + 2] = sourceDirectory + File.separator + ( String )filesToDo.elementAt( i ); + arg = ""; + + for( int x = 0; x < 12; x++ ) + { + arg += " " + args[x]; + } + + System.out.println( "arg = " + arg ); + + helperTask.clearArgs(); + helperTask.setArgs( arg ); + helperTask.setClasspath( compileClasspath ); + if( helperTask.executeJava() != 0 ) + { + log( files[i] + " failed to compile", Project.MSG_WARN ); + } + } + } + + + protected String replaceString( String inpString, String escapeChars, String replaceChars ) + { + String localString = ""; + int numTokens = 0; + StringTokenizer st = new StringTokenizer( inpString, escapeChars, true ); + numTokens = st.countTokens(); + for( int i = 0; i < numTokens; i++ ) + { + String test = st.nextToken(); + test = ( test.equals( escapeChars ) ? replaceChars : test ); + localString += test; + } + return localString; + } + + + protected void scanDir( String files[] ) + { + + long now = ( new Date() ).getTime(); + File jspFile = null; + String parents = null; + String pack = ""; + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( this.sourceDirectory, files[i] ); + //XXX + // All this to convert source to destination directory according to weblogic standards + // Can be written better... this is too hacky! + jspFile = new File( files[i] ); + parents = jspFile.getParent(); + int loc = 0; + + if( ( parents != null ) && ( !( "" ).equals( parents ) ) ) + { + parents = this.replaceString( parents, File.separator, "_/" ); + pack = pathToPackage + File.separator + "_" + parents; + } + else + { + pack = pathToPackage; + } + + String filePath = pack + File.separator + "_"; + int startingIndex + = files[i].lastIndexOf( File.separator ) != -1 ? files[i].lastIndexOf( File.separator ) + 1 : 0; + int endingIndex = files[i].indexOf( ".jsp" ); + if( endingIndex == -1 ) + { + break; + } + + filePath += files[i].substring( startingIndex, endingIndex ); + filePath += ".class"; + File classFile = new File( this.destinationDirectory, filePath ); + + if( srcFile.lastModified() > now ) + { + log( "Warning: file modified in the future: " + + files[i], Project.MSG_WARN ); + } + if( srcFile.lastModified() > classFile.lastModified() ) + { + //log("Files are" + srcFile.getAbsolutePath()+" " +classFile.getAbsolutePath()); + filesToDo.addElement( files[i] ); + log( "Recompiling File " + files[i], Project.MSG_VERBOSE ); + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java new file mode 100644 index 000000000..62465e8d8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.optional.jsp.JspC; + +/** + * The interface that all jsp compiler adapters must adher to.

      + * + * A compiler adapter is an adapter that interprets the jspc's parameters in + * preperation to be passed off to the compier this adapter represents. As all + * the necessary values are stored in the Jspc task itself, the only thing all + * adapters need is the jsp task, the execute command and a parameterless + * constructor (for reflection).

      + * + * @author Jay Dickon Glanville + * jayglanville@home.com + * @author Matthew Watson mattw@i3sp.com + */ + +public interface CompilerAdapter +{ + + /** + * Sets the compiler attributes, which are stored in the Jspc task. + * + * @param attributes The new Jspc value + */ + void setJspc( JspC attributes ); + + /** + * Executes the task. + * + * @return has the compilation been successful + * @exception BuildException Description of Exception + */ + boolean execute() + throws BuildException; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java new file mode 100644 index 000000000..2e648a5ac --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +/** + * Creates the necessary compiler adapter, given basic criteria. + * + * @author J D Glanville + * @author Matthew Watson mattw@i3sp.com + */ +public class CompilerAdapterFactory +{ + + /** + * This is a singlton -- can't create instances!! + */ + private CompilerAdapterFactory() { } + + /** + * Based on the parameter passed in, this method creates the necessary + * factory desired. The current mapping for compiler names are as follows: + * + *
        + *
      • jasper = jasper compiler (the default) + *
      • a fully quallified classname = the name of a jsp compiler + * adapter + *
      + * + * + * @param compilerType either the name of the desired compiler, or the full + * classname of the compiler's adapter. + * @param task a task to log through. + * @return The Compiler value + * @throws BuildException if the compiler type could not be resolved into a + * compiler adapter. + */ + public static CompilerAdapter getCompiler( String compilerType, Task task ) + throws BuildException + { + /* + * If I've done things right, this should be the extent of the + * conditional statements required. + */ + if( compilerType.equalsIgnoreCase( "jasper" ) ) + { + return new JasperC(); + } + return resolveClassName( compilerType ); + } + + /** + * Tries to resolve the given classname into a compiler adapter. Throws a + * fit if it can't. + * + * @param className The fully qualified classname to be created. + * @return Description of the Returned Value + * @throws BuildException This is the fit that is thrown if className isn't + * an instance of CompilerAdapter. + */ + private static CompilerAdapter resolveClassName( String className ) + throws BuildException + { + try + { + Class c = Class.forName( className ); + Object o = c.newInstance(); + return ( CompilerAdapter )o; + } + catch( ClassNotFoundException cnfe ) + { + throw new BuildException( className + " can\'t be found.", cnfe ); + } + catch( ClassCastException cce ) + { + throw new BuildException( className + " isn\'t the classname of " + + "a compiler adapter.", cce ); + } + catch( Throwable t ) + { + // for all other possibilities + throw new BuildException( className + " caused an interesting " + + "exception.", t ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java new file mode 100644 index 000000000..6c54419d4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.optional.jsp.JspC; +import org.apache.tools.ant.types.Commandline; + +/** + * This is the default implementation for the CompilerAdapter interface. This is + * currently very light on the ground since only one compiler type is supported. + * + * @author Matthew Watson mattw@i3sp.com + */ +public abstract class DefaultCompilerAdapter + implements CompilerAdapter +{ + /* + * ------------------------------------------------------------ + */ + private static String lSep = System.getProperty( "line.separator" ); + /* + * ------------------------------------------------------------ + */ + protected JspC attributes; + + public void setJspc( JspC attributes ) + { + this.attributes = attributes; + } + + public JspC getJspc() + { + return attributes; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Logs the compilation parameters, adds the files to compile and logs the + * &qout;niceSourceList" + * + * @param jspc Description of Parameter + * @param compileList Description of Parameter + * @param cmd Description of Parameter + */ + protected void logAndAddFilesToCompile( JspC jspc, + Vector compileList, + Commandline cmd ) + { + jspc.log( "Compilation args: " + cmd.toString(), Project.MSG_VERBOSE ); + + StringBuffer niceSourceList = new StringBuffer( "File" ); + if( compileList.size() != 1 ) + { + niceSourceList.append( "s" ); + } + niceSourceList.append( " to be compiled:" ); + + niceSourceList.append( lSep ); + + Enumeration enum = compileList.elements(); + while( enum.hasMoreElements() ) + { + String arg = ( String )enum.nextElement(); + cmd.createArgument().setValue( arg ); + niceSourceList.append( " " + arg + lSep ); + } + + jspc.log( niceSourceList.toString(), Project.MSG_VERBOSE ); + } + /* + * ------------------------------------------------------------ + */ +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java new file mode 100644 index 000000000..4f0985bbf --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.optional.jsp.JspC; +import org.apache.tools.ant.types.Commandline; + +/** + * The implementation of the jasper compiler. This is a cut-and-paste of the + * original Jspc task. + * + * @author Matthew Watson mattw@i3sp.com + */ +public class JasperC extends DefaultCompilerAdapter +{ + /* + * ------------------------------------------------------------ + */ + public boolean execute() + throws BuildException + { + getJspc().log( "Using jasper compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupJasperCommand(); + + try + { + // Create an instance of the compiler, redirecting output to + // the project log + Java java = ( Java )( getJspc().getProject() ).createTask( "java" ); + if( getJspc().getClasspath() != null ) + java.setClasspath( getJspc().getClasspath() ); + java.setClassname( "org.apache.jasper.JspC" ); + String args[] = cmd.getArguments(); + for( int i = 0; i < args.length; i++ ) + java.createArg().setValue( args[i] ); + java.setFailonerror( true ); + java.execute(); + return true; + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error running jsp compiler: ", + ex, getJspc().getLocation() ); + } + } + } + + /* + * ------------------------------------------------------------ + */ + private Commandline setupJasperCommand() + { + Commandline cmd = new Commandline(); + JspC jspc = getJspc(); + if( jspc.getDestdir() != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( jspc.getDestdir() ); + } + if( jspc.getPackage() != null ) + { + cmd.createArgument().setValue( "-p" ); + cmd.createArgument().setValue( jspc.getPackage() ); + } + if( jspc.getVerbose() != 0 ) + { + cmd.createArgument().setValue( "-v" + jspc.getVerbose() ); + } + if( jspc.isMapped() ) + { + cmd.createArgument().setValue( "-mapped" ); + } + if( jspc.getIeplugin() != null ) + { + cmd.createArgument().setValue( "-ieplugin" ); + cmd.createArgument().setValue( jspc.getIeplugin() ); + } + if( jspc.getUriroot() != null ) + { + cmd.createArgument().setValue( "-uriroot" ); + cmd.createArgument().setValue( jspc.getUriroot().toString() ); + } + if( jspc.getUribase() != null ) + { + cmd.createArgument().setValue( "-uribase" ); + cmd.createArgument().setValue( jspc.getUribase().toString() ); + } + logAndAddFilesToCompile( getJspc(), getJspc().getCompileList(), cmd ); + return cmd; + } + /* + * ------------------------------------------------------------ + */ +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java new file mode 100644 index 000000000..0d1e7e9d7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + + + +/** + * Transform a JUnit xml report. The default transformation generates an html + * report in either framed or non-framed style. The non-framed style is + * convenient to have a concise report via mail, the framed report is much more + * convenient if you want to browse into different packages or testcases since + * it is a Javadoc like report. + * + * @author Stephane Bailliez + */ +public class AggregateTransformer +{ + + public final static String FRAMES = "frames"; + + public final static String NOFRAMES = "noframes"; + + /** + * XML Parser factory + */ + protected final static DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance(); + + /** + * the xml document to process + */ + protected Document document; + + /** + * the format to use for the report. Must be FRAMES or NOFRAMES + * + */ + protected String format; + + /** + * the style directory. XSLs should be read from here if necessary + */ + protected File styleDir; + + /** + * Task + */ + protected Task task; + + /** + * the destination directory, this is the root from where html should be + * generated + */ + protected File toDir; + + public AggregateTransformer( Task task ) + { + this.task = task; + } + + /** + * set the extension of the output files + * + * @param ext The new Extension value + */ + public void setExtension( String ext ) + { + task.log( "extension is not used anymore", Project.MSG_WARN ); + } + + public void setFormat( Format format ) + { + this.format = format.getValue(); + } + + /** + * set the style directory. It is optional and will override the default xsl + * used. + * + * @param styledir the directory containing the xsl files if the user would + * like to override with its own style. + */ + public void setStyledir( File styledir ) + { + this.styleDir = styledir; + } + + /** + * set the destination directory + * + * @param todir The new Todir value + */ + public void setTodir( File todir ) + { + this.toDir = todir; + } + + public void setXmlDocument( Document doc ) + { + this.document = doc; + } + + public void transform() + throws BuildException + { + checkOptions(); + final long t0 = System.currentTimeMillis(); + try + { + Element root = document.getDocumentElement(); + XalanExecutor executor = XalanExecutor.newInstance( this ); + executor.execute(); + } + catch( Exception e ) + { + throw new BuildException( "Errors while applying transformations", e ); + } + final long dt = System.currentTimeMillis() - t0; + task.log( "Transform time: " + dt + "ms" ); + } + + /** + * Set the xml file to be processed. This is a helper if you want to set the + * file directly. Much more for testing purposes. + * + * @param xmlfile xml file to be processed + * @exception BuildException Description of Exception + */ + protected void setXmlfile( File xmlfile ) + throws BuildException + { + try + { + DocumentBuilder builder = dbfactory.newDocumentBuilder(); + InputStream in = new FileInputStream( xmlfile ); + try + { + Document doc = builder.parse( in ); + setXmlDocument( doc ); + } + finally + { + in.close(); + } + } + catch( Exception e ) + { + throw new BuildException( "Error while parsing document: " + xmlfile, e ); + } + } + + /** + * Get the systemid of the appropriate stylesheet based on its name and + * styledir. If no styledir is defined it will load it as a java resource in + * the xsl child package, otherwise it will get it from the given directory. + * + * @return The StylesheetSystemId value + * @throws IOException thrown if the requested stylesheet does not exist. + */ + protected String getStylesheetSystemId() + throws IOException + { + String xslname = "junit-frames.xsl"; + if( NOFRAMES.equals( format ) ) + { + xslname = "junit-noframes.xsl"; + } + URL url = null; + if( styleDir == null ) + { + url = getClass().getResource( "xsl/" + xslname ); + if( url == null ) + { + throw new FileNotFoundException( "Could not find jar resource " + xslname ); + } + } + else + { + File file = new File( styleDir, xslname ); + if( !file.exists() ) + { + throw new FileNotFoundException( "Could not find file '" + file + "'" ); + } + url = new URL( "file", "", file.getAbsolutePath() ); + } + return url.toExternalForm(); + } + + /** + * check for invalid options + * + * @exception BuildException Description of Exception + */ + protected void checkOptions() + throws BuildException + { + // set the destination directory relative from the project if needed. + if( toDir == null ) + { + toDir = task.getProject().resolveFile( "." ); + } + else if( !toDir.isAbsolute() ) + { + toDir = task.getProject().resolveFile( toDir.getPath() ); + } + } + + public static class Format extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{FRAMES, NOFRAMES}; + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java new file mode 100644 index 000000000..b2173d3e8 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.util.Vector; + +/** + * Baseclass for BatchTest and JUnitTest. + * + * @author Stefan Bodewig + * @author Stephane Bailliez + */ +public abstract class BaseTest +{ + protected boolean haltOnError = false; + protected boolean haltOnFail = false; + protected boolean filtertrace = true; + protected boolean fork = false; + protected String ifProperty = null; + protected String unlessProperty = null; + protected Vector formatters = new Vector(); + /** + * destination directory + */ + protected File destDir = null; + protected String errorProperty; + + protected String failureProperty; + + public void setErrorProperty( String errorProperty ) + { + this.errorProperty = errorProperty; + } + + public void setFailureProperty( String failureProperty ) + { + this.failureProperty = failureProperty; + } + + public void setFiltertrace( boolean value ) + { + filtertrace = value; + } + + public void setFork( boolean value ) + { + fork = value; + } + + public void setHaltonerror( boolean value ) + { + haltOnError = value; + } + + public void setHaltonfailure( boolean value ) + { + haltOnFail = value; + } + + public void setIf( String propertyName ) + { + ifProperty = propertyName; + } + + /** + * Sets the destination directory. + * + * @param destDir The new Todir value + */ + public void setTodir( File destDir ) + { + this.destDir = destDir; + } + + public void setUnless( String propertyName ) + { + unlessProperty = propertyName; + } + + public java.lang.String getErrorProperty() + { + return errorProperty; + } + + public java.lang.String getFailureProperty() + { + return failureProperty; + } + + public boolean getFiltertrace() + { + return filtertrace; + } + + public boolean getFork() + { + return fork; + } + + public boolean getHaltonerror() + { + return haltOnError; + } + + public boolean getHaltonfailure() + { + return haltOnFail; + } + + /** + * @return the destination directory as an absolute path if it exists + * otherwise return null + */ + public String getTodir() + { + if( destDir != null ) + { + return destDir.getAbsolutePath(); + } + return null; + } + + public void addFormatter( FormatterElement elem ) + { + formatters.addElement( elem ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java new file mode 100644 index 000000000..115e2c1a3 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; + +/** + *

      + * + * Create then run JUnitTest's based on the list of files given by + * the fileset attribute.

      + * + * Every .java or .class file in the fileset is + * assumed to be a testcase. A JUnitTest is created for each of + * these named classes with basic setup inherited from the parent BatchTest + * . + * + * @author Jeff Martin + * @author Stefan Bodewig + * @author Stephane Bailliez + * @see JUnitTest + */ +public final class BatchTest extends BaseTest +{ + + /** + * the list of filesets containing the testcase filename rules + */ + private Vector filesets = new Vector(); + + /** + * the reference to the project + */ + private Project project; + + /** + * create a new batchtest instance + * + * @param project the project it depends on. + */ + public BatchTest( Project project ) + { + this.project = project; + } + + /** + * Convenient method to convert a pathname without extension to a fully + * qualified classname. For example org/apache/Whatever will be + * converted to org.apache.Whatever + * + * @param filename the filename to "convert" to a classname. + * @return the classname matching the filename. + */ + public final static String javaToClass( String filename ) + { + return filename.replace( File.separatorChar, '.' ); + } + + /** + * Return all JUnitTest instances obtain by applying the fileset + * rules. + * + * @return an enumeration of all elements of this batchtest that are a + * JUnitTest instance. + */ + public final Enumeration elements() + { + JUnitTest[] tests = createAllJUnitTest(); + return Enumerations.fromArray( tests ); + } + + /** + * Add a new fileset instance to this batchtest. Whatever the fileset is, + * only filename that are .java or .class will be + * considered as 'candidates'. + * + * @param fs the new fileset containing the rules to get the testcases. + */ + public void addFileSet( FileSet fs ) + { + filesets.addElement( fs ); + } + + /** + * Convenient method to merge the JUnitTest s of this batchtest to + * a Vector . + * + * @param v the vector to which should be added all individual tests of this + * batch test. + */ + final void addTestsTo( Vector v ) + { + JUnitTest[] tests = createAllJUnitTest(); + v.ensureCapacity( v.size() + tests.length ); + for( int i = 0; i < tests.length; i++ ) + { + v.addElement( tests[i] ); + } + } + + /** + * Iterate over all filesets and return the filename of all files that end + * with .java or .class . This is to avoid wrapping a + * JUnitTest over an xml file for example. A Testcase is obviously a + * java file (compiled or not). + * + * @return an array of filenames without their extension. As they should + * normally be taken from their root, filenames should match their + * fully qualified class name (If it is not the case it will fail when + * running the test). For the class org/apache/Whatever.class + * it will return org/apache/Whatever . + */ + private String[] getFilenames() + { + Vector v = new Vector(); + final int size = this.filesets.size(); + for( int j = 0; j < size; j++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( j ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + ds.scan(); + String[] f = ds.getIncludedFiles(); + for( int k = 0; k < f.length; k++ ) + { + String pathname = f[k]; + if( pathname.endsWith( ".java" ) ) + { + v.addElement( pathname.substring( 0, pathname.length() - ".java".length() ) ); + } + else if( pathname.endsWith( ".class" ) ) + { + v.addElement( pathname.substring( 0, pathname.length() - ".class".length() ) ); + } + } + } + + String[] files = new String[v.size()]; + v.copyInto( files ); + return files; + } + + /** + * Create all JUnitTest s based on the filesets. Each instance is + * configured to match this instance properties. + * + * @return the array of all JUnitTest s that belongs to this batch. + */ + private JUnitTest[] createAllJUnitTest() + { + String[] filenames = getFilenames(); + JUnitTest[] tests = new JUnitTest[filenames.length]; + for( int i = 0; i < tests.length; i++ ) + { + String classname = javaToClass( filenames[i] ); + tests[i] = createJUnitTest( classname ); + } + return tests; + } + + /** + * Create a JUnitTest that has the same property as this + * BatchTest instance. + * + * @param classname the name of the class that should be run as a + * JUnitTest . It must be a fully qualified name. + * @return the JUnitTest over the given classname. + */ + private JUnitTest createJUnitTest( String classname ) + { + JUnitTest test = new JUnitTest(); + test.setName( classname ); + test.setHaltonerror( this.haltOnError ); + test.setHaltonfailure( this.haltOnFail ); + test.setFiltertrace( this.filtertrace ); + test.setFork( this.fork ); + test.setIf( this.ifProperty ); + test.setUnless( this.unlessProperty ); + test.setTodir( this.destDir ); + test.setFailureProperty( failureProperty ); + test.setErrorProperty( errorProperty ); + Enumeration list = this.formatters.elements(); + while( list.hasMoreElements() ) + { + test.addFormatter( ( FormatterElement )list.nextElement() ); + } + return test; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java new file mode 100644 index 000000000..81ee9ac8f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import org.apache.tools.ant.BuildException; + +/** + * Prints plain text output of the test to a specified Writer. Inspired by the + * PlainJUnitResultFormatter. + * + * @author Robert Watkins + * @see FormatterElement + * @see PlainJUnitResultFormatter + */ +public class BriefJUnitResultFormatter implements JUnitResultFormatter +{ + + /** + * Formatter for timings. + */ + private java.text.NumberFormat m_numberFormat = java.text.NumberFormat.getInstance(); + + /** + * Output suite has written to System.out + */ + private String systemOutput = null; + + /** + * Output suite has written to System.err + */ + private String systemError = null; + + /** + * Where to write the log to. + */ + private java.io.OutputStream m_out; + + /** + * Used for writing the results. + */ + private java.io.PrintWriter m_output; + + /** + * Used for writing formatted results to. + */ + private java.io.PrintWriter m_resultWriter; + + /** + * Used as part of formatting the results. + */ + private java.io.StringWriter m_results; + + public BriefJUnitResultFormatter() + { + m_results = new java.io.StringWriter(); + m_resultWriter = new java.io.PrintWriter( m_results ); + } + + /** + * Sets the stream the formatter is supposed to write its results to. + * + * @param out The new Output value + */ + public void setOutput( java.io.OutputStream out ) + { + m_out = out; + m_output = new java.io.PrintWriter( out ); + } + + public void setSystemError( String err ) + { + systemError = err; + } + + public void setSystemOutput( String out ) + { + systemOutput = out; + } + + /** + * A test caused an error. + * + * @param test The feature to be added to the Error attribute + * @param error The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable error ) + { + formatError( "\tCaused an ERROR", test, error ); + } + + /** + * Interface TestListener for JUnit <= 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + formatError( "\tFAILED", test, t ); + } + + /** + * Interface TestListener for JUnit > 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * A test ended. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) { } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + String newLine = System.getProperty( "line.separator" ); + StringBuffer sb = new StringBuffer( "Testsuite: " ); + sb.append( suite.getName() ); + sb.append( newLine ); + sb.append( "Tests run: " ); + sb.append( suite.runCount() ); + sb.append( ", Failures: " ); + sb.append( suite.failureCount() ); + sb.append( ", Errors: " ); + sb.append( suite.errorCount() ); + sb.append( ", Time elapsed: " ); + sb.append( m_numberFormat.format( suite.getRunTime() / 1000.0 ) ); + sb.append( " sec" ); + sb.append( newLine ); + sb.append( newLine ); + + // append the err and output streams to the log + if( systemOutput != null && systemOutput.length() > 0 ) + { + sb.append( "------------- Standard Output ---------------" ) + .append( newLine ) + .append( systemOutput ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + if( systemError != null && systemError.length() > 0 ) + { + sb.append( "------------- Standard Error -----------------" ) + .append( newLine ) + .append( systemError ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + if( output() != null ) + { + try + { + output().write( sb.toString() ); + resultWriter().close(); + output().write( m_results.toString() ); + output().flush(); + } + finally + { + if( m_out != ( Object )System.out && + m_out != ( Object )System.err ) + { + try + { + m_out.close(); + } + catch( java.io.IOException e ) + {} + } + } + } + } + + /** + * A test started. + * + * @param test Description of Parameter + */ + public void startTest( Test test ) { } + + /** + * The whole testsuite started. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void startTestSuite( JUnitTest suite ) + throws BuildException { } + + /** + * Format an error and print it. + * + * @param type Description of Parameter + * @param test Description of Parameter + * @param error Description of Parameter + */ + protected synchronized void formatError( String type, Test test, + Throwable error ) + { + if( test != null ) + { + endTest( test ); + } + + resultWriter().println( formatTest( test ) + type ); + resultWriter().println( error.getMessage() ); + String strace = JUnitTestRunner.getFilteredTrace( error ); + resultWriter().println( strace ); + resultWriter().println( "" ); + } + + /** + * Format the test for printing.. + * + * @param test Description of Parameter + * @return Description of the Returned Value + */ + protected String formatTest( Test test ) + { + if( test == null ) + { + return "Null Test: "; + } + else + { + return "Testcase: " + test.toString() + ":"; + } + } + + protected java.io.PrintWriter output() + { + return m_output; + } + + protected java.io.PrintWriter resultWriter() + { + return m_resultWriter; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java new file mode 100644 index 000000000..3fab8d7be --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.util.Vector; +import org.w3c.dom.Attr; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Comment; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.ProcessingInstruction; +import org.w3c.dom.Text; + +/** + * Some utilities that might be useful when manipulating DOM trees. + * + * @author Stephane Bailliez + */ +public final class DOMUtil +{ + + /** + * unused constructor + */ + private DOMUtil() { } + + + /** + * Iterate over the children of a given node and return the first node that + * has a specific name. + * + * @param parent the node to search child from. Can be null . + * @param tagname the child name we are looking for. Cannot be null + * . + * @return the first child that matches the given name or null if + * the parent is null or if a child does not match the given + * name. + */ + public static Element getChildByTagName( Node parent, String tagname ) + { + if( parent == null ) + { + return null; + } + NodeList childList = parent.getChildNodes(); + final int len = childList.getLength(); + for( int i = 0; i < len; i++ ) + { + Node child = childList.item( i ); + if( child != null && child.getNodeType() == Node.ELEMENT_NODE && + child.getNodeName().equals( tagname ) ) + { + return ( Element )child; + } + } + return null; + } + + /** + * return the attribute value of an element. + * + * @param node the node to get the attribute from. + * @param name the name of the attribute we are looking for the value. + * @return the value of the requested attribute or null if the + * attribute was not found or if node is not an Element + * . + */ + public static String getNodeAttribute( Node node, String name ) + { + if( node instanceof Element ) + { + Element element = ( Element )node; + return element.getAttribute( name ); + } + return null; + } + + /** + * Simple tree walker that will clone recursively a node. This is to avoid + * using parser-specific API such as Sun's changeNodeOwner when we + * are dealing with DOM L1 implementations since cloneNode(boolean) + * will not change the owner document. changeNodeOwner is much + * faster and avoid the costly cloning process. importNode is in + * the DOM L2 interface. + * + * @param parent the node parent to which we should do the import to. + * @param child the node to clone recursively. Its clone will be appended to + * parent . + * @return the cloned node that is appended to parent + */ + public final static Node importNode( Node parent, Node child ) + { + Node copy = null; + final Document doc = parent.getOwnerDocument(); + + switch ( child.getNodeType() ) + { + case Node.CDATA_SECTION_NODE: + copy = doc.createCDATASection( ( ( CDATASection )child ).getData() ); + break; + case Node.COMMENT_NODE: + copy = doc.createComment( ( ( Comment )child ).getData() ); + break; + case Node.DOCUMENT_FRAGMENT_NODE: + copy = doc.createDocumentFragment(); + break; + case Node.ELEMENT_NODE: + final Element elem = doc.createElement( ( ( Element )child ).getTagName() ); + copy = elem; + final NamedNodeMap attributes = child.getAttributes(); + if( attributes != null ) + { + final int size = attributes.getLength(); + for( int i = 0; i < size; i++ ) + { + final Attr attr = ( Attr )attributes.item( i ); + elem.setAttribute( attr.getName(), attr.getValue() ); + } + } + break; + case Node.ENTITY_REFERENCE_NODE: + copy = doc.createEntityReference( child.getNodeName() ); + break; + case Node.PROCESSING_INSTRUCTION_NODE: + final ProcessingInstruction pi = ( ProcessingInstruction )child; + copy = doc.createProcessingInstruction( pi.getTarget(), pi.getData() ); + break; + case Node.TEXT_NODE: + copy = doc.createTextNode( ( ( Text )child ).getData() ); + break; + default: + // this should never happen + throw new IllegalStateException( "Invalid node type: " + child.getNodeType() ); + } + + // okay we have a copy of the child, now the child becomes the parent + // and we are iterating recursively over its children. + try + { + final NodeList children = child.getChildNodes(); + if( children != null ) + { + final int size = children.getLength(); + for( int i = 0; i < size; i++ ) + { + final Node newChild = children.item( i ); + if( newChild != null ) + { + importNode( copy, newChild ); + } + } + } + } + catch( DOMException ignored ) + { + } + + // bingo append it. (this should normally not be done here) + parent.appendChild( copy ); + return copy; + } + + /** + * list a set of node that match a specific filter. The list can be made + * recursively or not. + * + * @param parent the parent node to search from + * @param filter the filter that children should match. + * @param recurse true if you want the list to be made recursively + * otherwise false . + * @return Description of the Returned Value + */ + public static NodeList listChildNodes( Node parent, NodeFilter filter, boolean recurse ) + { + NodeListImpl matches = new NodeListImpl(); + NodeList children = parent.getChildNodes(); + if( children != null ) + { + final int len = children.getLength(); + for( int i = 0; i < len; i++ ) + { + Node child = children.item( i ); + if( filter.accept( child ) ) + { + matches.addElement( child ); + } + if( recurse ) + { + NodeList recmatches = listChildNodes( child, filter, recurse ); + final int reclength = matches.getLength(); + for( int j = 0; j < reclength; j++ ) + { + matches.addElement( recmatches.item( i ) ); + } + } + } + } + return matches; + } + + /** + * Filter interface to be applied when iterating over a DOM tree. Just think + * of it like a FileFilter clone. + * + * @author RT + */ + public interface NodeFilter + { + /** + * @param node the node to check for acceptance. + * @return true if the node is accepted by this filter, + * otherwise false + */ + boolean accept( Node node ); + } + + /** + * custom implementation of a nodelist + * + * @author RT + */ + public static class NodeListImpl extends Vector implements NodeList + { + public int getLength() + { + return size(); + } + + public Node item( int i ) + { + try + { + return ( Node )elementAt( i ); + } + catch( ArrayIndexOutOfBoundsException e ) + { + return null;// conforming to NodeList interface + } + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java new file mode 100644 index 000000000..bc75a4e5a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * A couple of methods related to enumerations that might be useful. This class + * should probably disappear once the required JDK is set to 1.2 instead of 1.1. + * + * @author Stephane Bailliez + */ +public final class Enumerations +{ + + private Enumerations() { } + + /** + * creates an enumeration from an array of objects. + * + * @param array the array of object to enumerate. + * @return the enumeration over the array of objects. + */ + public static Enumeration fromArray( Object[] array ) + { + return new ArrayEnumeration( array ); + } + + /** + * creates an enumeration from an array of enumeration. The created + * enumeration will sequentially enumerate over all elements of each + * enumeration and skip null enumeration elements in the array. + * + * @param enums the array of enumerations. + * @return the enumeration over the array of enumerations. + */ + public static Enumeration fromCompound( Enumeration[] enums ) + { + return new CompoundEnumeration( enums ); + } + +} + + +/** + * Convenient enumeration over an array of objects. + * + * @author Stephane Bailliez + */ +class ArrayEnumeration implements Enumeration +{ + + /** + * object array + */ + private Object[] array; + + /** + * current index + */ + private int pos; + + /** + * Initialize a new enumeration that wraps an array. + * + * @param array the array of object to enumerate. + */ + public ArrayEnumeration( Object[] array ) + { + this.array = array; + this.pos = 0; + } + + /** + * Tests if this enumeration contains more elements. + * + * @return true if and only if this enumeration object contains + * at least one more element to provide; false otherwise. + */ + public boolean hasMoreElements() + { + return ( pos < array.length ); + } + + /** + * Returns the next element of this enumeration if this enumeration object + * has at least one more element to provide. + * + * @return the next element of this enumeration. + * @throws NoSuchElementException if no more elements exist. + */ + public Object nextElement() + throws NoSuchElementException + { + if( hasMoreElements() ) + { + Object o = array[pos]; + pos++; + return o; + } + throw new NoSuchElementException(); + } +} + +/** + * Convenient enumeration over an array of enumeration. For example:

      + * Enumeration e1 = v1.elements();
      + * while (e1.hasMoreElements()){
      + *    // do something
      + * }
      + * Enumeration e2 = v2.elements();
      + * while (e2.hasMoreElements()){
      + *    // do the same thing
      + * }
      + * 
      can be written as:
      + * Enumeration[] enums = { v1.elements(), v2.elements() };
      + * Enumeration e = Enumerations.fromCompound(enums);
      + * while (e.hasMoreElements()){
      + *    // do something
      + * }
      + * 
      Note that the enumeration will skip null elements in the array. The + * following is thus possible:
      + * Enumeration[] enums = { v1.elements(), null, v2.elements() }; // a null enumeration in the array
      + * Enumeration e = Enumerations.fromCompound(enums);
      + * while (e.hasMoreElements()){
      + *    // do something
      + * }
      + * 
      + * + * @author Stephane Bailliez + */ +class CompoundEnumeration implements Enumeration +{ + + /** + * index in the enums array + */ + private int index = 0; + + /** + * enumeration array + */ + private Enumeration[] enumArray; + + public CompoundEnumeration( Enumeration[] enumarray ) + { + this.enumArray = enumarray; + } + + /** + * Tests if this enumeration contains more elements. + * + * @return true if and only if this enumeration object contains + * at least one more element to provide; false otherwise. + */ + public boolean hasMoreElements() + { + while( index < enumArray.length ) + { + if( enumArray[index] != null && enumArray[index].hasMoreElements() ) + { + return true; + } + index++; + } + return false; + } + + /** + * Returns the next element of this enumeration if this enumeration object + * has at least one more element to provide. + * + * @return the next element of this enumeration. + * @throws NoSuchElementException if no more elements exist. + */ + public Object nextElement() + throws NoSuchElementException + { + if( hasMoreElements() ) + { + return enumArray[index].nextElement(); + } + throw new NoSuchElementException(); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java new file mode 100644 index 000000000..b7db0aa61 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + *

      + * + * A wrapper for the implementations of JUnitResultFormatter. In + * particular, used as a nested <formatter> element in a + * <junit> task.

      + * + * For example,

      + *       <junit printsummary="no" haltonfailure="yes" fork="false">
      + *           <formatter type="plain" usefile="false" />
      + *           <test name="org.apache.ecs.InternationalCharTest" />
      + *       </junit>
      adds a plain type + * implementation (PlainJUnitResultFormatter) to display the + * results of the test.

      + * + * Either the type or the classname attribute must be + * set. + * + * @author Stefan Bodewig + * @see JUnitTask + * @see XMLJUnitResultFormatter + * @see BriefJUnitResultFormatter + * @see PlainJUnitResultFormatter + * @see JUnitResultFormatter + */ +public class FormatterElement +{ + private OutputStream out = System.out; + private boolean useFile = true; + + private String classname; + private String extension; + private File outFile; + + /** + *

      + * + * Set name of class to be used as the formatter.

      + * + * This class must implement JUnitResultFormatter + * + * @param classname The new Classname value + */ + public void setClassname( String classname ) + { + this.classname = classname; + } + + public void setExtension( String ext ) + { + this.extension = ext; + } + + /** + *

      + * + * Set output stream for formatter to use.

      + * + * Defaults to standard out. + * + * @param out The new Output value + */ + public void setOutput( OutputStream out ) + { + this.out = out; + } + + /** + *

      + * + * Quick way to use a standard formatter.

      + * + * At the moment, there are three supported standard formatters. + *

        + *
      • The xml type uses a XMLJUnitResultFormatter + * . + *
      • The brief type uses a BriefJUnitResultFormatter + * . + *
      • The plain type (the default) uses a PlainJUnitResultFormatter + * . + *
      + *

      + * + * Sets classname attribute - so you can't use that attribute + * if you use this one. + * + * @param type The new Type value + */ + public void setType( TypeAttribute type ) + { + if( "xml".equals( type.getValue() ) ) + { + setClassname( "org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter" ); + setExtension( ".xml" ); + } + else + { + if( "brief".equals( type.getValue() ) ) + { + setClassname( "org.apache.tools.ant.taskdefs.optional.junit.BriefJUnitResultFormatter" ); + } + else + {// must be plain, ensured by TypeAttribute + setClassname( "org.apache.tools.ant.taskdefs.optional.junit.PlainJUnitResultFormatter" ); + } + setExtension( ".txt" ); + } + } + + /** + * Set whether the formatter should log to file. + * + * @param useFile The new UseFile value + */ + public void setUseFile( boolean useFile ) + { + this.useFile = useFile; + } + + /** + * Get name of class to be used as the formatter. + * + * @return The Classname value + */ + public String getClassname() + { + return classname; + } + + public String getExtension() + { + return extension; + } + + /** + *

      + * + * Set the file which the formatte should log to.

      + * + * Note that logging to file must be enabled . + * + * @param out The new Outfile value + */ + void setOutfile( File out ) + { + this.outFile = out; + } + + /** + * Get whether the formatter should log to file. + * + * @return The UseFile value + */ + boolean getUseFile() + { + return useFile; + } + + JUnitResultFormatter createFormatter() + throws BuildException + { + if( classname == null ) + { + throw new BuildException( "you must specify type or classname" ); + } + + Class f = null; + try + { + f = Class.forName( classname ); + } + catch( ClassNotFoundException e ) + { + throw new BuildException( e ); + } + + Object o = null; + try + { + o = f.newInstance(); + } + catch( InstantiationException e ) + { + throw new BuildException( e ); + } + catch( IllegalAccessException e ) + { + throw new BuildException( e ); + } + + if( !( o instanceof JUnitResultFormatter ) ) + { + throw new BuildException( classname + " is not a JUnitResultFormatter" ); + } + + JUnitResultFormatter r = ( JUnitResultFormatter )o; + + if( useFile && outFile != null ) + { + try + { + out = new FileOutputStream( outFile ); + } + catch( java.io.IOException e ) + { + throw new BuildException( e ); + } + } + r.setOutput( out ); + return r; + } + + /** + *

      + * + * Enumerated attribute with the values "plain", "xml" and "brief".

      + * + * Use to enumerate options for type attribute. + * + * @author RT + */ + public static class TypeAttribute extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"plain", "xml", "brief"}; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java new file mode 100644 index 000000000..7f5f3a4ed --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.OutputStream; +import junit.framework.TestListener; +import org.apache.tools.ant.BuildException; + +/** + * This Interface describes classes that format the results of a JUnit testrun. + * + * @author Stefan Bodewig + */ +public interface JUnitResultFormatter extends TestListener +{ + /** + * The whole testsuite started. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + void startTestSuite( JUnitTest suite ) + throws BuildException; + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + void endTestSuite( JUnitTest suite ) + throws BuildException; + + /** + * Sets the stream the formatter is supposed to write its results to. + * + * @param out The new Output value + */ + void setOutput( OutputStream out ); + + /** + * This is what the test has written to System.out + * + * @param out The new SystemOutput value + */ + void setSystemOutput( String out ); + + /** + * This is what the test has written to System.err + * + * @param err The new SystemError value + */ + void setSystemError( String err ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java new file mode 100644 index 000000000..b53a45da3 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java @@ -0,0 +1,812 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Random; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteWatchdog; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; + +/** + * Ant task to run JUnit tests.

      + * + * JUnit is a framework to create unit test. It has been initially created by + * Erich Gamma and Kent Beck. JUnit can be found at http://www.junit.org .

      + * + * JUnitTask can run a single specific JUnitTest using + * the test element. For example, the following target

      + *   <target name="test-int-chars" depends="jar-test">
      + *       <echo message="testing international characters"/>
      + *       <junit printsummary="no" haltonfailure="yes" fork="false">
      + *           <classpath refid="classpath"/>
      + *           <formatter type="plain" usefile="false" />
      + *           <test name="org.apache.ecs.InternationalCharTest" />
      + *       </junit>
      + *   </target>
      + * 
      runs a single junit test (org.apache.ecs.InternationalCharTest + * ) in the current VM using the path with id classpath as + * classpath and presents the results formatted using the standard plain + * formatter on the command line.

      + * + * This task can also run batches of tests. The batchtest element + * creates a BatchTest based on a fileset. This allows, for + * example, all classes found in directory to be run as testcases. For example, + *

      + * <target name="run-tests" depends="dump-info,compile-tests" if="junit.present">
      + *   <junit printsummary="no" haltonfailure="yes" fork="${junit.fork}">
      + *     <jvmarg value="-classic"/>
      + *     <classpath refid="tests-classpath"/>
      + *     <sysproperty key="build.tests" value="${build.tests}"/>
      + *     <formatter type="brief" usefile="false" />
      + *     <batchtest>
      + *       <fileset dir="${tests.dir}">
      + *         <include name="**/*Test*" />
      + *       </fileset>
      + *     </batchtest>
      + *   </junit>
      + * </target>
      + * 
      this target finds any classes with a test + * directory anywhere in their path (under the top ${tests.dir}, of + * course) and creates JUnitTest's for each one.

      + * + * Of course, <junit> and <batch> elements + * can be combined for more complex tests. For an example, see the ant build.xml + * target run-tests (the second example is an edited version).

      + * + * To spawn a new Java VM to prevent interferences between different testcases, + * you need to enable fork. A number of attributes and elements + * allow you to set up how this JVM runs. + *

        + *
      • {@link #setTimeout} property sets the maximum time allowed before a + * test is 'timed out' + *
      • {@link #setMaxmemory} property sets memory assignment for the forked + * jvm + *
      • {@link #setJvm} property allows the jvm to be specified + *
      • The <jvmarg> element sets arguements to be passed + * to the forked jvm + *
      + * + * + * @author Thomas Haas + * @author Stefan Bodewig + * @author Stephane Bailliez + * @author Gerrit Riessen + * @author Erik Hatcher + * @see JUnitTest + * @see BatchTest + */ +public class JUnitTask extends Task +{ + + private CommandlineJava commandline = new CommandlineJava(); + private Vector tests = new Vector(); + private Vector batchTests = new Vector(); + private Vector formatters = new Vector(); + private File dir = null; + + private Integer timeout = null; + private boolean summary = false; + private String summaryValue = ""; + private boolean filtertrace = true; + private JUnitTestRunner runner = null; + + /** + * Creates a new JUnitRunner and enables fork of a new Java VM. + * + * @exception Exception Description of Exception + */ + public JUnitTask() + throws Exception + { + commandline.setClassname( "org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" ); + } + + /** + * The directory to invoke the VM in. Ignored if no JVM is forked. + * + * @param dir the directory to invoke the JVM from. + * @see #setFork(boolean) + */ + public void setDir( File dir ) + { + this.dir = dir; + } + + /** + * Tells this task to set the named property to "true" when there is a error + * in a test. This property is applied on all BatchTest (batchtest) and + * JUnitTest (test), however, it can possibly be overriden by their own + * properties. + * + * @param propertyName The new ErrorProperty value + */ + public void setErrorProperty( String propertyName ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setErrorProperty( propertyName ); + } + } + + /** + * Tells this task to set the named property to "true" when there is a + * failure in a test. This property is applied on all BatchTest (batchtest) + * and JUnitTest (test), however, it can possibly be overriden by their own + * properties. + * + * @param propertyName The new FailureProperty value + */ + public void setFailureProperty( String propertyName ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setFailureProperty( propertyName ); + } + } + + /** + * Tells this task whether to smartly filter the stack frames of JUnit + * testcase errors and failures before reporting them. This property is + * applied on all BatchTest (batchtest) and JUnitTest (test) however it can + * possibly be overridden by their own properties. + * + * @param value false if it should not filter, otherwise true + * + */ + public void setFiltertrace( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setFiltertrace( value ); + } + } + + /** + * Tells whether a JVM should be forked for each testcase. It avoids + * interference between testcases and possibly avoids hanging the build. + * this property is applied on all BatchTest (batchtest) and JUnitTest + * (test) however it can possibly be overridden by their own properties. + * + * @param value true if a JVM should be forked, otherwise false + * + * @see #setTimeout + */ + public void setFork( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setFork( value ); + } + } + + /** + * Tells this task to halt when there is an error in a test. this property + * is applied on all BatchTest (batchtest) and JUnitTest (test) however it + * can possibly be overridden by their own properties. + * + * @param value true if it should halt, otherwise false + */ + public void setHaltonerror( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setHaltonerror( value ); + } + } + + /** + * Tells this task to halt when there is a failure in a test. this property + * is applied on all BatchTest (batchtest) and JUnitTest (test) however it + * can possibly be overridden by their own properties. + * + * @param value true if it should halt, otherwise false + */ + public void setHaltonfailure( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setHaltonfailure( value ); + } + } + + /** + * Set a new VM to execute the testcase. Default is java . Ignored + * if no JVM is forked. + * + * @param value the new VM to use instead of java + * @see #setFork(boolean) + */ + public void setJvm( String value ) + { + commandline.setVm( value ); + } + + /** + * Set the maximum memory to be used by all forked JVMs. + * + * @param max the value as defined by -mx or -Xmx in the + * java command line options. + */ + public void setMaxmemory( String max ) + { + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + createJvmarg().setValue( "-mx" + max ); + } + else + { + createJvmarg().setValue( "-Xmx" + max ); + } + } + + /** + * Tells whether the task should print a short summary of the task. + * + * @param value true to print a summary, withOutAndErr to + * include the test's output as well, false otherwise. + * @see SummaryJUnitResultFormatter + */ + public void setPrintsummary( SummaryAttribute value ) + { + summaryValue = value.getValue(); + summary = value.asBoolean(); + } + + /** + * Set the timeout value (in milliseconds). If the test is running for more + * than this value, the test will be canceled. (works only when in 'fork' + * mode). + * + * @param value the maximum time (in milliseconds) allowed before declaring + * the test as 'timed-out' + * @see #setFork(boolean) + */ + public void setTimeout( Integer value ) + { + timeout = value; + } + + /** + * Add a new formatter to all tests of this task. + * + * @param fe The feature to be added to the Formatter attribute + */ + public void addFormatter( FormatterElement fe ) + { + formatters.addElement( fe ); + } + + /** + * Add a nested sysproperty element. This might be useful to tranfer Ant + * properties to the testcases when JVM forking is not enabled. + * + * @param sysp The feature to be added to the Sysproperty attribute + */ + public void addSysproperty( Environment.Variable sysp ) + { + commandline.addSysproperty( sysp ); + } + + /** + * Add a new single testcase. + * + * @param test a new single testcase + * @see JUnitTest + */ + public void addTest( JUnitTest test ) + { + tests.addElement( test ); + } + + /** + * Create a new set of testcases (also called ..batchtest) and add it to the + * list. + * + * @return a new instance of a batch test. + * @see BatchTest + */ + public BatchTest createBatchTest() + { + BatchTest test = new BatchTest( project ); + batchTests.addElement( test ); + return test; + } + + /** + * <classpath> allows classpath to be set for tests. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + return commandline.createClasspath( project ).createPath(); + } + + /** + * Create a new JVM argument. Ignored if no JVM is forked. + * + * @return create a new JVM argument so that any argument can be passed to + * the JVM. + * @see #setFork(boolean) + */ + public Commandline.Argument createJvmarg() + { + return commandline.createVmArgument(); + } + + /** + * Runs the testcase. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Enumeration list = getIndividualTests(); + while( list.hasMoreElements() ) + { + JUnitTest test = ( JUnitTest )list.nextElement(); + if( test.shouldRun( project ) ) + { + execute( test ); + } + } + } + + /** + * Adds the jars or directories containing Ant, this task and JUnit to the + * classpath - this should make the forked JVM work without having to + * specify them directly. + */ + public void init() + { + addClasspathEntry( "/junit/framework/TestCase.class" ); + addClasspathEntry( "/org/apache/tools/ant/Task.class" ); + addClasspathEntry( "/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.class" ); + } + + /** + * Get the default output for a formatter. + * + * @return The DefaultOutput value + */ + protected OutputStream getDefaultOutput() + { + return new LogOutputStream( this, Project.MSG_INFO ); + } + + /** + * Merge all individual tests from the batchtest with all individual tests + * and return an enumeration over all JUnitTest . + * + * @return The IndividualTests value + */ + protected Enumeration getIndividualTests() + { + Enumeration[] enums = new Enumeration[batchTests.size() + 1]; + for( int i = 0; i < batchTests.size(); i++ ) + { + BatchTest batchtest = ( BatchTest )batchTests.elementAt( i ); + enums[i] = batchtest.elements(); + } + enums[enums.length - 1] = tests.elements(); + return Enumerations.fromCompound( enums ); + } + + /** + * return the file or null if does not use a file + * + * @param fe Description of Parameter + * @param test Description of Parameter + * @return The Output value + */ + protected File getOutput( FormatterElement fe, JUnitTest test ) + { + if( fe.getUseFile() ) + { + String filename = test.getOutfile() + fe.getExtension(); + File destFile = new File( test.getTodir(), filename ); + String absFilename = destFile.getAbsolutePath(); + return project.resolveFile( absFilename ); + } + return null; + } + + /** + * Search for the given resource and add the directory or archive that + * contains it to the classpath.

      + * + * Doesn't work for archives in JDK 1.1 as the URL returned by getResource + * doesn't contain the name of the archive.

      + * + * @param resource The feature to be added to the ClasspathEntry attribute + */ + protected void addClasspathEntry( String resource ) + { + URL url = getClass().getResource( resource ); + if( url != null ) + { + String u = url.toString(); + if( u.startsWith( "jar:file:" ) ) + { + int pling = u.indexOf( "!" ); + String jarName = u.substring( 9, pling ); + log( "Implicitly adding " + jarName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( jarName ) ).getAbsolutePath() ) ); + } + else if( u.startsWith( "file:" ) ) + { + int tail = u.indexOf( resource ); + String dirName = u.substring( 5, tail ); + log( "Implicitly adding " + dirName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( dirName ) ).getAbsolutePath() ) ); + } + else + { + log( "Don\'t know how to handle resource URL " + u, + Project.MSG_DEBUG ); + } + } + else + { + log( "Couldn\'t find " + resource, Project.MSG_DEBUG ); + } + } + + protected Enumeration allTests() + { + Enumeration[] enums = {tests.elements(), batchTests.elements()}; + return Enumerations.fromCompound( enums ); + } + + /** + * @return null if there is a timeout value, otherwise the watchdog + * instance. + * @exception BuildException Description of Exception + */ + protected ExecuteWatchdog createWatchdog() + throws BuildException + { + if( timeout == null ) + { + return null; + } + return new ExecuteWatchdog( timeout.intValue() ); + } + + /** + * Run the tests. + * + * @param test Description of Parameter + * @exception BuildException Description of Exception + */ + protected void execute( JUnitTest test ) + throws BuildException + { + // set the default values if not specified + //@todo should be moved to the test class instead. + if( test.getTodir() == null ) + { + test.setTodir( project.resolveFile( "." ) ); + } + + if( test.getOutfile() == null ) + { + test.setOutfile( "TEST-" + test.getName() ); + } + + // execute the test and get the return code + int exitValue = JUnitTestRunner.ERRORS; + boolean wasKilled = false; + if( !test.getFork() ) + { + exitValue = executeInVM( test ); + } + else + { + ExecuteWatchdog watchdog = createWatchdog(); + exitValue = executeAsForked( test, watchdog ); + // null watchdog means no timeout, you'd better not check with null + if( watchdog != null ) + { + wasKilled = watchdog.killedProcess(); + } + } + + // if there is an error/failure and that it should halt, stop everything otherwise + // just log a statement + boolean errorOccurredHere = exitValue == JUnitTestRunner.ERRORS; + boolean failureOccurredHere = exitValue != JUnitTestRunner.SUCCESS; + if( errorOccurredHere || failureOccurredHere ) + { + if( errorOccurredHere && test.getHaltonerror() + || failureOccurredHere && test.getHaltonfailure() ) + { + throw new BuildException( "Test " + test.getName() + " failed" + + ( wasKilled ? " (timeout)" : "" ), + location ); + } + else + { + log( "TEST " + test.getName() + " FAILED" + + ( wasKilled ? " (timeout)" : "" ), Project.MSG_ERR ); + if( errorOccurredHere && test.getErrorProperty() != null ) + { + project.setProperty( test.getErrorProperty(), "true" ); + } + if( failureOccurredHere && test.getFailureProperty() != null ) + { + project.setProperty( test.getFailureProperty(), "true" ); + } + } + } + } + + protected void handleErrorOutput( String line ) + { + if( runner != null ) + { + runner.handleErrorOutput( line ); + } + else + { + super.handleErrorOutput( line ); + } + } + + // in VM is not very nice since it could probably hang the + // whole build. IMHO this method should be avoided and it would be best + // to remove it in future versions. TBD. (SBa) + + + protected void handleOutput( String line ) + { + if( runner != null ) + { + runner.handleOutput( line ); + } + else + { + super.handleOutput( line ); + } + } + + /** + * Execute a testcase by forking a new JVM. The command will block until it + * finishes. To know if the process was destroyed or not, use the + * killedProcess() method of the watchdog class. + * + * @param test the testcase to execute. + * @param watchdog the watchdog in charge of cancelling the test if it + * exceeds a certain amount of time. Can be null , in this + * case the test could probably hang forever. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int executeAsForked( JUnitTest test, ExecuteWatchdog watchdog ) + throws BuildException + { + CommandlineJava cmd = ( CommandlineJava )commandline.clone(); + + cmd.setClassname( "org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" ); + cmd.createArgument().setValue( test.getName() ); + cmd.createArgument().setValue( "filtertrace=" + test.getFiltertrace() ); + cmd.createArgument().setValue( "haltOnError=" + test.getHaltonerror() ); + cmd.createArgument().setValue( "haltOnFailure=" + test.getHaltonfailure() ); + if( summary ) + { + log( "Running " + test.getName(), Project.MSG_INFO ); + cmd.createArgument().setValue( "formatter=org.apache.tools.ant.taskdefs.optional.junit.SummaryJUnitResultFormatter" ); + } + + StringBuffer formatterArg = new StringBuffer( 128 ); + final FormatterElement[] feArray = mergeFormatters( test ); + for( int i = 0; i < feArray.length; i++ ) + { + FormatterElement fe = feArray[i]; + formatterArg.append( "formatter=" ); + formatterArg.append( fe.getClassname() ); + File outFile = getOutput( fe, test ); + if( outFile != null ) + { + formatterArg.append( "," ); + formatterArg.append( outFile ); + } + cmd.createArgument().setValue( formatterArg.toString() ); + formatterArg.setLength( 0 ); + } + + // Create a temporary file to pass the Ant properties to the forked test + File propsFile = new File( "junit" + ( new Random( System.currentTimeMillis() ) ).nextLong() + ".properties" ); + cmd.createArgument().setValue( "propsfile=" + propsFile.getAbsolutePath() ); + Hashtable p = project.getProperties(); + Properties props = new Properties(); + for( Enumeration enum = p.keys(); enum.hasMoreElements(); ) + { + Object key = enum.nextElement(); + props.put( key, p.get( key ) ); + } + try + { + FileOutputStream outstream = new FileOutputStream( propsFile ); + props.save( outstream, "Ant JUnitTask generated properties file" ); + outstream.close(); + } + catch( java.io.IOException e ) + { + throw new BuildException( "Error creating temporary properties file.", e, location ); + } + + Execute execute = new Execute( new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ), watchdog ); + execute.setCommandline( cmd.getCommandline() ); + execute.setAntRun( project ); + if( dir != null ) + { + execute.setWorkingDirectory( dir ); + } + + log( "Executing: " + cmd.toString(), Project.MSG_VERBOSE ); + int retVal; + try + { + retVal = execute.execute(); + } + catch( IOException e ) + { + throw new BuildException( "Process fork failed.", e, location ); + } + finally + { + if( !propsFile.delete() ) + throw new BuildException( "Could not delete temporary properties file." ); + } + + return retVal; + } + + /** + * Execute inside VM. + * + * @param test Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int executeInVM( JUnitTest test ) + throws BuildException + { + test.setProperties( project.getProperties() ); + if( dir != null ) + { + log( "dir attribute ignored if running in the same VM", Project.MSG_WARN ); + } + + CommandlineJava.SysProperties sysProperties = commandline.getSystemProperties(); + if( sysProperties != null ) + { + sysProperties.setSystem(); + } + try + { + log( "Using System properties " + System.getProperties(), Project.MSG_VERBOSE ); + AntClassLoader cl = null; + Path classpath = commandline.getClasspath(); + if( classpath != null ) + { + log( "Using CLASSPATH " + classpath, Project.MSG_VERBOSE ); + + cl = new AntClassLoader( null, project, classpath, false ); + // make sure the test will be accepted as a TestCase + cl.addSystemPackageRoot( "junit" ); + // will cause trouble in JDK 1.1 if omitted + cl.addSystemPackageRoot( "org.apache.tools.ant" ); + } + runner = new JUnitTestRunner( test, test.getHaltonerror(), test.getFiltertrace(), test.getHaltonfailure(), cl ); + if( summary ) + { + log( "Running " + test.getName(), Project.MSG_INFO ); + + SummaryJUnitResultFormatter f = + new SummaryJUnitResultFormatter(); + f.setWithOutAndErr( "withoutanderr".equalsIgnoreCase( summaryValue ) ); + f.setOutput( getDefaultOutput() ); + runner.addFormatter( f ); + } + + final FormatterElement[] feArray = mergeFormatters( test ); + for( int i = 0; i < feArray.length; i++ ) + { + FormatterElement fe = feArray[i]; + File outFile = getOutput( fe, test ); + if( outFile != null ) + { + fe.setOutfile( outFile ); + } + else + { + fe.setOutput( getDefaultOutput() ); + } + runner.addFormatter( fe.createFormatter() ); + } + + runner.run(); + return runner.getRetCode(); + } + finally + { + if( sysProperties != null ) + { + sysProperties.restoreSystem(); + } + } + } + + private FormatterElement[] mergeFormatters( JUnitTest test ) + { + Vector feVector = ( Vector )formatters.clone(); + test.addFormattersTo( feVector ); + FormatterElement[] feArray = new FormatterElement[feVector.size()]; + feVector.copyInto( feArray ); + return feArray; + } + + /** + * Print summary enumeration values. + * + * @author RT + */ + public static class SummaryAttribute extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"true", "yes", "false", "no", + "on", "off", "withOutAndErr"}; + } + + public boolean asBoolean() + { + return "true".equals( value ) + || "on".equals( value ) + || "yes".equals( value ) + || "withOutAndErr".equals( value ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java new file mode 100644 index 000000000..93c2b2d01 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.Project; + +/** + *

      + * + * Run a single JUnit test.

      + * + * The JUnit test is actually run by {@link JUnitTestRunner}. So read the doc + * comments for that class :) + * + * @author Thomas Haas + * @author Stefan Bodewig , + * @author Stephane Bailliez + * @see JUnitTask + * @see JUnitTestRunner + */ +public class JUnitTest extends BaseTest +{ + + /** + * the name of the test case + */ + private String name = null; + + /** + * the name of the result file + */ + private String outfile = null; + + // Snapshot of the system properties + private Properties props = null; + private long runTime; + + // @todo this is duplicating TestResult information. Only the time is not + // part of the result. So we'd better derive a new class from TestResult + // and deal with it. (SB) + private long runs, failures, errors; + + public JUnitTest() { } + + public JUnitTest( String name ) + { + this.name = name; + } + + public JUnitTest( String name, boolean haltOnError, boolean haltOnFailure, boolean filtertrace ) + { + this.name = name; + this.haltOnError = haltOnError; + this.haltOnFail = haltOnFailure; + this.filtertrace = filtertrace; + } + + public void setCounts( long runs, long failures, long errors ) + { + this.runs = runs; + this.failures = failures; + this.errors = errors; + } + + /** + * Set the name of the test class. + * + * @param value The new Name value + */ + public void setName( String value ) + { + name = value; + } + + /** + * Set the name of the output file. + * + * @param value The new Outfile value + */ + public void setOutfile( String value ) + { + outfile = value; + } + + public void setProperties( Hashtable p ) + { + props = new Properties(); + for( Enumeration enum = p.keys(); enum.hasMoreElements(); ) + { + Object key = enum.nextElement(); + props.put( key, p.get( key ) ); + } + } + + public void setRunTime( long runTime ) + { + this.runTime = runTime; + } + + public FormatterElement[] getFormatters() + { + FormatterElement[] fes = new FormatterElement[formatters.size()]; + formatters.copyInto( fes ); + return fes; + } + + /** + * Get the name of the test class. + * + * @return The Name value + */ + public String getName() + { + return name; + } + + /** + * Get the name of the output file + * + * @return the name of the output file. + */ + public String getOutfile() + { + return outfile; + } + + public Properties getProperties() + { + return props; + } + + public long getRunTime() + { + return runTime; + } + + public long errorCount() + { + return errors; + } + + public long failureCount() + { + return failures; + } + + public long runCount() + { + return runs; + } + + public boolean shouldRun( Project p ) + { + if( ifProperty != null && p.getProperty( ifProperty ) == null ) + { + return false; + } + else if( unlessProperty != null && + p.getProperty( unlessProperty ) != null ) + { + return false; + } + + return true; + } + + /** + * Convenient method to add formatters to a vector + * + * @param v The feature to be added to the FormattersTo attribute + */ + void addFormattersTo( Vector v ) + { + final int count = formatters.size(); + for( int i = 0; i < count; i++ ) + { + v.addElement( formatters.elementAt( i ) ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java new file mode 100644 index 000000000..29170e402 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java @@ -0,0 +1,637 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Vector; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestListener; +import junit.framework.TestResult; +import junit.framework.TestSuite; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.StringUtils; + +/** + * Simple Testrunner for JUnit that runs all tests of a testsuite.

      + * + * This TestRunner expects a name of a TestCase class as its argument. If this + * class provides a static suite() method it will be called and the resulting + * Test will be run. So, the signature should be

      
      + *     public static junit.framework.Test suite()
      + * 

      + * + * If no such method exists, all public methods starting with "test" and taking + * no argument will be run.

      + * + * Summary output is generated at the end. + * + * @author Stefan Bodewig + * @author Erik Hatcher + */ + +public class JUnitTestRunner implements TestListener +{ + + /** + * No problems with this test. + */ + public final static int SUCCESS = 0; + + /** + * Some tests failed. + */ + public final static int FAILURES = 1; + + /** + * An error occured. + */ + public final static int ERRORS = 2; + + /** + * Do we filter junit.*.* stack frames out of failure and error exceptions. + */ + private static boolean filtertrace = true; + + private final static String[] DEFAULT_TRACE_FILTERS = new String[]{ + "junit.framework.TestCase", + "junit.framework.TestResult", + "junit.framework.TestSuite", + "junit.framework.Assert.", // don't filter AssertionFailure + "junit.swingui.TestRunner", + "junit.awtui.TestRunner", + "junit.textui.TestRunner", + "java.lang.reflect.Method.invoke(", + "org.apache.tools.ant." + }; + + private static Vector fromCmdLine = new Vector(); + + /** + * Holds the registered formatters. + */ + private Vector formatters = new Vector(); + + /** + * Do we stop on errors. + */ + private boolean haltOnError = false; + + /** + * Do we stop on test failures. + */ + private boolean haltOnFailure = false; + + /** + * The corresponding testsuite. + */ + private Test suite = null; + + /** + * Returncode + */ + private int retCode = SUCCESS; + + /** + * Exception caught in constructor. + */ + private Exception exception; + + /** + * The TestSuite we are currently running. + */ + private JUnitTest junitTest; + + /** + * Collects TestResults. + */ + private TestResult res; + + /** + * output written during the test + */ + private PrintStream systemError; + + /** + * Error output during the test + */ + private PrintStream systemOut; + + /** + * Constructor for fork=true or when the user hasn't specified a classpath. + * + * @param test Description of Parameter + * @param haltOnError Description of Parameter + * @param filtertrace Description of Parameter + * @param haltOnFailure Description of Parameter + */ + public JUnitTestRunner( JUnitTest test, boolean haltOnError, boolean filtertrace, + boolean haltOnFailure ) + { + this( test, haltOnError, filtertrace, haltOnFailure, null ); + } + + /** + * Constructor to use when the user has specified a classpath. + * + * @param test Description of Parameter + * @param haltOnError Description of Parameter + * @param filtertrace Description of Parameter + * @param haltOnFailure Description of Parameter + * @param loader Description of Parameter + */ + public JUnitTestRunner( JUnitTest test, boolean haltOnError, boolean filtertrace, + boolean haltOnFailure, ClassLoader loader ) + { + //JUnitTestRunner.filtertrace = filtertrace; + this.filtertrace = filtertrace; + this.junitTest = test; + this.haltOnError = haltOnError; + this.haltOnFailure = haltOnFailure; + + try + { + Class testClass = null; + if( loader == null ) + { + testClass = Class.forName( test.getName() ); + } + else + { + testClass = loader.loadClass( test.getName() ); + AntClassLoader.initializeClass( testClass ); + } + + Method suiteMethod = null; + try + { + // check if there is a suite method + suiteMethod = testClass.getMethod( "suite", new Class[0] ); + } + catch( Exception e ) + { + // no appropriate suite method found. We don't report any + // error here since it might be perfectly normal. We don't + // know exactly what is the cause, but we're doing exactly + // the same as JUnit TestRunner do. We swallow the exceptions. + } + if( suiteMethod != null ) + { + // if there is a suite method available, then try + // to extract the suite from it. If there is an error + // here it will be caught below and reported. + suite = ( Test )suiteMethod.invoke( null, new Class[0] ); + } + else + { + // try to extract a test suite automatically + // this will generate warnings if the class is no suitable Test + suite = new TestSuite( testClass ); + } + + } + catch( Exception e ) + { + retCode = ERRORS; + exception = e; + } + } + + /** + * Returns a filtered stack trace. This is ripped out of + * junit.runner.BaseTestRunner. Scott M. Stirling. + * + * @param t Description of Parameter + * @return The FilteredTrace value + */ + public static String getFilteredTrace( Throwable t ) + { + String trace = StringUtils.getStackTrace( t ); + return JUnitTestRunner.filterStack( trace ); + } + + /** + * Filters stack frames from internal JUnit and Ant classes + * + * @param stack Description of Parameter + * @return Description of the Returned Value + */ + public static String filterStack( String stack ) + { + if( !filtertrace ) + { + return stack; + } + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter( sw ); + StringReader sr = new StringReader( stack ); + BufferedReader br = new BufferedReader( sr ); + + String line; + try + { + while( ( line = br.readLine() ) != null ) + { + if( !filterLine( line ) ) + { + pw.println( line ); + } + } + } + catch( Exception IOException ) + { + return stack;// return the stack unfiltered + } + return sw.toString(); + } + + /** + * Entry point for standalone (forked) mode. Parameters: testcaseclassname + * plus parameters in the format key=value, none of which is required. + * + * + * + * + * + * + * key + * + * + * + * description + * + * + * + * default value + * + * + * + * + * + * + * + * haltOnError + * + * + * + * halt test on errors? + * + * + * + * false + * + * + * + * + * + * + * + * haltOnFailure + * + * + * + * halt test on failures? + * + * + * + * false + * + * + * + * + * + * + * + * formatter + * + * + * + * A JUnitResultFormatter given as classname,filename. If filename is + * ommitted, System.out is assumed. + * + * + * + * none + * + * + * + * + * + * + * + * @param args The command line arguments + * @exception IOException Description of Exception + */ + public static void main( String[] args ) + throws IOException + { + boolean exitAtEnd = true; + boolean haltError = false; + boolean haltFail = false; + boolean stackfilter = true; + Properties props = new Properties(); + + if( args.length == 0 ) + { + System.err.println( "required argument TestClassName missing" ); + System.exit( ERRORS ); + } + + for( int i = 1; i < args.length; i++ ) + { + if( args[i].startsWith( "haltOnError=" ) ) + { + haltError = Project.toBoolean( args[i].substring( 12 ) ); + } + else if( args[i].startsWith( "haltOnFailure=" ) ) + { + haltFail = Project.toBoolean( args[i].substring( 14 ) ); + } + else if( args[i].startsWith( "filtertrace=" ) ) + { + stackfilter = Project.toBoolean( args[i].substring( 12 ) ); + } + else if( args[i].startsWith( "formatter=" ) ) + { + try + { + createAndStoreFormatter( args[i].substring( 10 ) ); + } + catch( BuildException be ) + { + System.err.println( be.getMessage() ); + System.exit( ERRORS ); + } + } + else if( args[i].startsWith( "propsfile=" ) ) + { + FileInputStream in = new FileInputStream( args[i].substring( 10 ) ); + props.load( in ); + in.close(); + } + } + + JUnitTest t = new JUnitTest( args[0] ); + + // Add/overlay system properties on the properties from the Ant project + Hashtable p = System.getProperties(); + for( Enumeration enum = p.keys(); enum.hasMoreElements(); ) + { + Object key = enum.nextElement(); + props.put( key, p.get( key ) ); + } + t.setProperties( props ); + + JUnitTestRunner runner = new JUnitTestRunner( t, haltError, stackfilter, haltFail ); + transferFormatters( runner ); + runner.run(); + System.exit( runner.getRetCode() ); + } + + /** + * Line format is: formatter=(, + * + * )? + * + * @param line Description of Parameter + * @exception BuildException Description of Exception + */ + private static void createAndStoreFormatter( String line ) + throws BuildException + { + FormatterElement fe = new FormatterElement(); + int pos = line.indexOf( ',' ); + if( pos == -1 ) + { + fe.setClassname( line ); + } + else + { + fe.setClassname( line.substring( 0, pos ) ); + fe.setOutfile( new File( line.substring( pos + 1 ) ) ); + } + fromCmdLine.addElement( fe.createFormatter() ); + } + + private static boolean filterLine( String line ) + { + for( int i = 0; i < DEFAULT_TRACE_FILTERS.length; i++ ) + { + if( line.indexOf( DEFAULT_TRACE_FILTERS[i] ) > 0 ) + { + return true; + } + } + return false; + } + + private static void transferFormatters( JUnitTestRunner runner ) + { + for( int i = 0; i < fromCmdLine.size(); i++ ) + { + runner.addFormatter( ( JUnitResultFormatter )fromCmdLine.elementAt( i ) ); + } + } + + /** + * Returns what System.exit() would return in the standalone version. + * + * @return 2 if errors occurred, 1 if tests failed else 0. + */ + public int getRetCode() + { + return retCode; + } + + /** + * Interface TestListener.

      + * + * An error occured while running the test. + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) + { + if( haltOnError ) + { + res.stop(); + } + } + + /** + * Interface TestListener for JUnit <= 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + if( haltOnFailure ) + { + res.stop(); + } + } + + /** + * Interface TestListener for JUnit > 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + public void addFormatter( JUnitResultFormatter f ) + { + formatters.addElement( f ); + } + + /** + * Interface TestListener.

      + * + * A Test is finished. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) { } + + public void run() + { + res = new TestResult(); + res.addListener( this ); + for( int i = 0; i < formatters.size(); i++ ) + { + res.addListener( ( TestListener )formatters.elementAt( i ) ); + } + + long start = System.currentTimeMillis(); + + fireStartTestSuite(); + if( exception != null ) + {// had an exception in the constructor + for( int i = 0; i < formatters.size(); i++ ) + { + ( ( TestListener )formatters.elementAt( i ) ).addError( null, + exception ); + } + junitTest.setCounts( 1, 0, 1 ); + junitTest.setRunTime( 0 ); + } + else + { + + ByteArrayOutputStream errStrm = new ByteArrayOutputStream(); + systemError = new PrintStream( errStrm ); + + ByteArrayOutputStream outStrm = new ByteArrayOutputStream(); + systemOut = new PrintStream( outStrm ); + + try + { + suite.run( res ); + } + finally + { + systemError.close(); + systemError = null; + systemOut.close(); + systemOut = null; + sendOutAndErr( new String( outStrm.toByteArray() ), + new String( errStrm.toByteArray() ) ); + + junitTest.setCounts( res.runCount(), res.failureCount(), + res.errorCount() ); + junitTest.setRunTime( System.currentTimeMillis() - start ); + } + } + fireEndTestSuite(); + + if( retCode != SUCCESS || res.errorCount() != 0 ) + { + retCode = ERRORS; + } + else if( res.failureCount() != 0 ) + { + retCode = FAILURES; + } + } + + /** + * Interface TestListener.

      + * + * A new Test is started. + * + * @param t Description of Parameter + */ + public void startTest( Test t ) { } + + protected void handleErrorOutput( String line ) + { + if( systemError != null ) + { + systemError.println( line ); + } + } + + protected void handleOutput( String line ) + { + if( systemOut != null ) + { + systemOut.println( line ); + } + } + + private void fireEndTestSuite() + { + for( int i = 0; i < formatters.size(); i++ ) + { + ( ( JUnitResultFormatter )formatters.elementAt( i ) ).endTestSuite( junitTest ); + } + } + + private void fireStartTestSuite() + { + for( int i = 0; i < formatters.size(); i++ ) + { + ( ( JUnitResultFormatter )formatters.elementAt( i ) ).startTestSuite( junitTest ); + } + } + + private void sendOutAndErr( String out, String err ) + { + for( int i = 0; i < formatters.size(); i++ ) + { + JUnitResultFormatter formatter = + ( ( JUnitResultFormatter )formatters.elementAt( i ) ); + + formatter.setSystemOutput( out ); + formatter.setSystemError( err ); + } + } + +}// JUnitTestRunner diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java new file mode 100644 index 000000000..082af4960 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.lang.reflect.Method; +import junit.framework.Test; +import junit.framework.TestCase; + +/** + * Work around for some changes to the public JUnit API between different JUnit + * releases. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class JUnitVersionHelper +{ + + private static Method testCaseName = null; + static + { + try + { + testCaseName = TestCase.class.getMethod( "getName", new Class[0] ); + } + catch( NoSuchMethodException e ) + { + // pre JUnit 3.7 + try + { + testCaseName = TestCase.class.getMethod( "name", new Class[0] ); + } + catch( NoSuchMethodException e2 ) + {} + } + } + + /** + * JUnit 3.7 introduces TestCase.getName() and subsequent versions of JUnit + * remove the old name() method. This method provides access to the name of + * a TestCase via reflection that is supposed to work with version before + * and after JUnit 3.7. + * + * @param t Description of Parameter + * @return The TestCaseName value + */ + public static String getTestCaseName( Test t ) + { + if( t instanceof TestCase && testCaseName != null ) + { + try + { + return ( String )testCaseName.invoke( t, new Object[0] ); + } + catch( Throwable e ) + {} + } + return "unknown"; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java new file mode 100644 index 000000000..239da0895 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.NumberFormat; +import java.util.Hashtable; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import org.apache.tools.ant.BuildException; + +/** + * Prints plain text output of the test to a specified Writer. + * + * @author Stefan Bodewig + */ + +public class PlainJUnitResultFormatter implements JUnitResultFormatter +{ + + /** + * Formatter for timings. + */ + private NumberFormat nf = NumberFormat.getInstance(); + /** + * Timing helper. + */ + private Hashtable testStarts = new Hashtable(); + /** + * Suppress endTest if testcase failed. + */ + private Hashtable failed = new Hashtable(); + + private String systemOutput = null; + private String systemError = null; + /** + * Helper to store intermediate output. + */ + private StringWriter inner; + /** + * Where to write the log to. + */ + private OutputStream out; + /** + * Convenience layer on top of {@link #inner inner}. + */ + private PrintWriter wri; + + public PlainJUnitResultFormatter() + { + inner = new StringWriter(); + wri = new PrintWriter( inner ); + } + + public void setOutput( OutputStream out ) + { + this.out = out; + } + + public void setSystemError( String err ) + { + systemError = err; + } + + public void setSystemOutput( String out ) + { + systemOutput = out; + } + + /** + * Interface TestListener.

      + * + * An error occured while running the test. + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) + { + formatError( "\tCaused an ERROR", test, t ); + } + + /** + * Interface TestListener for JUnit <= 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + formatError( "\tFAILED", test, t ); + } + + /** + * Interface TestListener for JUnit > 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * Interface TestListener.

      + * + * A Test is finished. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) + { + synchronized( wri ) + { + wri.print( "Testcase: " + + JUnitVersionHelper.getTestCaseName( test ) ); + if( Boolean.TRUE.equals( failed.get( test ) ) ) + { + return; + } + Long l = ( Long )testStarts.get( test ); + wri.println( " took " + + nf.format( ( System.currentTimeMillis() - l.longValue() ) + / 1000.0 ) + + " sec" ); + } + } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + String newLine = System.getProperty( "line.separator" ); + StringBuffer sb = new StringBuffer( "Testsuite: " ); + sb.append( suite.getName() ); + sb.append( newLine ); + sb.append( "Tests run: " ); + sb.append( suite.runCount() ); + sb.append( ", Failures: " ); + sb.append( suite.failureCount() ); + sb.append( ", Errors: " ); + sb.append( suite.errorCount() ); + sb.append( ", Time elapsed: " ); + sb.append( nf.format( suite.getRunTime() / 1000.0 ) ); + sb.append( " sec" ); + sb.append( newLine ); + + // append the err and output streams to the log + if( systemOutput != null && systemOutput.length() > 0 ) + { + sb.append( "------------- Standard Output ---------------" ) + .append( newLine ) + .append( systemOutput ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + if( systemError != null && systemError.length() > 0 ) + { + sb.append( "------------- Standard Error -----------------" ) + .append( newLine ) + .append( systemError ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + sb.append( newLine ); + + if( out != null ) + { + try + { + out.write( sb.toString().getBytes() ); + wri.close(); + out.write( inner.toString().getBytes() ); + out.flush(); + } + catch( IOException ioex ) + { + throw new BuildException( "Unable to write output", ioex ); + } + finally + { + if( out != System.out && out != System.err ) + { + try + { + out.close(); + } + catch( IOException e ) + {} + } + } + } + } + + /** + * Interface TestListener.

      + * + * A new Test is started. + * + * @param t Description of Parameter + */ + public void startTest( Test t ) + { + testStarts.put( t, new Long( System.currentTimeMillis() ) ); + failed.put( t, Boolean.FALSE ); + } + + /** + * Empty. + * + * @param suite Description of Parameter + */ + public void startTestSuite( JUnitTest suite ) { } + + private void formatError( String type, Test test, Throwable t ) + { + synchronized( wri ) + { + if( test != null ) + { + endTest( test ); + failed.put( test, Boolean.TRUE ); + } + + wri.println( type ); + wri.println( t.getMessage() ); + String strace = JUnitTestRunner.getFilteredTrace( t ); + wri.print( strace ); + wri.println( "" ); + } + } + +}// PlainJUnitResultFormatter diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java new file mode 100644 index 000000000..2932da2d0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.IOException; +import java.io.OutputStream; +import java.text.NumberFormat; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import org.apache.tools.ant.BuildException; + +/** + * Prints short summary output of the test to Ant's logging system. + * + * @author Stefan Bodewig + */ + +public class SummaryJUnitResultFormatter implements JUnitResultFormatter +{ + + /** + * Formatter for timings. + */ + private NumberFormat nf = NumberFormat.getInstance(); + + private boolean withOutAndErr = false; + private String systemOutput = null; + private String systemError = null; + /** + * OutputStream to write to. + */ + private OutputStream out; + + /** + * Empty + */ + public SummaryJUnitResultFormatter() { } + + public void setOutput( OutputStream out ) + { + this.out = out; + } + + public void setSystemError( String err ) + { + systemError = err; + } + + public void setSystemOutput( String out ) + { + systemOutput = out; + } + + /** + * Should the output to System.out and System.err be written to the summary. + * + * @param value The new WithOutAndErr value + */ + public void setWithOutAndErr( boolean value ) + { + withOutAndErr = value; + } + + /** + * Empty + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) { } + + /** + * Empty + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) { } + + /** + * Interface TestListener for JUnit > 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * Empty + * + * @param test Description of Parameter + */ + public void endTest( Test test ) { } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + String newLine = System.getProperty( "line.separator" ); + StringBuffer sb = new StringBuffer( "Tests run: " ); + sb.append( suite.runCount() ); + sb.append( ", Failures: " ); + sb.append( suite.failureCount() ); + sb.append( ", Errors: " ); + sb.append( suite.errorCount() ); + sb.append( ", Time elapsed: " ); + sb.append( nf.format( suite.getRunTime() / 1000.0 ) ); + sb.append( " sec" ); + sb.append( newLine ); + + if( withOutAndErr ) + { + if( systemOutput != null && systemOutput.length() > 0 ) + { + sb.append( "Output:" ).append( newLine ).append( systemOutput ) + .append( newLine ); + } + + if( systemError != null && systemError.length() > 0 ) + { + sb.append( "Error: " ).append( newLine ).append( systemError ) + .append( newLine ); + } + } + + try + { + out.write( sb.toString().getBytes() ); + out.flush(); + } + catch( IOException ioex ) + { + throw new BuildException( "Unable to write summary output", ioex ); + } + finally + { + if( out != System.out && out != System.err ) + { + try + { + out.close(); + } + catch( IOException e ) + {} + } + } + } + + /** + * Empty + * + * @param t Description of Parameter + */ + public void startTest( Test t ) { } + + /** + * Empty + * + * @param suite Description of Parameter + */ + public void startTestSuite( JUnitTest suite ) { } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java new file mode 100644 index 000000000..53a2d1bf5 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; + +/** + *

      + * + * Interface groups XML constants. Interface that groups all constants used + * throughout the XML documents that are generated by the + * XMLJUnitResultFormatter As of now the DTD is:

      + * <-----------------
      + *
      + * @author Stephane Bailliez
      + * @see XMLJUnitResultFormatter
      + * @see XMLResultAggregator
      + * @todo describe DTDs ----------------------> 
      + */ +public interface XMLConstants +{ + /** + * the testsuites element for the aggregate document + */ + String TESTSUITES = "testsuites"; + + /** + * the testsuite element + */ + String TESTSUITE = "testsuite"; + + /** + * the testcase element + */ + String TESTCASE = "testcase"; + + /** + * the error element + */ + String ERROR = "error"; + + /** + * the failure element + */ + String FAILURE = "failure"; + + /** + * the system-err element + */ + String SYSTEM_ERR = "system-err"; + + /** + * the system-out element + */ + String SYSTEM_OUT = "system-out"; + + /** + * package attribute for the aggregate document + */ + String ATTR_PACKAGE = "package"; + + /** + * name attribute for property, testcase and testsuite elements + */ + String ATTR_NAME = "name"; + + /** + * time attribute for testcase and testsuite elements + */ + String ATTR_TIME = "time"; + + /** + * errors attribute for testsuite elements + */ + String ATTR_ERRORS = "errors"; + + /** + * failures attribute for testsuite elements + */ + String ATTR_FAILURES = "failures"; + + /** + * tests attribute for testsuite elements + */ + String ATTR_TESTS = "tests"; + + /** + * type attribute for failure and error elements + */ + String ATTR_TYPE = "type"; + + /** + * message attribute for failure elements + */ + String ATTR_MESSAGE = "message"; + + /** + * the properties element + */ + String PROPERTIES = "properties"; + + /** + * the property element + */ + String PROPERTY = "property"; + + /** + * value attribute for property elements + */ + String ATTR_VALUE = "value"; + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java new file mode 100644 index 000000000..97501dc2c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.DOMElementWriter; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; + +/** + * Prints XML output of the test to a specified Writer. + * + * @author Stefan Bodewig + * @author Erik Hatcher + * @see FormatterElement + */ + +public class XMLJUnitResultFormatter implements JUnitResultFormatter, XMLConstants +{ + /** + * Element for the current test. + */ + private Hashtable testElements = new Hashtable(); + /** + * Timing helper. + */ + private Hashtable testStarts = new Hashtable(); + + /** + * The XML document. + */ + private Document doc; + /** + * Where to write the log to. + */ + private OutputStream out; + /** + * The wrapper for the whole testsuite. + */ + private Element rootElement; + + public XMLJUnitResultFormatter() { } + + private static DocumentBuilder getDocumentBuilder() + { + try + { + return DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } + catch( Exception exc ) + { + throw new ExceptionInInitializerError( exc ); + } + } + + public void setOutput( OutputStream out ) + { + this.out = out; + } + + public void setSystemError( String out ) + { + formatOutput( SYSTEM_ERR, out ); + } + + public void setSystemOutput( String out ) + { + formatOutput( SYSTEM_OUT, out ); + } + + /** + * Interface TestListener.

      + * + * An error occured while running the test. + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) + { + formatError( ERROR, test, t ); + } + + /** + * Interface TestListener for JUnit <= 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + formatError( FAILURE, test, t ); + } + + /** + * Interface TestListener for JUnit > 3.4.

      + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * Interface TestListener.

      + * + * A Test is finished. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) + { + Element currentTest = ( Element )testElements.get( test ); + Long l = ( Long )testStarts.get( test ); + currentTest.setAttribute( ATTR_TIME, + "" + ( ( System.currentTimeMillis() - l.longValue() ) + / 1000.0 ) ); + } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + rootElement.setAttribute( ATTR_TESTS, "" + suite.runCount() ); + rootElement.setAttribute( ATTR_FAILURES, "" + suite.failureCount() ); + rootElement.setAttribute( ATTR_ERRORS, "" + suite.errorCount() ); + rootElement.setAttribute( ATTR_TIME, "" + ( suite.getRunTime() / 1000.0 ) ); + if( out != null ) + { + Writer wri = null; + try + { + wri = new OutputStreamWriter( out, "UTF8" ); + wri.write( "\n" ); + ( new DOMElementWriter() ).write( rootElement, wri, 0, " " ); + wri.flush(); + } + catch( IOException exc ) + { + throw new BuildException( "Unable to write log file", exc ); + } + finally + { + if( out != System.out && out != System.err ) + { + if( wri != null ) + { + try + { + wri.close(); + } + catch( IOException e ) + {} + } + } + } + } + } + + /** + * Interface TestListener.

      + * + * A new Test is started. + * + * @param t Description of Parameter + */ + public void startTest( Test t ) + { + testStarts.put( t, new Long( System.currentTimeMillis() ) ); + + Element currentTest = doc.createElement( TESTCASE ); + currentTest.setAttribute( ATTR_NAME, + JUnitVersionHelper.getTestCaseName( t ) ); + rootElement.appendChild( currentTest ); + testElements.put( t, currentTest ); + } + + /** + * The whole testsuite started. + * + * @param suite Description of Parameter + */ + public void startTestSuite( JUnitTest suite ) + { + doc = getDocumentBuilder().newDocument(); + rootElement = doc.createElement( TESTSUITE ); + rootElement.setAttribute( ATTR_NAME, suite.getName() ); + + // Output properties + Element propsElement = doc.createElement( PROPERTIES ); + rootElement.appendChild( propsElement ); + Properties props = suite.getProperties(); + if( props != null ) + { + Enumeration e = props.propertyNames(); + while( e.hasMoreElements() ) + { + String name = ( String )e.nextElement(); + Element propElement = doc.createElement( PROPERTY ); + propElement.setAttribute( ATTR_NAME, name ); + propElement.setAttribute( ATTR_VALUE, props.getProperty( name ) ); + propsElement.appendChild( propElement ); + } + } + } + + private void formatError( String type, Test test, Throwable t ) + { + if( test != null ) + { + endTest( test ); + } + + Element nested = doc.createElement( type ); + Element currentTest = null; + if( test != null ) + { + currentTest = ( Element )testElements.get( test ); + } + else + { + currentTest = rootElement; + } + + currentTest.appendChild( nested ); + + String message = t.getMessage(); + if( message != null && message.length() > 0 ) + { + nested.setAttribute( ATTR_MESSAGE, t.getMessage() ); + } + nested.setAttribute( ATTR_TYPE, t.getClass().getName() ); + + String strace = JUnitTestRunner.getFilteredTrace( t ); + Text trace = doc.createTextNode( strace ); + nested.appendChild( trace ); + } + + private void formatOutput( String type, String output ) + { + Element nested = doc.createElement( type ); + rootElement.appendChild( nested ); + Text content = doc.createTextNode( output ); + nested.appendChild( content ); + } + +}// XMLJUnitResultFormatter diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java new file mode 100644 index 000000000..cc72e239f --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Enumeration; +import java.util.Vector; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.DOMElementWriter; +import org.apache.tools.ant.util.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + + +/** + *

      + * + * This is an helper class that will aggregate all testsuites under a specific + * directory and create a new single document. It is not particulary clean but + * should be helpful while I am thinking about another technique.

      + * + * The main problem is due to the fact that a JVM can be forked for a testcase + * thus making it impossible to aggregate all testcases since the listener is + * (obviously) in the forked JVM. A solution could be to write a TestListener + * that will receive events from the TestRunner via sockets. This is IMHO the + * simplest way to do it to avoid this file hacking thing. + * + * @author Stephane Bailliez + */ +public class XMLResultAggregator extends Task implements XMLConstants +{ + + /** + * the default directory: . . It is resolved from the project + * directory + */ + public final static String DEFAULT_DIR = "."; + + /** + * the default file name: TESTS-TestSuites.xml + */ + public final static String DEFAULT_FILENAME = "TESTS-TestSuites.xml"; + + /** + * the list of all filesets, that should contains the xml to aggregate + */ + protected Vector filesets = new Vector(); + + protected Vector transformers = new Vector(); + + /** + * the directory to write the file to + */ + protected File toDir; + + /** + * the name of the result file + */ + protected String toFile; + + /** + * Create a new document builder. Will issue an + * ExceptionInitializerError if something is going wrong. It is fatal + * anyway. + * + * @return a new document builder to create a DOM + * @todo factorize this somewhere else. It is duplicated code. + */ + private static DocumentBuilder getDocumentBuilder() + { + try + { + return DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } + catch( Exception exc ) + { + throw new ExceptionInInitializerError( exc ); + } + } + + /** + * Set the destination directory where the results should be written. If not + * set if will use {@link #DEFAULT_DIR}. When given a relative directory it + * will resolve it from the project directory. + * + * @param value the directory where to write the results, absolute or + * relative. + */ + public void setTodir( File value ) + { + toDir = value; + } + + /** + * Set the name of the file aggregating the results. It must be relative + * from the todir attribute. If not set it will use {@link + * #DEFAULT_FILENAME} + * + * @param value the name of the file. + * @see #setTodir(File) + */ + public void setTofile( String value ) + { + toFile = value; + } + + /** + * Add a new fileset containing the xml results to aggregate + * + * @param fs the new fileset of xml results. + */ + public void addFileSet( FileSet fs ) + { + filesets.addElement( fs ); + } + + + public AggregateTransformer createReport() + { + AggregateTransformer transformer = new AggregateTransformer( this ); + transformers.addElement( transformer ); + return transformer; + } + + /** + * Aggregate all testsuites into a single document and write it to the + * specified directory and file. + * + * @throws BuildException thrown if there is a serious error while writing + * the document. + */ + public void execute() + throws BuildException + { + Element rootElement = createDocument(); + File destFile = getDestinationFile(); + // write the document + try + { + writeDOMTree( rootElement.getOwnerDocument(), destFile ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to write test aggregate to '" + destFile + "'", e ); + } + // apply transformation + Enumeration enum = transformers.elements(); + while( enum.hasMoreElements() ) + { + AggregateTransformer transformer = + ( AggregateTransformer )enum.nextElement(); + transformer.setXmlDocument( rootElement.getOwnerDocument() ); + transformer.transform(); + } + } + + /** + * Get the full destination file where to write the result. It is made of + * the todir and tofile attributes. + * + * @return the destination file where should be written the result file. + */ + protected File getDestinationFile() + { + if( toFile == null ) + { + toFile = DEFAULT_FILENAME; + } + if( toDir == null ) + { + toDir = project.resolveFile( DEFAULT_DIR ); + } + return new File( toDir, toFile ); + } + + /** + * Get all .xml files in the fileset. + * + * @return all files in the fileset that end with a '.xml'. + */ + protected File[] getFiles() + { + Vector v = new Vector(); + final int size = filesets.size(); + for( int i = 0; i < size; i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + ds.scan(); + String[] f = ds.getIncludedFiles(); + for( int j = 0; j < f.length; j++ ) + { + String pathname = f[j]; + if( pathname.endsWith( ".xml" ) ) + { + File file = new File( ds.getBasedir(), pathname ); + file = project.resolveFile( file.getPath() ); + v.addElement( file ); + } + } + } + + File[] files = new File[v.size()]; + v.copyInto( files ); + return files; + } + + /** + *

      + * + * Add a new testsuite node to the document. The main difference is that it + * split the previous fully qualified name into a package and a name.

      + * + * For example: org.apache.Whatever will be split into + * org.apache and Whatever . + * + * @param root the root element to which the testsuite node should + * be appended. + * @param testsuite the element to append to the given root. It will + * slightly modify the original node to change the name attribute and + * add a package one. + */ + protected void addTestSuite( Element root, Element testsuite ) + { + String fullclassname = testsuite.getAttribute( ATTR_NAME ); + int pos = fullclassname.lastIndexOf( '.' ); + + // a missing . might imply no package at all. Don't get fooled. + String pkgName = ( pos == -1 ) ? "" : fullclassname.substring( 0, pos ); + String classname = ( pos == -1 ) ? fullclassname : fullclassname.substring( pos + 1 ); + Element copy = ( Element )DOMUtil.importNode( root, testsuite ); + + // modify the name attribute and set the package + copy.setAttribute( ATTR_NAME, classname ); + copy.setAttribute( ATTR_PACKAGE, pkgName ); + } + + /** + *

      + * + * Create a DOM tree. Has 'testsuites' as firstchild and aggregates all + * testsuite results that exists in the base directory. + * + * @return the root element of DOM tree that aggregates all testsuites. + */ + protected Element createDocument() + { + // create the dom tree + DocumentBuilder builder = getDocumentBuilder(); + Document doc = builder.newDocument(); + Element rootElement = doc.createElement( TESTSUITES ); + doc.appendChild( rootElement ); + + // get all files and add them to the document + File[] files = getFiles(); + for( int i = 0; i < files.length; i++ ) + { + try + { + log( "Parsing file: '" + files[i] + "'", Project.MSG_VERBOSE ); + //XXX there seems to be a bug in xerces 1.3.0 that doesn't like file object + // will investigate later. It does not use the given directory but + // the vm dir instead ? Works fine with crimson. + Document testsuiteDoc = builder.parse( "file:///" + files[i].getAbsolutePath() ); + Element elem = testsuiteDoc.getDocumentElement(); + // make sure that this is REALLY a testsuite. + if( TESTSUITE.equals( elem.getNodeName() ) ) + { + addTestSuite( rootElement, elem ); + } + else + { + // issue a warning. + log( "the file " + files[i] + " is not a valid testsuite XML document", Project.MSG_WARN ); + } + } + catch( SAXException e ) + { + // a testcase might have failed and write a zero-length document, + // It has already failed, but hey.... mm. just put a warning + log( "The file " + files[i] + " is not a valid XML document. It is possibly corrupted.", Project.MSG_WARN ); + log( StringUtils.getStackTrace( e ), Project.MSG_DEBUG ); + } + catch( IOException e ) + { + log( "Error while accessing file " + files[i] + ": " + e.getMessage(), Project.MSG_ERR ); + } + } + return rootElement; + } + + //----- from now, the methods are all related to DOM tree manipulation + + /** + * Write the DOM tree to a file. + * + * @param doc the XML document to dump to disk. + * @param file the filename to write the document to. Should obviouslly be a + * .xml file. + * @throws IOException thrown if there is an error while writing the + * content. + */ + protected void writeDOMTree( Document doc, File file ) + throws IOException + { + OutputStream out = new FileOutputStream( file ); + PrintWriter wri = new PrintWriter( new OutputStreamWriter( out, "UTF8" ) ); + wri.write( "\n" ); + ( new DOMElementWriter() ).write( doc.getDocumentElement(), wri, 0, " " ); + wri.flush(); + wri.close(); + // writers do not throw exceptions, so check for them. + if( wri.checkError() ) + { + throw new IOException( "Error while writing DOM content" ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java new file mode 100644 index 000000000..5f426d55e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.OutputStream; +import org.apache.xalan.xslt.XSLTInputSource; +import org.apache.xalan.xslt.XSLTProcessor; +import org.apache.xalan.xslt.XSLTProcessorFactory; +import org.apache.xalan.xslt.XSLTResultTarget; + +/** + * Xalan 1 executor. It will need a lot of things in the classpath: xerces for + * the serialization, xalan and bsf for the extension. + * + * @author RT + * @todo do everything via reflection to avoid compile problems ? + */ +public class Xalan1Executor extends XalanExecutor +{ + void execute() + throws Exception + { + XSLTProcessor processor = XSLTProcessorFactory.getProcessor(); + // need to quote otherwise it breaks because of "extra illegal tokens" + processor.setStylesheetParam( "output.dir", "'" + caller.toDir.getAbsolutePath() + "'" ); + XSLTInputSource xml_src = new XSLTInputSource( caller.document ); + String system_id = caller.getStylesheetSystemId(); + XSLTInputSource xsl_src = new XSLTInputSource( system_id ); + OutputStream os = getOutputStream(); + XSLTResultTarget target = new XSLTResultTarget( os ); + processor.process( xml_src, xsl_src, target ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java new file mode 100644 index 000000000..617424290 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.OutputStream; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +/** + * Xalan executor via JAXP. Nothing special must exists in the classpath besides + * of course, a parser, jaxp and xalan. + * + * @author RT + */ +public class Xalan2Executor extends XalanExecutor +{ + void execute() + throws Exception + { + TransformerFactory tfactory = TransformerFactory.newInstance(); + String system_id = caller.getStylesheetSystemId(); + Source xsl_src = new StreamSource( system_id ); + Transformer tformer = tfactory.newTransformer( xsl_src ); + Source xml_src = new DOMSource( caller.document ); + OutputStream os = getOutputStream(); + tformer.setParameter( "output.dir", caller.toDir.getAbsolutePath() ); + Result result = new StreamResult( os ); + tformer.transform( xml_src, result ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java new file mode 100644 index 000000000..0b95c3426 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import org.apache.tools.ant.BuildException; + +/** + * Command class that encapsulate specific behavior for each Xalan version. The + * right executor will be instantiated at runtime via class lookup. For + * instance, it will check first for Xalan2, then for Xalan1. + * + * @author RT + */ +abstract class XalanExecutor +{ + /** + * the transformer caller + */ + protected AggregateTransformer caller; + + /** + * Create a valid Xalan executor. It checks first if Xalan2 is present, if + * not it checks for xalan1. If none is available, it fails. + * + * @param caller object containing the transformation information. + * @return Description of the Returned Value + * @throws BuildException thrown if it could not find a valid xalan + * executor. + */ + static XalanExecutor newInstance( AggregateTransformer caller ) + throws BuildException + { + Class procVersion = null; + XalanExecutor executor = null; + try + { + procVersion = Class.forName( "org.apache.xalan.processor.XSLProcessorVersion" ); + executor = new Xalan2Executor(); + } + catch( Exception xalan2missing ) + { + try + { + procVersion = Class.forName( "org.apache.xalan.xslt.XSLProcessorVersion" ); + executor = ( XalanExecutor )Class.forName( + "org.apache.tools.ant.taskdefs.optional.junit.Xalan1Executor" ).newInstance(); + } + catch( Exception xalan1missing ) + { + throw new BuildException( "Could not find xalan2 nor xalan1 in the classpath. Check http://xml.apache.org/xalan-j" ); + } + } + String version = getXalanVersion( procVersion ); + caller.task.log( "Using Xalan version: " + version ); + executor.setCaller( caller ); + return executor; + } + + /** + * pretty useful data (Xalan version information) to display. + * + * @param procVersion Description of Parameter + * @return The XalanVersion value + */ + private static String getXalanVersion( Class procVersion ) + { + try + { + Field f = procVersion.getField( "S_VERSION" ); + return f.get( null ).toString(); + } + catch( Exception e ) + { + return "?"; + } + } + + /** + * get the appropriate stream based on the format (frames/noframes) + * + * @return The OutputStream value + * @exception IOException Description of Exception + */ + protected OutputStream getOutputStream() + throws IOException + { + if( caller.FRAMES.equals( caller.format ) ) + { + // dummy output for the framed report + // it's all done by extension... + return new ByteArrayOutputStream(); + } + else + { + return new FileOutputStream( new File( caller.toDir, "junit-noframes.html" ) ); + } + } + + /** + * override to perform transformation + * + * @exception Exception Description of Exception + */ + abstract void execute() + throws Exception; + + /** + * set the caller for this object. + * + * @param caller The new Caller value + */ + private final void setCaller( AggregateTransformer caller ) + { + this.caller = caller; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java new file mode 100644 index 000000000..500cddfd7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.metamata; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Random; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; + +/** + * Somewhat abstract framework to be used for other metama 2.0 tasks. This + * should include, audit, metrics, cover and mparse. For more information, visit + * the website at www.metamata.com + * + * @author Stephane Bailliez + */ +public abstract class AbstractMetamataTask extends Task +{ + + //--------------------------- ATTRIBUTES ----------------------------------- + + /** + * The user classpath to be provided. It matches the -classpath of the + * command line. The classpath must includes both the .class and + * the .java files for accurate audit. + */ + protected Path classPath = null; + + /** + * the path to the source file + */ + protected Path sourcePath = null; + + /** + * Metamata home directory. It will be passed as a metamata.home + * property and should normally matches the environment property + * META_HOME set by the Metamata installer. + */ + protected File metamataHome = null; + + /** + * the command line used to run MAudit + */ + protected CommandlineJava cmdl = new CommandlineJava(); + + /** + * the set of files to be audited + */ + protected Vector fileSets = new Vector(); + + /** + * the options file where are stored the command line options + */ + protected File optionsFile = null; + + // this is used to keep track of which files were included. It will + // be set when calling scanFileSets(); + protected Hashtable includedFiles = null; + + public AbstractMetamataTask() { } + + /** + * initialize the task with the classname of the task to run + * + * @param className Description of Parameter + */ + protected AbstractMetamataTask( String className ) + { + cmdl.setVm( "java" ); + cmdl.setClassname( className ); + } + + /** + * convenient method for JDK 1.1. Will copy all elements from src to dest + * + * @param dest The feature to be added to the AllVector attribute + * @param files The feature to be added to the AllVector attribute + */ + protected final static void addAllVector( Vector dest, Enumeration files ) + { + while( files.hasMoreElements() ) + { + dest.addElement( files.nextElement() ); + } + } + + protected final static File createTmpFile() + { + // must be compatible with JDK 1.1 !!!! + final long rand = ( new Random( System.currentTimeMillis() ) ).nextLong(); + File file = new File( "metamata" + rand + ".tmp" ); + return file; + } + + /** + * -mx or -Xmx depending on VM version + * + * @param max The new Maxmemory value + */ + public void setMaxmemory( String max ) + { + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + createJvmarg().setValue( "-mx" + max ); + } + else + { + createJvmarg().setValue( "-Xmx" + max ); + } + } + + /** + * the metamata.home property to run all tasks. + * + * @param metamataHome The new Metamatahome value + */ + public void setMetamatahome( final File metamataHome ) + { + this.metamataHome = metamataHome; + } + + + /** + * The java files or directory to be audited + * + * @param fs The feature to be added to the FileSet attribute + */ + public void addFileSet( FileSet fs ) + { + fileSets.addElement( fs ); + } + + /** + * user classpath + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classPath == null ) + { + classPath = new Path( project ); + } + return classPath; + } + + /** + * Creates a nested jvmarg element. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createJvmarg() + { + return cmdl.createVmArgument(); + } + + /** + * create the source path for this task + * + * @return Description of the Returned Value + */ + public Path createSourcepath() + { + if( sourcePath == null ) + { + sourcePath = new Path( project ); + } + return sourcePath; + } + + /** + * execute the command line + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + try + { + setUp(); + ExecuteStreamHandler handler = createStreamHandler(); + execute0( handler ); + } + finally + { + cleanUp(); + } + } + + //--------------------- PRIVATE/PROTECTED METHODS -------------------------- + + /** + * check the options and build the command line + * + * @exception BuildException Description of Exception + */ + protected void setUp() + throws BuildException + { + checkOptions(); + + // set the classpath as the jar file + File jar = getMetamataJar( metamataHome ); + final Path classPath = cmdl.createClasspath( project ); + classPath.createPathElement().setLocation( jar ); + + // set the metamata.home property + final Commandline.Argument vmArgs = cmdl.createVmArgument(); + vmArgs.setValue( "-Dmetamata.home=" + metamataHome.getAbsolutePath() ); + + // retrieve all the files we want to scan + includedFiles = scanFileSets(); + log( includedFiles.size() + " files added for audit", Project.MSG_VERBOSE ); + + // write all the options to a temp file and use it ro run the process + Vector options = getOptions(); + optionsFile = createTmpFile(); + generateOptionsFile( optionsFile, options ); + Commandline.Argument args = cmdl.createArgument(); + args.setLine( "-arguments " + optionsFile.getAbsolutePath() ); + } + + /** + * return the location of the jar file used to run + * + * @param home Description of Parameter + * @return The MetamataJar value + */ + protected final File getMetamataJar( File home ) + { + return new File( new File( home.getAbsolutePath() ), "lib/metamata.jar" ); + } + + + protected Hashtable getFileMapping() + { + return includedFiles; + } + + /** + * return all options of the command line as string elements + * + * @return The Options value + */ + protected abstract Vector getOptions(); + + /** + * validate options set + * + * @exception BuildException Description of Exception + */ + protected void checkOptions() + throws BuildException + { + // do some validation first + if( metamataHome == null || !metamataHome.exists() ) + { + throw new BuildException( "'metamatahome' must point to Metamata home directory." ); + } + metamataHome = project.resolveFile( metamataHome.getPath() ); + File jar = getMetamataJar( metamataHome ); + if( !jar.exists() ) + { + throw new BuildException( jar + " does not exist. Check your metamata installation." ); + } + } + + /** + * clean up all the mess that we did with temporary objects + */ + protected void cleanUp() + { + if( optionsFile != null ) + { + optionsFile.delete(); + optionsFile = null; + } + } + + /** + * create a stream handler that will be used to get the output since + * metamata tools do not report with convenient files such as XML. + * + * @return Description of the Returned Value + */ + protected abstract ExecuteStreamHandler createStreamHandler(); + + + /** + * execute the process with a specific handler + * + * @param handler Description of Parameter + * @exception BuildException Description of Exception + */ + protected void execute0( ExecuteStreamHandler handler ) + throws BuildException + { + final Execute process = new Execute( handler ); + log( cmdl.toString(), Project.MSG_VERBOSE ); + process.setCommandline( cmdl.getCommandline() ); + try + { + if( process.execute() != 0 ) + { + throw new BuildException( "Metamata task failed." ); + } + } + catch( IOException e ) + { + throw new BuildException( "Failed to launch Metamata task: " + e ); + } + } + + + protected void generateOptionsFile( File tofile, Vector options ) + throws BuildException + { + FileWriter fw = null; + try + { + fw = new FileWriter( tofile ); + PrintWriter pw = new PrintWriter( fw ); + final int size = options.size(); + for( int i = 0; i < size; i++ ) + { + pw.println( options.elementAt( i ) ); + } + pw.flush(); + } + catch( IOException e ) + { + throw new BuildException( "Error while writing options file " + tofile, e ); + } + finally + { + if( fw != null ) + { + try + { + fw.close(); + } + catch( IOException ignored ) + {} + } + } + } + + /** + * @return the list of .java files (as their absolute path) that should be + * audited. + */ + protected Hashtable scanFileSets() + { + Hashtable files = new Hashtable(); + for( int i = 0; i < fileSets.size(); i++ ) + { + FileSet fs = ( FileSet )fileSets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + ds.scan(); + String[] f = ds.getIncludedFiles(); + log( i + ") Adding " + f.length + " files from directory " + ds.getBasedir(), Project.MSG_VERBOSE ); + for( int j = 0; j < f.length; j++ ) + { + String pathname = f[j]; + if( pathname.endsWith( ".java" ) ) + { + File file = new File( ds.getBasedir(), pathname ); +// file = project.resolveFile(file.getAbsolutePath()); + String classname = pathname.substring( 0, pathname.length() - ".java".length() ); + classname = classname.replace( File.separatorChar, '.' ); + files.put( file.getAbsolutePath(), classname );// it's a java file, add it. + } + } + } + return files; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java new file mode 100644 index 000000000..80c5415bb --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.metamata; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Path; + +/** + * Metamata Audit evaluates Java code for programming errors, weaknesses, and + * style violation.

      + * + * Metamata Audit exists in three versions: + *

        + *
      • The Lite version evaluates about 15 built-in rules.
      • + *
      • The Pro version evaluates about 50 built-in rules.
      • + *
      • The Enterprise version allows you to add your own customized rules via + * the API.
      • + *
          For more information, visit the website at www.metamata.com + * + * @author Stephane Bailliez + */ +public class MAudit extends AbstractMetamataTask +{ + + /* + * As of Metamata 2.0, the command line of MAudit is as follows: + * Usage + * maudit + */ +public class DOMElementWriter +{ + + private static String lSep = System.getProperty( "line.separator" ); + private StringBuffer sb = new StringBuffer(); + + /** + * Don't try to be too smart but at least recognize the predefined entities. + */ + protected String[] knownEntities = {"gt", "amp", "lt", "apos", "quot"}; + + /** + * Is the given argument a character or entity reference? + * + * @param ent Description of Parameter + * @return The Reference value + */ + public boolean isReference( String ent ) + { + if( !( ent.charAt( 0 ) == '&' ) || !ent.endsWith( ";" ) ) + { + return false; + } + + if( ent.charAt( 1 ) == '#' ) + { + if( ent.charAt( 2 ) == 'x' ) + { + try + { + Integer.parseInt( ent.substring( 3, ent.length() - 1 ), 16 ); + return true; + } + catch( NumberFormatException nfe ) + { + return false; + } + } + else + { + try + { + Integer.parseInt( ent.substring( 2, ent.length() - 1 ) ); + return true; + } + catch( NumberFormatException nfe ) + { + return false; + } + } + } + + String name = ent.substring( 1, ent.length() - 1 ); + for( int i = 0; i < knownEntities.length; i++ ) + { + if( name.equals( knownEntities[i] ) ) + { + return true; + } + } + return false; + } + + /** + * Escape <, > & ' and " as their entities. + * + * @param value Description of Parameter + * @return Description of the Returned Value + */ + public String encode( String value ) + { + sb.setLength( 0 ); + for( int i = 0; i < value.length(); i++ ) + { + char c = value.charAt( i ); + switch ( c ) + { + case '<': + sb.append( "<" ); + break; + case '>': + sb.append( ">" ); + break; + case '\'': + sb.append( "'" ); + break; + case '\"': + sb.append( """ ); + break; + case '&': + int nextSemi = value.indexOf( ";", i ); + if( nextSemi < 0 + || !isReference( value.substring( i, nextSemi + 1 ) ) ) + { + sb.append( "&" ); + } + else + { + sb.append( '&' ); + } + break; + default: + sb.append( c ); + break; + } + } + return sb.toString(); + } + + /** + * Writes a DOM tree to a stream. + * + * @param element the Root DOM element of the tree + * @param out where to send the output + * @param indent number of + * @param indentWith strings, that should be used to indent the + * corresponding tag. + * @exception IOException Description of Exception + */ + public void write( Element element, Writer out, int indent, + String indentWith ) + throws IOException + { + + // Write indent characters + for( int i = 0; i < indent; i++ ) + { + out.write( indentWith ); + } + + // Write element + out.write( "<" ); + out.write( element.getTagName() ); + + // Write attributes + NamedNodeMap attrs = element.getAttributes(); + for( int i = 0; i < attrs.getLength(); i++ ) + { + Attr attr = ( Attr )attrs.item( i ); + out.write( " " ); + out.write( attr.getName() ); + out.write( "=\"" ); + out.write( encode( attr.getValue() ) ); + out.write( "\"" ); + } + out.write( ">" ); + + // Write child elements and text + boolean hasChildren = false; + NodeList children = element.getChildNodes(); + for( int i = 0; i < children.getLength(); i++ ) + { + Node child = children.item( i ); + + switch ( child.getNodeType() ) + { + + case Node.ELEMENT_NODE: + if( !hasChildren ) + { + out.write( lSep ); + hasChildren = true; + } + write( ( Element )child, out, indent + 1, indentWith ); + break; + case Node.TEXT_NODE: + out.write( encode( child.getNodeValue() ) ); + break; + case Node.CDATA_SECTION_NODE: + out.write( "" ); + break; + case Node.ENTITY_REFERENCE_NODE: + out.write( '&' ); + out.write( child.getNodeName() ); + out.write( ';' ); + break; + case Node.PROCESSING_INSTRUCTION_NODE: + out.write( " 0 ) + { + out.write( ' ' ); + out.write( data ); + } + out.write( "?>" ); + break; + } + } + + // If we had child elements, we need to indent before we close + // the element, otherwise we're on the same line and don't need + // to indent + if( hasChildren ) + { + for( int i = 0; i < indent; i++ ) + { + out.write( indentWith ); + } + } + + // Write element close + out.write( "" ); + out.write( lSep ); + out.flush(); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/FileNameMapper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/FileNameMapper.java new file mode 100644 index 000000000..33ae65096 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/FileNameMapper.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Interface to be used by SourceFileScanner.

          + * + * Used to find the name of the target file(s) corresponding to a source file. + *

          + * + * The rule by which the file names are transformed is specified via the setFrom + * and setTo methods. The exact meaning of these is implementation dependent. + *

          + * + * @author
          Stefan Bodewig + */ +public interface FileNameMapper +{ + + /** + * Sets the from part of the transformation rule. + * + * @param from The new From value + */ + void setFrom( String from ); + + /** + * Sets the to part of the transformation rule. + * + * @param to The new To value + */ + void setTo( String to ); + + /** + * Returns an array containing the target filename(s) for the given source + * file.

          + * + * if the given rule doesn't apply to the source file, implementation must + * return null. SourceFileScanner will then omit the source file in + * question.

          + * + * @param sourceFileName the name of the source file relative to some given + * basedirectory. + * @return Description of the Returned Value + */ + String[] mapFileName( String sourceFileName ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/FileUtils.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/FileUtils.java new file mode 100644 index 000000000..a27ab6f63 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/FileUtils.java @@ -0,0 +1,674 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.text.DecimalFormat; +import java.util.Random; +import java.util.Stack; +import java.util.StringTokenizer; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FilterSetCollection; + +/** + * This class also encapsulates methods which allow Files to be refered to using + * abstract path names which are translated to native system file paths at + * runtime as well as copying files or setting there last modification time. + * + * @author duncan@x180.com + * @author Conor MacNeill + * @author Stefan Bodewig + * @version $Revision$ + */ + +public class FileUtils +{ + private static Random rand = new Random( System.currentTimeMillis() ); + private static Object lockReflection = new Object(); + private static java.lang.reflect.Method setLastModified = null; + + /** + * Empty constructor. + */ + protected FileUtils() { } + + /** + * Factory method. + * + * @return Description of the Returned Value + */ + public static FileUtils newFileUtils() + { + return new FileUtils(); + } + + /** + * Calls File.setLastModified(long time) in a Java 1.1 compatible way. + * + * @param file The new FileLastModified value + * @param time The new FileLastModified value + * @exception BuildException Description of Exception + */ + public void setFileLastModified( File file, long time ) + throws BuildException + { + if( Project.getJavaVersion() == Project.JAVA_1_1 ) + { + return; + } + Long[] times = new Long[1]; + if( time < 0 ) + { + times[0] = new Long( System.currentTimeMillis() ); + } + else + { + times[0] = new Long( time ); + } + + try + { + getSetLastModified().invoke( file, times ); + } + catch( java.lang.reflect.InvocationTargetException ite ) + { + Throwable nested = ite.getTargetException(); + throw new BuildException( "Exception setting the modification time " + + "of " + file, nested ); + } + catch( Throwable other ) + { + throw new BuildException( "Exception setting the modification time " + + "of " + file, other ); + } + } + + /** + * Emulation of File.getParentFile for JDK 1.1 + * + * @param f Description of Parameter + * @return The ParentFile value + * @since 1.10 + */ + public File getParentFile( File f ) + { + if( f != null ) + { + String p = f.getParent(); + if( p != null ) + { + return new File( p ); + } + } + return null; + } + + /** + * Compares the contents of two files. + * + * @param f1 Description of Parameter + * @param f2 Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + * @since 1.9 + */ + public boolean contentEquals( File f1, File f2 ) + throws IOException + { + if( f1.exists() != f2.exists() ) + { + return false; + } + + if( !f1.exists() ) + { + // two not existing files are equal + return true; + } + + if( f1.isDirectory() || f2.isDirectory() ) + { + // don't want to compare directory contents for now + return false; + } + + InputStream in1 = null; + InputStream in2 = null; + try + { + in1 = new BufferedInputStream( new FileInputStream( f1 ) ); + in2 = new BufferedInputStream( new FileInputStream( f2 ) ); + + int expectedByte = in1.read(); + while( expectedByte != -1 ) + { + if( expectedByte != in2.read() ) + { + return false; + } + expectedByte = in1.read(); + } + if( in2.read() != -1 ) + { + return false; + } + return true; + } + finally + { + if( in1 != null ) + { + try + { + in1.close(); + } + catch( IOException e ) + {} + } + if( in2 != null ) + { + try + { + in2.close(); + } + catch( IOException e ) + {} + } + } + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), null, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile, FilterSetCollection filters ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), filters, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile, FilterSetCollection filters, + boolean overwrite ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), filters, + overwrite, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile, FilterSetCollection filters, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), filters, + overwrite, preserveLastModified ); + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile ) + throws IOException + { + copyFile( sourceFile, destFile, null, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile, FilterSetCollection filters ) + throws IOException + { + copyFile( sourceFile, destFile, filters, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile, FilterSetCollection filters, + boolean overwrite ) + throws IOException + { + copyFile( sourceFile, destFile, filters, overwrite, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile, FilterSetCollection filters, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + + if( overwrite || !destFile.exists() || + destFile.lastModified() < sourceFile.lastModified() ) + { + + if( destFile.exists() && destFile.isFile() ) + { + destFile.delete(); + } + + // ensure that parent dir of dest file exists! + // not using getParentFile method to stay 1.1 compat + File parent = getParentFile( destFile ); + if( !parent.exists() ) + { + parent.mkdirs(); + } + + if( filters != null && filters.hasFilters() ) + { + BufferedReader in = new BufferedReader( new FileReader( sourceFile ) ); + BufferedWriter out = new BufferedWriter( new FileWriter( destFile ) ); + + int length; + String newline = null; + String line = in.readLine(); + while( line != null ) + { + if( line.length() == 0 ) + { + out.newLine(); + } + else + { + newline = filters.replaceTokens( line ); + out.write( newline ); + out.newLine(); + } + line = in.readLine(); + } + + out.close(); + in.close(); + } + else + { + FileInputStream in = new FileInputStream( sourceFile ); + FileOutputStream out = new FileOutputStream( destFile ); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + out.write( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + + in.close(); + out.close(); + } + + if( preserveLastModified ) + { + setFileLastModified( destFile, sourceFile.lastModified() ); + } + } + } + + /** + * Create a temporary file in a given directory.

          + * + * The file denoted by the returned abstract pathname did not exist before + * this method was invoked, any subsequent invocation of this method will + * yield a different file name.

          + * + * This method is different to File.createTempFile of JDK 1.2 as it doesn't + * create the file itself and doesn't use platform specific temporary + * directory when the parentDir attribute is null.

          + * + * @param parentDir Directory to create the temporary file in - current + * working directory will be assumed if this parameter is null. + * @param prefix Description of Parameter + * @param suffix Description of Parameter + * @return Description of the Returned Value + * @since 1.8 + */ + public File createTempFile( String prefix, String suffix, File parentDir ) + { + + File result = null; + String parent = null; + if( parentDir != null ) + { + parent = parentDir.getPath(); + } + DecimalFormat fmt = new DecimalFormat( "#####" ); + synchronized( rand ) + { + do + { + result = new File( parent, + prefix + fmt.format( rand.nextInt() ) + + suffix ); + }while ( result.exists() ); + } + return result; + } + + /** + * "normalize" the given absolute path.

          + * + * This includes: + *

            + *
          • Uppercase the drive letter if there is one.
          • + *
          • Remove redundant slashes after the drive spec.
          • + *
          • resolve all ./, .\, ../ and ..\ sequences.
          • + *
          • DOS style paths that start with a drive letter will have \ as the + * separator.
          • + *
          + * + * + * @param path Description of Parameter + * @return Description of the Returned Value + * @throws java.lang.NullPointerException if the file path is equal to null. + */ + public File normalize( String path ) + { + String orig = path; + + path = path.replace( '/', File.separatorChar ) + .replace( '\\', File.separatorChar ); + + // make sure we are dealing with an absolute path + if( !path.startsWith( File.separator ) && + !( path.length() >= 2 && + Character.isLetter( path.charAt( 0 ) ) && + path.charAt( 1 ) == ':' ) + ) + { + String msg = path + " is not an absolute path"; + throw new BuildException( msg ); + } + + boolean dosWithDrive = false; + String root = null; + // Eliminate consecutive slashes after the drive spec + if( path.length() >= 2 && + Character.isLetter( path.charAt( 0 ) ) && + path.charAt( 1 ) == ':' ) + { + + dosWithDrive = true; + + char[] ca = path.replace( '/', '\\' ).toCharArray(); + StringBuffer sb = new StringBuffer(); + sb.append( Character.toUpperCase( ca[0] ) ).append( ':' ); + + for( int i = 2; i < ca.length; i++ ) + { + if( ( ca[i] != '\\' ) || + ( ca[i] == '\\' && ca[i - 1] != '\\' ) + ) + { + sb.append( ca[i] ); + } + } + + path = sb.toString().replace( '\\', File.separatorChar ); + if( path.length() == 2 ) + { + root = path; + path = ""; + } + else + { + root = path.substring( 0, 3 ); + path = path.substring( 3 ); + } + + } + else + { + if( path.length() == 1 ) + { + root = File.separator; + path = ""; + } + else if( path.charAt( 1 ) == File.separatorChar ) + { + // UNC drive + root = File.separator + File.separator; + path = path.substring( 2 ); + } + else + { + root = File.separator; + path = path.substring( 1 ); + } + } + + Stack s = new Stack(); + s.push( root ); + StringTokenizer tok = new StringTokenizer( path, File.separator ); + while( tok.hasMoreTokens() ) + { + String thisToken = tok.nextToken(); + if( ".".equals( thisToken ) ) + { + continue; + } + else if( "..".equals( thisToken ) ) + { + if( s.size() < 2 ) + { + throw new BuildException( "Cannot resolve path " + orig ); + } + else + { + s.pop(); + } + } + else + {// plain component + s.push( thisToken ); + } + } + + StringBuffer sb = new StringBuffer(); + for( int i = 0; i < s.size(); i++ ) + { + if( i > 1 ) + { + // not before the filesystem root and not after it, since root + // already contains one + sb.append( File.separatorChar ); + } + sb.append( s.elementAt( i ) ); + } + + path = sb.toString(); + if( dosWithDrive ) + { + path = path.replace( '/', '\\' ); + } + return new File( path ); + } + + /** + * Interpret the filename as a file relative to the given file - unless the + * filename already represents an absolute filename. + * + * @param file the "reference" file for relative paths. This instance must + * be an absolute file and must not contain "./" or + * "../" sequences (same for \ instead of /). If it is null, + * this call is equivalent to new java.io.File(filename). + * @param filename a file name + * @return an absolute file that doesn't contain "./" or + * "../" sequences and uses the correct separator for the + * current platform. + */ + public File resolveFile( File file, String filename ) + { + filename = filename.replace( '/', File.separatorChar ) + .replace( '\\', File.separatorChar ); + + // deal with absolute files + if( filename.startsWith( File.separator ) || + ( filename.length() >= 2 && + Character.isLetter( filename.charAt( 0 ) ) && + filename.charAt( 1 ) == ':' ) + ) + { + return normalize( filename ); + } + + if( file == null ) + { + return new File( filename ); + } + + File helpFile = new File( file.getAbsolutePath() ); + StringTokenizer tok = new StringTokenizer( filename, File.separator ); + while( tok.hasMoreTokens() ) + { + String part = tok.nextToken(); + if( part.equals( ".." ) ) + { + helpFile = getParentFile( helpFile ); + if( helpFile == null ) + { + String msg = "The file or path you specified (" + + filename + ") is invalid relative to " + + file.getPath(); + throw new BuildException( msg ); + } + } + else if( part.equals( "." ) ) + { + // Do nothing here + } + else + { + helpFile = new File( helpFile, part ); + } + } + + return new File( helpFile.getAbsolutePath() ); + } + + /** + * see whether we have a setLastModified method in File and return it. + * + * @return The SetLastModified value + */ + protected final Method getSetLastModified() + { + if( Project.getJavaVersion() == Project.JAVA_1_1 ) + { + return null; + } + if( setLastModified == null ) + { + synchronized( lockReflection ) + { + if( setLastModified == null ) + { + try + { + setLastModified = + java.io.File.class.getMethod( "setLastModified", + new Class[]{Long.TYPE} ); + } + catch( NoSuchMethodException nse ) + { + throw new BuildException( "File.setlastModified not in JDK > 1.1?", + nse ); + } + } + } + } + return setLastModified; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/FlatFileNameMapper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/FlatFileNameMapper.java new file mode 100644 index 000000000..37c6e5bad --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/FlatFileNameMapper.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that always returns the source file name + * without any leading directory information.

          + * + * This is the default FileNameMapper for the copy and move tasks if the flatten + * attribute has been set.

          + * + * @author Stefan Bodewig + */ +public class FlatFileNameMapper implements FileNameMapper +{ + + /** + * Ignored. + * + * @param from The new From value + */ + public void setFrom( String from ) { } + + /** + * Ignored. + * + * @param to The new To value + */ + public void setTo( String to ) { } + + /** + * Returns an one-element array containing the source file name without any + * leading directory information. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + return new String[]{new java.io.File( sourceFileName ).getName()}; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/GlobPatternMapper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/GlobPatternMapper.java new file mode 100644 index 000000000..f9cb24277 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/GlobPatternMapper.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that does simple wildcard pattern + * replacements.

          + * + * This does simple translations like *.foo -> *.bar where the prefix to .foo + * will be left unchanged. It only handles a single * character, use regular + * expressions for more complicated situations.

          + * + * This is one of the more useful Mappers, it is used by javac for example.

          + * + * @author Stefan Bodewig + */ +public class GlobPatternMapper implements FileNameMapper +{ + /** + * Part of "from" pattern before the *. + */ + protected String fromPrefix = null; + + /** + * Part of "from" pattern after the *. + */ + protected String fromPostfix = null; + + /** + * Part of "to" pattern before the *. + */ + protected String toPrefix = null; + + /** + * Part of "to" pattern after the *. + */ + protected String toPostfix = null; + + /** + * Length of the postfix ("from" pattern). + */ + protected int postfixLength; + + /** + * Length of the prefix ("from" pattern). + */ + protected int prefixLength; + + /** + * Sets the "from" pattern. Required. + * + * @param from The new From value + */ + public void setFrom( String from ) + { + int index = from.lastIndexOf( "*" ); + if( index == -1 ) + { + fromPrefix = from; + fromPostfix = ""; + } + else + { + fromPrefix = from.substring( 0, index ); + fromPostfix = from.substring( index + 1 ); + } + prefixLength = fromPrefix.length(); + postfixLength = fromPostfix.length(); + } + + /** + * Sets the "to" pattern. Required. + * + * @param to The new To value + */ + public void setTo( String to ) + { + int index = to.lastIndexOf( "*" ); + if( index == -1 ) + { + toPrefix = to; + toPostfix = ""; + } + else + { + toPrefix = to.substring( 0, index ); + toPostfix = to.substring( index + 1 ); + } + } + + /** + * Returns null if the source file name doesn't match the "from" + * pattern, an one-element array containing the translated file otherwise. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + if( fromPrefix == null + || !sourceFileName.startsWith( fromPrefix ) + || !sourceFileName.endsWith( fromPostfix ) ) + { + return null; + } + return new String[]{toPrefix + + extractVariablePart( sourceFileName ) + + toPostfix}; + } + + /** + * Returns the part of the given string that matches the * in the + * "from" pattern. + * + * @param name Description of Parameter + * @return Description of the Returned Value + */ + protected String extractVariablePart( String name ) + { + return name.substring( prefixLength, + name.length() - postfixLength ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/IdentityMapper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/IdentityMapper.java new file mode 100644 index 000000000..a93007e83 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/IdentityMapper.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that always returns the source file name. + *

          + * + * This is the default FileNameMapper for the copy and move tasks.

          + * + * @author Stefan Bodewig + */ +public class IdentityMapper implements FileNameMapper +{ + + /** + * Ignored. + * + * @param from The new From value + */ + public void setFrom( String from ) { } + + /** + * Ignored. + * + * @param to The new To value + */ + public void setTo( String to ) { } + + /** + * Returns an one-element array containing the source file name. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + return new String[]{sourceFileName}; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/MergingMapper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/MergingMapper.java new file mode 100644 index 000000000..d9c0b866e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/MergingMapper.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that always returns the same target file + * name.

          + * + * This is the default FileNameMapper for the archiving tasks and uptodate.

          + * + * @author Stefan Bodewig + */ +public class MergingMapper implements FileNameMapper +{ + protected String[] mergedFile = null; + + /** + * Ignored. + * + * @param from The new From value + */ + public void setFrom( String from ) { } + + /** + * Sets the name of the merged file. + * + * @param to The new To value + */ + public void setTo( String to ) + { + mergedFile = new String[]{to}; + } + + /** + * Returns an one-element array containing the file name set via setTo. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + return mergedFile; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/RegexpPatternMapper.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/RegexpPatternMapper.java new file mode 100644 index 000000000..df29877a4 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/RegexpPatternMapper.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.regexp.RegexpMatcher; +import org.apache.tools.ant.util.regexp.RegexpMatcherFactory; + +/** + * Implementation of FileNameMapper that does regular expression replacements. + * + * @author Stefan Bodewig + */ +public class RegexpPatternMapper implements FileNameMapper +{ + protected RegexpMatcher reg = null; + protected char[] to = null; + protected StringBuffer result = new StringBuffer(); + + public RegexpPatternMapper() + throws BuildException + { + reg = ( new RegexpMatcherFactory() ).newRegexpMatcher(); + } + + /** + * Sets the "from" pattern. Required. + * + * @param from The new From value + * @exception BuildException Description of Exception + */ + public void setFrom( String from ) + throws BuildException + { + try + { + reg.setPattern( from ); + } + catch( NoClassDefFoundError e ) + { + // depending on the implementation the actual RE won't + // get instantiated in the constructor. + throw new BuildException( "Cannot load regular expression matcher", + e ); + } + } + + /** + * Sets the "to" pattern. Required. + * + * @param to The new To value + */ + public void setTo( String to ) + { + this.to = to.toCharArray(); + } + + /** + * Returns null if the source file name doesn't match the "from" + * pattern, an one-element array containing the translated file otherwise. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + if( reg == null || to == null + || !reg.matches( sourceFileName ) ) + { + return null; + } + return new String[]{replaceReferences( sourceFileName )}; + } + + /** + * Replace all backreferences in the to pattern with the matched groups of + * the source. + * + * @param source Description of Parameter + * @return Description of the Returned Value + */ + protected String replaceReferences( String source ) + { + Vector v = reg.getGroups( source ); + + result.setLength( 0 ); + for( int i = 0; i < to.length; i++ ) + { + if( to[i] == '\\' ) + { + if( ++i < to.length ) + { + int value = Character.digit( to[i], 10 ); + if( value > -1 ) + { + result.append( ( String )v.elementAt( value ) ); + } + else + { + result.append( to[i] ); + } + } + else + { + // XXX - should throw an exception instead? + result.append( '\\' ); + } + } + else + { + result.append( to[i] ); + } + } + return result.toString(); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/SourceFileScanner.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/SourceFileScanner.java new file mode 100644 index 000000000..4106357ac --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/SourceFileScanner.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.io.File; +import java.util.Vector; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.myrmidon.framework.Os; + +/** + * Utility class that collects the functionality of the various scanDir methods + * that have been scattered in several tasks before.

          + * + * The only method returns an array of source files. The array is a subset of + * the files given as a parameter and holds only those that are newer than their + * corresponding target files.

          + * + * @author Stefan Bodewig + */ +public class SourceFileScanner +{ + + protected Task task; + + private FileUtils fileUtils; + + /** + * @param task The task we should log messages through + */ + public SourceFileScanner( Task task ) + { + this.task = task; + fileUtils = FileUtils.newFileUtils(); + } + + /** + * Restrict the given set of files to those that are newer than their + * corresponding target files. + * + * @param files the original set of files + * @param srcDir all files are relative to this directory + * @param destDir target files live here. if null file names returned by the + * mapper are assumed to be absolute. + * @param mapper knows how to construct a target file names from source file + * names. + * @return Description of the Returned Value + */ + public String[] restrict( String[] files, File srcDir, File destDir, + FileNameMapper mapper ) + { + + long now = ( new java.util.Date() ).getTime(); + StringBuffer targetList = new StringBuffer(); + + /* + * If we're on Windows, we have to munge the time up to 2 secs to + * be able to check file modification times. + * (Windows has a max resolution of two secs for modification times) + * Actually this is a feature of the FAT file system, NTFS does + * not have it, so if we could reliably passively test for an NTFS + * file systems we could turn this off... + */ + if( Os.isFamily( "windows" ) ) + { + now += 2000; + } + + Vector v = new Vector(); + for( int i = 0; i < files.length; i++ ) + { + + String[] targets = mapper.mapFileName( files[i] ); + if( targets == null || targets.length == 0 ) + { + task.log( files[i] + " skipped - don\'t know how to handle it", + Project.MSG_VERBOSE ); + continue; + } + + File src = fileUtils.resolveFile( srcDir, files[i] ); + + if( src.lastModified() > now ) + { + task.log( "Warning: " + files[i] + " modified in the future.", + Project.MSG_WARN ); + } + + boolean added = false; + targetList.setLength( 0 ); + for( int j = 0; !added && j < targets.length; j++ ) + { + File dest = fileUtils.resolveFile( destDir, targets[j] ); + + if( !dest.exists() ) + { + task.log( files[i] + " added as " + dest.getAbsolutePath() + " doesn\'t exist.", + Project.MSG_VERBOSE ); + v.addElement( files[i] ); + added = true; + } + else if( src.lastModified() > dest.lastModified() ) + { + task.log( files[i] + " added as " + dest.getAbsolutePath() + " is outdated.", + Project.MSG_VERBOSE ); + v.addElement( files[i] ); + added = true; + } + else + { + if( targetList.length() > 0 ) + { + targetList.append( ", " ); + } + targetList.append( dest.getAbsolutePath() ); + } + } + + if( !added ) + { + task.log( files[i] + " omitted as " + targetList.toString() + + ( targets.length == 1 ? " is" : " are " ) + + " up to date.", Project.MSG_VERBOSE ); + } + + } + String[] result = new String[v.size()]; + v.copyInto( result ); + return result; + } + + /** + * Convinience layer on top of restrict that returns the source files as + * File objects (containing absolute paths if srcDir is absolute). + * + * @param files Description of Parameter + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param mapper Description of Parameter + * @return Description of the Returned Value + */ + public File[] restrictAsFiles( String[] files, File srcDir, File destDir, + FileNameMapper mapper ) + { + String[] res = restrict( files, srcDir, destDir, mapper ); + File[] result = new File[res.length]; + for( int i = 0; i < res.length; i++ ) + { + result[i] = new File( srcDir, res[i] ); + } + return result; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/StringUtils.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/StringUtils.java new file mode 100644 index 000000000..6881490c0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/StringUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * A set of helper methods related to string manipulation. + * + * @author Stephane Bailliez + */ +public final class StringUtils +{ + + /** + * the line separator for this OS + */ + public final static String LINE_SEP = System.getProperty( "line.separator" ); + + /** + * Convenient method to retrieve the full stacktrace from a given exception. + * + * @param t the exception to get the stacktrace from. + * @return the stacktrace from the given exception. + */ + public static String getStackTrace( Throwable t ) + { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter( sw, true ); + t.printStackTrace( pw ); + pw.flush(); + pw.close(); + return sw.toString(); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Dependencies.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Dependencies.java new file mode 100644 index 000000000..b7808ff0c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Dependencies.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.depend; +import java.io.*; +import java.util.*; +import org.apache.bcel.*; +import org.apache.bcel.classfile.*; + + +public class Dependencies implements Visitor +{ + private boolean verbose = false; + private Set dependencies = new HashSet(); + private ConstantPool constantPool; + + private JavaClass javaClass; + + public static void applyFilter( Collection collection, Filter filter ) + { + Iterator i = collection.iterator(); + while( i.hasNext() ) + { + Object next = i.next(); + if( !filter.accept( next ) ) + { + i.remove(); + } + } + } + + public static void main( String[] args ) + { + try + { + Dependencies visitor = new Dependencies(); + + Set set = new TreeSet(); + Set newSet = new HashSet(); + + int o = 0; + String arg = null; + if( "-base".equals( args[0] ) ) + { + arg = args[1]; + if( !arg.endsWith( File.separator ) ) + { + arg = arg + File.separator; + } + o = 2; + } + final String base = arg; + + for( int i = o; i < args.length; i++ ) + { + String fileName = args[i].substring( 0, args[i].length() - ".class".length() ); + if( base != null && fileName.startsWith( base ) ) + fileName = fileName.substring( base.length() ); + newSet.add( fileName ); + } + set.addAll( newSet ); + + do + { + Iterator i = newSet.iterator(); + while( i.hasNext() ) + { + String fileName = i.next() + ".class"; + + if( base != null ) + { + fileName = base + fileName; + } + + JavaClass javaClass = new ClassParser( fileName ).parse(); + javaClass.accept( visitor ); + } + newSet.clear(); + newSet.addAll( visitor.getDependencies() ); + visitor.clearDependencies(); + + applyFilter( newSet, + new Filter() + { + public boolean accept( Object object ) + { + String fileName = object + ".class"; + if( base != null ) + fileName = base + fileName; + return new File( fileName ).exists(); + } + } ); + newSet.removeAll( set ); + set.addAll( newSet ); + }while ( newSet.size() > 0 ); + + Iterator i = set.iterator(); + while( i.hasNext() ) + { + System.out.println( i.next() ); + } + } + catch( Exception e ) + { + System.err.println( e.getMessage() ); + e.printStackTrace( System.err ); + } + } + + public Set getDependencies() + { + return dependencies; + } + + public void clearDependencies() + { + dependencies.clear(); + } + + public void visitCode( Code obj ) { } + + public void visitCodeException( CodeException obj ) { } + + public void visitConstantClass( ConstantClass obj ) + { + if( verbose ) + { + System.out.println( "visit ConstantClass" ); + System.out.println( obj.getConstantValue( constantPool ) ); + } + dependencies.add( "" + obj.getConstantValue( constantPool ) ); + } + + public void visitConstantDouble( ConstantDouble obj ) { } + + public void visitConstantFieldref( ConstantFieldref obj ) { } + + public void visitConstantFloat( ConstantFloat obj ) { } + + public void visitConstantInteger( ConstantInteger obj ) { } + + public void visitConstantInterfaceMethodref( ConstantInterfaceMethodref obj ) { } + + public void visitConstantLong( ConstantLong obj ) { } + + public void visitConstantMethodref( ConstantMethodref obj ) { } + + public void visitConstantNameAndType( ConstantNameAndType obj ) { } + + public void visitConstantPool( ConstantPool obj ) + { + if( verbose ) + System.out.println( "visit ConstantPool" ); + this.constantPool = obj; + + // visit constants + for( int idx = 0; idx < constantPool.getLength(); idx++ ) + { + Constant c = constantPool.getConstant( idx ); + if( c != null ) + { + c.accept( this ); + } + } + } + + public void visitConstantString( ConstantString obj ) { } + + public void visitConstantUtf8( ConstantUtf8 obj ) { } + + public void visitConstantValue( ConstantValue obj ) { } + + public void visitDeprecated( Deprecated obj ) { } + + public void visitExceptionTable( ExceptionTable obj ) { } + + public void visitField( Field obj ) + { + if( verbose ) + { + System.out.println( "visit Field" ); + System.out.println( obj.getSignature() ); + } + addClasses( obj.getSignature() ); + } + + public void visitInnerClass( InnerClass obj ) { } + + public void visitInnerClasses( InnerClasses obj ) { } + + public void visitJavaClass( JavaClass obj ) + { + if( verbose ) + { + System.out.println( "visit JavaClass" ); + } + + this.javaClass = obj; + dependencies.add( javaClass.getClassName().replace( '.', '/' ) ); + + // visit constant pool + javaClass.getConstantPool().accept( this ); + + // visit fields + Field[] fields = obj.getFields(); + for( int i = 0; i < fields.length; i++ ) + { + fields[i].accept( this ); + } + + // visit methods + Method[] methods = obj.getMethods(); + for( int i = 0; i < methods.length; i++ ) + { + methods[i].accept( this ); + } + } + + public void visitLineNumber( LineNumber obj ) { } + + public void visitLineNumberTable( LineNumberTable obj ) { } + + public void visitLocalVariable( LocalVariable obj ) { } + + public void visitLocalVariableTable( LocalVariableTable obj ) { } + + public void visitMethod( Method obj ) + { + if( verbose ) + { + System.out.println( "visit Method" ); + System.out.println( obj.getSignature() ); + } + String signature = obj.getSignature(); + int pos = signature.indexOf( ")" ); + addClasses( signature.substring( 1, pos ) ); + addClasses( signature.substring( pos + 1 ) ); + } + + public void visitSourceFile( SourceFile obj ) { } + + public void visitStackMap( StackMap obj ) { } + + public void visitStackMapEntry( StackMapEntry obj ) { } + + public void visitSynthetic( Synthetic obj ) { } + + public void visitUnknown( Unknown obj ) { } + + void addClass( String string ) + { + int pos = string.indexOf( 'L' ); + if( pos != -1 ) + { + dependencies.add( string.substring( pos + 1 ) ); + } + } + + void addClasses( String string ) + { + StringTokenizer tokens = new StringTokenizer( string, ";" ); + while( tokens.hasMoreTokens() ) + { + addClass( tokens.nextToken() ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Filter.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Filter.java new file mode 100644 index 000000000..2cd26d2aa --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/depend/Filter.java @@ -0,0 +1,14 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.depend; +import java.util.*; + +public interface Filter +{ + boolean accept( Object object ); +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java new file mode 100644 index 000000000..1421fb167 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; +import org.apache.tools.ant.BuildException; + +/** + * Implementation of RegexpMatcher for Jakarta-ORO. + * + * @author Stefan Bodewig + * @author Matthew Inger + */ +public class JakartaOroMatcher implements RegexpMatcher +{ + protected final Perl5Compiler compiler = new Perl5Compiler(); + protected final Perl5Matcher matcher = new Perl5Matcher(); + + private String pattern; + + public JakartaOroMatcher() { } + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + */ + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + /** + * Returns a Vector of matched groups found in the argument.

          + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

          . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String argument ) + throws BuildException + { + return getGroups( argument, MATCH_DEFAULT ); + } + + /** + * Returns a Vector of matched groups found in the argument.

          + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

          . + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String input, int options ) + throws BuildException + { + if( !matches( input, options ) ) + { + return null; + } + Vector v = new Vector(); + MatchResult mr = matcher.getMatch(); + int cnt = mr.groups(); + for( int i = 0; i < cnt; i++ ) + { + v.addElement( mr.group( i ) ); + } + return v; + } + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + */ + public String getPattern() + { + return this.pattern; + } + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String argument ) + throws BuildException + { + return matches( argument, MATCH_DEFAULT ); + } + + /** + * Does the given argument match the pattern? + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String input, int options ) + throws BuildException + { + Pattern p = getCompiledPattern( options ); + return matcher.contains( input, p ); + } + + /** + * Get a compiled representation of the regexp pattern + * + * @param options Description of Parameter + * @return The CompiledPattern value + * @exception BuildException Description of Exception + */ + protected Pattern getCompiledPattern( int options ) + throws BuildException + { + try + { + // compute the compiler options based on the input options first + Pattern p = compiler.compile( pattern, getCompilerOptions( options ) ); + return p; + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + protected int getCompilerOptions( int options ) + { + int cOptions = Perl5Compiler.DEFAULT_MASK; + + if( RegexpUtil.hasFlag( options, MATCH_CASE_INSENSITIVE ) ) + { + cOptions |= Perl5Compiler.CASE_INSENSITIVE_MASK; + } + if( RegexpUtil.hasFlag( options, MATCH_MULTILINE ) ) + { + cOptions |= Perl5Compiler.MULTILINE_MASK; + } + if( RegexpUtil.hasFlag( options, MATCH_SINGLELINE ) ) + { + cOptions |= Perl5Compiler.SINGLELINE_MASK; + } + + return cOptions; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java new file mode 100644 index 000000000..88e8f31f6 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.oro.text.regex.Perl5Substitution; +import org.apache.oro.text.regex.Substitution; +import org.apache.oro.text.regex.Util; +import org.apache.tools.ant.BuildException; + + +/** + * Regular expression implementation using the Jakarta Oro package + * + * @author Matthew Inger + * mattinger@mindless.com + */ +public class JakartaOroRegexp extends JakartaOroMatcher implements Regexp +{ + + public JakartaOroRegexp() + { + super(); + } + + public String substitute( String input, String argument, int options ) + throws BuildException + { + // translate \1 to $1 so that the Perl5Substitution will work + StringBuffer subst = new StringBuffer(); + for( int i = 0; i < argument.length(); i++ ) + { + char c = argument.charAt( i ); + if( c == '\\' ) + { + if( ++i < argument.length() ) + { + c = argument.charAt( i ); + int value = Character.digit( c, 10 ); + if( value > -1 ) + { + subst.append( "$" ).append( value ); + } + else + { + subst.append( c ); + } + } + else + { + // XXX - should throw an exception instead? + subst.append( '\\' ); + } + } + else + { + subst.append( c ); + } + } + + // Do the substitution + Substitution s = + new Perl5Substitution( subst.toString(), + Perl5Substitution.INTERPOLATE_ALL ); + return Util.substitute( matcher, + getCompiledPattern( options ), + s, + input, + getSubsOptions( options ) ); + } + + protected int getSubsOptions( int options ) + { + boolean replaceAll = RegexpUtil.hasFlag( options, REPLACE_ALL ); + int subsOptions = 1; + if( replaceAll ) + { + subsOptions = Util.SUBSTITUTE_ALL; + } + return subsOptions; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java new file mode 100644 index 000000000..a51a8ff3a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.regexp.RE; +import org.apache.regexp.RESyntaxException; +import org.apache.tools.ant.BuildException; + +/** + * Implementation of RegexpMatcher for Jakarta-Regexp. + * + * @author Stefan Bodewig + * @author Matthew Inger + * mattinger@mindless.com + */ +public class JakartaRegexpMatcher implements RegexpMatcher +{ + + private String pattern; + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + */ + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + /** + * Returns a Vector of matched groups found in the argument.

          + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

          . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String argument ) + throws BuildException + { + return getGroups( argument, MATCH_DEFAULT ); + } + + public Vector getGroups( String input, int options ) + throws BuildException + { + RE reg = getCompiledPattern( options ); + if( !matches( input, reg ) ) + { + return null; + } + Vector v = new Vector(); + int cnt = reg.getParenCount(); + for( int i = 0; i < cnt; i++ ) + { + v.addElement( reg.getParen( i ) ); + } + return v; + } + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + */ + public String getPattern() + { + return pattern; + } + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String argument ) + throws BuildException + { + return matches( argument, MATCH_DEFAULT ); + } + + /** + * Does the given argument match the pattern? + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String input, int options ) + throws BuildException + { + return matches( input, getCompiledPattern( options ) ); + } + + protected RE getCompiledPattern( int options ) + throws BuildException + { + int cOptions = getCompilerOptions( options ); + try + { + RE reg = new RE( pattern ); + reg.setMatchFlags( cOptions ); + return reg; + } + catch( RESyntaxException e ) + { + throw new BuildException( e ); + } + } + + protected int getCompilerOptions( int options ) + { + int cOptions = RE.MATCH_NORMAL; + + if( RegexpUtil.hasFlag( options, MATCH_CASE_INSENSITIVE ) ) + cOptions |= RE.MATCH_CASEINDEPENDENT; + if( RegexpUtil.hasFlag( options, MATCH_MULTILINE ) ) + cOptions |= RE.MATCH_MULTILINE; + if( RegexpUtil.hasFlag( options, MATCH_SINGLELINE ) ) + cOptions |= RE.MATCH_SINGLELINE; + + return cOptions; + } + + private boolean matches( String input, RE reg ) + { + return reg.match( input ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java new file mode 100644 index 000000000..3f41e50e5 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.regexp.RE; +import org.apache.tools.ant.BuildException; + +/** + * Regular expression implementation using the Jakarta Regexp package + * + * @author Matthew Inger + * mattinger@mindless.com + */ +public class JakartaRegexpRegexp extends JakartaRegexpMatcher implements Regexp +{ + + public JakartaRegexpRegexp() + { + super(); + } + + public String substitute( String input, String argument, int options ) + throws BuildException + { + Vector v = getGroups( input, options ); + + // replace \1 with the corresponding group + StringBuffer result = new StringBuffer(); + for( int i = 0; i < argument.length(); i++ ) + { + char c = argument.charAt( i ); + if( c == '\\' ) + { + if( ++i < argument.length() ) + { + c = argument.charAt( i ); + int value = Character.digit( c, 10 ); + if( value > -1 ) + { + result.append( ( String )v.elementAt( value ) ); + } + else + { + result.append( c ); + } + } + else + { + // XXX - should throw an exception instead? + result.append( '\\' ); + } + } + else + { + result.append( c ); + } + } + argument = result.toString(); + + RE reg = getCompiledPattern( options ); + int sOptions = getSubsOptions( options ); + return reg.subst( input, argument, sOptions ); + } + + protected int getSubsOptions( int options ) + { + int subsOptions = RE.REPLACE_FIRSTONLY; + if( RegexpUtil.hasFlag( options, REPLACE_ALL ) ) + subsOptions = RE.REPLACE_ALL; + return subsOptions; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java new file mode 100644 index 000000000..7c486420e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import org.apache.tools.ant.BuildException; + +/** + * Implementation of RegexpMatcher for the built-in regexp matcher of JDK 1.4. + * + * @author Stefan Bodewig + * @author Matthew Inger + * mattinger@mindless.com + */ +public class Jdk14RegexpMatcher implements RegexpMatcher +{ + + private String pattern; + + public Jdk14RegexpMatcher() { } + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + */ + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + /** + * Returns a Vector of matched groups found in the argument.

          + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

          . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String argument ) + throws BuildException + { + return getGroups( argument, MATCH_DEFAULT ); + } + + /** + * Returns a Vector of matched groups found in the argument.

          + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

          . + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String input, int options ) + throws BuildException + { + Pattern p = getCompiledPattern( options ); + Matcher matcher = p.matcher( input ); + if( !matcher.find() ) + { + return null; + } + Vector v = new Vector(); + int cnt = matcher.groupCount(); + for( int i = 0; i <= cnt; i++ ) + { + v.addElement( matcher.group( i ) ); + } + return v; + } + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + */ + public String getPattern() + { + return pattern; + } + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String argument ) + throws BuildException + { + return matches( argument, MATCH_DEFAULT ); + } + + /** + * Does the given argument match the pattern? + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String input, int options ) + throws BuildException + { + try + { + Pattern p = getCompiledPattern( options ); + return p.matcher( input ).find(); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + protected Pattern getCompiledPattern( int options ) + throws BuildException + { + int cOptions = getCompilerOptions( options ); + try + { + Pattern p = Pattern.compile( this.pattern, cOptions ); + return p; + } + catch( PatternSyntaxException e ) + { + throw new BuildException( e ); + } + } + + protected int getCompilerOptions( int options ) + { + int cOptions = 0; + + if( RegexpUtil.hasFlag( options, MATCH_CASE_INSENSITIVE ) ) + cOptions |= Pattern.CASE_INSENSITIVE; + if( RegexpUtil.hasFlag( options, MATCH_MULTILINE ) ) + cOptions |= Pattern.MULTILINE; + if( RegexpUtil.hasFlag( options, MATCH_SINGLELINE ) ) + cOptions |= Pattern.DOTALL; + + return cOptions; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java new file mode 100644 index 000000000..23998f0e6 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.tools.ant.BuildException; + + +/** + * Regular expression implementation using the JDK 1.4 regular expression + * package + * + * @author Matthew Inger + * mattinger@mindless.com + */ +public class Jdk14RegexpRegexp extends Jdk14RegexpMatcher implements Regexp +{ + + public Jdk14RegexpRegexp() + { + super(); + } + + public String substitute( String input, String argument, int options ) + throws BuildException + { + // translate \1 to $(1) so that the Matcher will work + StringBuffer subst = new StringBuffer(); + for( int i = 0; i < argument.length(); i++ ) + { + char c = argument.charAt( i ); + if( c == '\\' ) + { + if( ++i < argument.length() ) + { + c = argument.charAt( i ); + int value = Character.digit( c, 10 ); + if( value > -1 ) + { + subst.append( "$" ).append( value ); + } + else + { + subst.append( c ); + } + } + else + { + // XXX - should throw an exception instead? + subst.append( '\\' ); + } + } + else + { + subst.append( c ); + } + } + argument = subst.toString(); + + int sOptions = getSubsOptions( options ); + Pattern p = getCompiledPattern( options ); + StringBuffer sb = new StringBuffer(); + + Matcher m = p.matcher( input ); + if( RegexpUtil.hasFlag( sOptions, REPLACE_ALL ) ) + { + sb.append( m.replaceAll( argument ) ); + } + else + { + boolean res = m.find(); + if( res ) + { + m.appendReplacement( sb, argument ); + m.appendTail( sb ); + } + else + { + sb.append( input ); + } + } + + return sb.toString(); + } + + protected int getSubsOptions( int options ) + { + int subsOptions = REPLACE_FIRST; + if( RegexpUtil.hasFlag( options, REPLACE_ALL ) ) + subsOptions = REPLACE_ALL; + return subsOptions; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Regexp.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Regexp.java new file mode 100644 index 000000000..2acaeda47 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/Regexp.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.tools.ant.BuildException; + +/** + * Interface which represents a regular expression, and the operations that can + * be performed on it. + * + * @author Matthew Inger + */ +public interface Regexp extends RegexpMatcher +{ + + /** + * Replace only the first occurance of the regular expression + */ + int REPLACE_FIRST = 0x00000001; + + /** + * Replace all occurances of the regular expression + */ + int REPLACE_ALL = 0x00000010; + + /** + * Perform a substitution on the regular expression. + * + * @param input The string to substitute on + * @param argument The string which defines the substitution + * @param options The list of options for the match and replace. See the + * MATCH_ and REPLACE_ constants above. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + String substitute( String input, String argument, int options ) + throws BuildException; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpFactory.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpFactory.java new file mode 100644 index 000000000..559bdf958 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpFactory.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Regular expression factory, which will create Regexp objects. The actual + * implementation class depends on the System or Ant Property: ant.regexp.regexpimpl + * . + * + * @author Matthew Inger + * mattinger@mindless.com + * @version $Revision$ + */ +public class RegexpFactory extends RegexpMatcherFactory +{ + public RegexpFactory() { } + + /** + * Create a new regular expression matcher instance. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Regexp newRegexp() + throws BuildException + { + return ( Regexp )newRegexp( null ); + } + + /** + * Create a new regular expression matcher instance. + * + * @param p Project whose ant.regexp.regexpimpl property will be used. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Regexp newRegexp( Project p ) + throws BuildException + { + String systemDefault = null; + if( p == null ) + { + systemDefault = System.getProperty( "ant.regexp.regexpimpl" ); + } + else + { + systemDefault = ( String )p.getProperties().get( "ant.regexp.regexpimpl" ); + } + + if( systemDefault != null ) + { + return createRegexpInstance( systemDefault ); + // XXX should we silently catch possible exceptions and try to + // load a different implementation? + } + + try + { + return createRegexpInstance( "org.apache.tools.ant.util.regexp.Jdk14RegexpRegexp" ); + } + catch( BuildException be ) + {} + + try + { + return createRegexpInstance( "org.apache.tools.ant.util.regexp.JakartaOroRegexp" ); + } + catch( BuildException be ) + {} + + try + { + return createRegexpInstance( "org.apache.tools.ant.util.regexp.JakartaRegexpRegexp" ); + } + catch( BuildException be ) + {} + + throw new BuildException( "No supported regular expression matcher found" ); + } + + /** + * Wrapper over {@seee RegexpMatcherFactory#createInstance createInstance} + * that ensures that we are dealing with a Regexp implementation. + * + * @param classname Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @since 1.3 + */ + protected Regexp createRegexpInstance( String classname ) + throws BuildException + { + + RegexpMatcher m = createInstance( classname ); + if( m instanceof Regexp ) + { + return ( Regexp )m; + } + else + { + throw new BuildException( classname + " doesn't implement the Regexp interface" ); + } + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcher.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcher.java new file mode 100644 index 000000000..26ef487c0 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcher.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.tools.ant.BuildException; + +/** + * Interface describing a regular expression matcher. + * + * @author Stefan Bodewig + * @author Matthew Inger + */ +public interface RegexpMatcher +{ + + /** + * Default Mask (case insensitive, neither multiline nor singleline + * specified). + */ + int MATCH_DEFAULT = 0x00000000; + + /** + * Perform a case insenstive match + */ + int MATCH_CASE_INSENSITIVE = 0x00000100; + + /** + * Treat the input as a multiline input + */ + int MATCH_MULTILINE = 0x00001000; + + /** + * Treat the input as singleline input ('.' matches newline) + */ + int MATCH_SINGLELINE = 0x00010000; + + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + * @exception BuildException Description of Exception + */ + void setPattern( String pattern ) + throws BuildException; + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + * @exception BuildException Description of Exception + */ + String getPattern() + throws BuildException; + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + boolean matches( String argument ) + throws BuildException; + + /** + * Returns a Vector of matched groups found in the argument.

          + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

          . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + Vector getGroups( String argument ) + throws BuildException; + + /** + * Does this regular expression match the input, given certain options + * + * @param input The string to check for a match + * @param options The list of options for the match. See the MATCH_ + * constants above. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + boolean matches( String input, int options ) + throws BuildException; + + /** + * Get the match groups from this regular expression. The return type of the + * elements is always String. + * + * @param input The string to check for a match + * @param options The list of options for the match. See the MATCH_ + * constants above. + * @return The Groups value + * @exception BuildException Description of Exception + */ + Vector getGroups( String input, int options ) + throws BuildException; + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java new file mode 100644 index 000000000..ae7b5331a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Simple Factory Class that produces an implementation of RegexpMatcher based + * on the system property ant.regexp.matcherimpl and the classes + * available.

          + * + * In a more general framework this class would be abstract and have a static + * newInstance method.

          + * + * @author Stefan Bodewig + */ +public class RegexpMatcherFactory +{ + + public RegexpMatcherFactory() { } + + /** + * Create a new regular expression instance. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public RegexpMatcher newRegexpMatcher() + throws BuildException + { + return newRegexpMatcher( null ); + } + + /** + * Create a new regular expression instance. + * + * @param p Project whose ant.regexp.regexpimpl property will be used. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public RegexpMatcher newRegexpMatcher( Project p ) + throws BuildException + { + String systemDefault = null; + if( p == null ) + { + systemDefault = System.getProperty( "ant.regexp.regexpimpl" ); + } + else + { + systemDefault = ( String )p.getProperties().get( "ant.regexp.regexpimpl" ); + } + + if( systemDefault != null ) + { + return createInstance( systemDefault ); + // XXX should we silently catch possible exceptions and try to + // load a different implementation? + } + + try + { + return createInstance( "org.apache.tools.ant.util.regexp.Jdk14RegexpMatcher" ); + } + catch( BuildException be ) + {} + + try + { + return createInstance( "org.apache.tools.ant.util.regexp.JakartaOroMatcher" ); + } + catch( BuildException be ) + {} + + try + { + return createInstance( "org.apache.tools.ant.util.regexp.JakartaRegexpMatcher" ); + } + catch( BuildException be ) + {} + + throw new BuildException( "No supported regular expression matcher found" ); + } + + protected RegexpMatcher createInstance( String className ) + throws BuildException + { + try + { + Class implClass = Class.forName( className ); + return ( RegexpMatcher )implClass.newInstance(); + } + catch( Throwable t ) + { + throw new BuildException( t ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpUtil.java b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpUtil.java new file mode 100644 index 000000000..fb1e3416a --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/ant/util/regexp/RegexpUtil.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; + +/** + * Regular expression utilities class which handles flag operations + * + * @author Matthew Inger + */ +public class RegexpUtil extends Object +{ + public final static boolean hasFlag( int options, int flag ) + { + return ( ( options & flag ) > 0 ); + } + + public final static int removeFlag( int options, int flag ) + { + return ( options & ( 0xFFFFFFFF - flag ) ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/bzip2/BZip2Constants.java b/proposal/myrmidon/src/main/org/apache/tools/bzip2/BZip2Constants.java new file mode 100644 index 000000000..fa26ce9fc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/bzip2/BZip2Constants.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; + +/** + * Base class for both the compress and decompress classes. Holds common arrays, + * and static data. + * + * @author Keiron Liddle + */ +public interface BZip2Constants +{ + + int baseBlockSize = 100000; + int MAX_ALPHA_SIZE = 258; + int MAX_CODE_LEN = 23; + int RUNA = 0; + int RUNB = 1; + int N_GROUPS = 6; + int G_SIZE = 50; + int N_ITERS = 4; + int MAX_SELECTORS = ( 2 + ( 900000 / G_SIZE ) ); + int NUM_OVERSHOOT_BYTES = 20; + + int rNums[] = { + 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, + 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, + 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, + 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, + 878, 465, 811, 169, 869, 675, 611, 697, 867, 561, + 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, + 150, 238, 59, 379, 684, 877, 625, 169, 643, 105, + 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, + 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, + 909, 545, 703, 919, 874, 474, 882, 500, 594, 612, + 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, + 161, 604, 958, 533, 221, 400, 386, 867, 600, 782, + 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, + 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, + 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, + 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, + 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, + 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, + 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, + 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, + 645, 863, 803, 350, 139, 93, 354, 99, 820, 908, + 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, + 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, + 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, + 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, + 857, 956, 358, 619, 580, 124, 737, 594, 701, 612, + 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, + 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, + 344, 805, 988, 739, 511, 655, 814, 334, 249, 515, + 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, + 433, 837, 553, 268, 926, 240, 102, 654, 459, 51, + 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, + 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, + 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, + 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, + 707, 151, 457, 449, 797, 195, 791, 558, 945, 679, + 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, + 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, + 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, + 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, + 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, + 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, + 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, + 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, + 661, 821, 976, 991, 658, 869, 905, 758, 745, 193, + 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, + 61, 688, 793, 644, 986, 403, 106, 366, 905, 644, + 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, + 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, + 920, 176, 193, 713, 857, 265, 203, 50, 668, 108, + 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, + 936, 638 + }; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2InputStream.java b/proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2InputStream.java new file mode 100644 index 000000000..d614359db --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2InputStream.java @@ -0,0 +1,939 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; +import java.io.*; + +/** + * An input stream that decompresses from the BZip2 format (without the file + * header chars) to be read as any other stream. + * + * @author Keiron Liddle + */ +public class CBZip2InputStream extends InputStream implements BZip2Constants +{ + + private final static int START_BLOCK_STATE = 1; + private final static int RAND_PART_A_STATE = 2; + private final static int RAND_PART_B_STATE = 3; + private final static int RAND_PART_C_STATE = 4; + private final static int NO_RAND_PART_A_STATE = 5; + private final static int NO_RAND_PART_B_STATE = 6; + private final static int NO_RAND_PART_C_STATE = 7; + private CRC mCrc = new CRC(); + + private boolean inUse[] = new boolean[256]; + + private char seqToUnseq[] = new char[256]; + private char unseqToSeq[] = new char[256]; + + private char selector[] = new char[MAX_SELECTORS]; + private char selectorMtf[] = new char[MAX_SELECTORS]; + + /* + * freq table collected to save a pass over the data + * during decompression. + */ + private int unzftab[] = new int[256]; + + private int limit[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int base[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int perm[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int minLens[] = new int[N_GROUPS]; + + private boolean streamEnd = false; + + private int currentChar = -1; + + private int currentState = START_BLOCK_STATE; + int rNToGo = 0; + int rTPos = 0; + int i, tPos; + + int i2, count, chPrev, ch2; + int j2; + char z; + + private boolean blockRandomised; + + /* + * always: in the range 0 .. 9. + * The current block size is 100000 * this number. + */ + private int blockSize100k; + private int bsBuff; + private int bsLive; + + private InputStream bsStream; + + private int bytesIn; + private int bytesOut; + private int computedBlockCRC, computedCombinedCRC; + + /* + * index of the last char in the block, so + * the block size == last + 1. + */ + private int last; + private char[] ll8; + private int nInUse; + + /* + * index in zptr[] of original string after sorting. + */ + private int origPtr; + + private int storedBlockCRC, storedCombinedCRC; + + private int[] tt; + + public CBZip2InputStream( InputStream zStream ) + { + ll8 = null; + tt = null; + bsSetStream( zStream ); + initialize(); + initBlock(); + setupBlock(); + } + + private static void badBGLengths() + { + cadvise(); + } + + private static void badBlockHeader() + { + cadvise(); + } + + private static void bitStreamEOF() + { + cadvise(); + } + + private static void blockOverrun() + { + cadvise(); + } + + private static void cadvise() + { + System.out.println( "CRC Error" ); + //throw new CCoruptionError(); + } + + private static void compressedStreamEOF() + { + cadvise(); + } + + private static void crcError() + { + cadvise(); + } + + public int read() + { + if( streamEnd ) + { + return -1; + } + else + { + int retChar = currentChar; + switch ( currentState ) + { + case START_BLOCK_STATE: + break; + case RAND_PART_A_STATE: + break; + case RAND_PART_B_STATE: + setupRandPartB(); + break; + case RAND_PART_C_STATE: + setupRandPartC(); + break; + case NO_RAND_PART_A_STATE: + break; + case NO_RAND_PART_B_STATE: + setupNoRandPartB(); + break; + case NO_RAND_PART_C_STATE: + setupNoRandPartC(); + break; + default: + break; + } + return retChar; + } + } + + private void setDecompressStructureSizes( int newSize100k ) + { + if( !( 0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k + && blockSize100k <= 9 ) ) + { + // throw new IOException("Invalid block size"); + } + + blockSize100k = newSize100k; + + if( newSize100k == 0 ) + return; + + int n = baseBlockSize * newSize100k; + ll8 = new char[n]; + tt = new int[n]; + } + + private void setupBlock() + { + int cftab[] = new int[257]; + char ch; + + cftab[0] = 0; + for( i = 1; i <= 256; i++ ) + cftab[i] = unzftab[i - 1]; + for( i = 1; i <= 256; i++ ) + cftab[i] += cftab[i - 1]; + + for( i = 0; i <= last; i++ ) + { + ch = ( char )ll8[i]; + tt[cftab[ch]] = i; + cftab[ch]++; + } + cftab = null; + + tPos = tt[origPtr]; + + count = 0; + i2 = 0; + ch2 = 256; + /* + * not a char and not EOF + */ + if( blockRandomised ) + { + rNToGo = 0; + rTPos = 0; + setupRandPartA(); + } + else + { + setupNoRandPartA(); + } + } + + private void setupNoRandPartA() + { + if( i2 <= last ) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + i2++; + + currentChar = ch2; + currentState = NO_RAND_PART_B_STATE; + mCrc.updateCRC( ch2 ); + } + else + { + endBlock(); + initBlock(); + setupBlock(); + } + } + + private void setupNoRandPartB() + { + if( ch2 != chPrev ) + { + currentState = NO_RAND_PART_A_STATE; + count = 1; + setupNoRandPartA(); + } + else + { + count++; + if( count >= 4 ) + { + z = ll8[tPos]; + tPos = tt[tPos]; + currentState = NO_RAND_PART_C_STATE; + j2 = 0; + setupNoRandPartC(); + } + else + { + currentState = NO_RAND_PART_A_STATE; + setupNoRandPartA(); + } + } + } + + private void setupNoRandPartC() + { + if( j2 < ( int )z ) + { + currentChar = ch2; + mCrc.updateCRC( ch2 ); + j2++; + } + else + { + currentState = NO_RAND_PART_A_STATE; + i2++; + count = 0; + setupNoRandPartA(); + } + } + + private void setupRandPartA() + { + if( i2 <= last ) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + if( rNToGo == 0 ) + { + rNToGo = rNums[rTPos]; + rTPos++; + if( rTPos == 512 ) + rTPos = 0; + } + rNToGo--; + ch2 ^= ( int )( ( rNToGo == 1 ) ? 1 : 0 ); + i2++; + + currentChar = ch2; + currentState = RAND_PART_B_STATE; + mCrc.updateCRC( ch2 ); + } + else + { + endBlock(); + initBlock(); + setupBlock(); + } + } + + private void setupRandPartB() + { + if( ch2 != chPrev ) + { + currentState = RAND_PART_A_STATE; + count = 1; + setupRandPartA(); + } + else + { + count++; + if( count >= 4 ) + { + z = ll8[tPos]; + tPos = tt[tPos]; + if( rNToGo == 0 ) + { + rNToGo = rNums[rTPos]; + rTPos++; + if( rTPos == 512 ) + rTPos = 0; + } + rNToGo--; + z ^= ( ( rNToGo == 1 ) ? 1 : 0 ); + j2 = 0; + currentState = RAND_PART_C_STATE; + setupRandPartC(); + } + else + { + currentState = RAND_PART_A_STATE; + setupRandPartA(); + } + } + } + + private void setupRandPartC() + { + if( j2 < ( int )z ) + { + currentChar = ch2; + mCrc.updateCRC( ch2 ); + j2++; + } + else + { + currentState = RAND_PART_A_STATE; + i2++; + count = 0; + setupRandPartA(); + } + } + + private void getAndMoveToFrontDecode() + { + char yy[] = new char[256]; + int i; + int j; + int nextSym; + int limitLast; + int EOB; + int groupNo; + int groupPos; + + limitLast = baseBlockSize * blockSize100k; + origPtr = bsGetIntVS( 24 ); + + recvDecodingTables(); + EOB = nInUse + 1; + groupNo = -1; + groupPos = 0; + + /* + * Setting up the unzftab entries here is not strictly + * necessary, but it does save having to do it later + * in a separate pass, and so saves a block's worth of + * cache misses. + */ + for( i = 0; i <= 255; i++ ) + unzftab[i] = 0; + + for( i = 0; i <= 255; i++ ) + yy[i] = ( char )i; + + last = -1; + { + int zt; + int zn; + int zvec; + int zj; + if( groupPos == 0 ) + { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR( zn ); + while( zvec > limit[zt][zn] ) + { + zn++; + { + { + while( bsLive < 1 ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + if( thech == -1 ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + zj = ( bsBuff >> ( bsLive - 1 ) ) & 1; + bsLive--; + } + zvec = ( zvec << 1 ) | zj; + } + nextSym = perm[zt][zvec - base[zt][zn]]; + } + + while( true ) + { + + if( nextSym == EOB ) + break; + + if( nextSym == RUNA || nextSym == RUNB ) + { + char ch; + int s = -1; + int N = 1; + do + { + if( nextSym == RUNA ) + s = s + ( 0 + 1 ) * N; + else if( nextSym == RUNB ) + s = s + ( 1 + 1 ) * N; + N = N * 2; + { + int zt; + int zn; + int zvec; + int zj; + if( groupPos == 0 ) + { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR( zn ); + while( zvec > limit[zt][zn] ) + { + zn++; + { + { + while( bsLive < 1 ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + if( thech == -1 ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + zj = ( bsBuff >> ( bsLive - 1 ) ) & 1; + bsLive--; + } + zvec = ( zvec << 1 ) | zj; + } + ; + nextSym = perm[zt][zvec - base[zt][zn]]; + } + }while ( nextSym == RUNA || nextSym == RUNB ); + + s++; + ch = seqToUnseq[yy[0]]; + unzftab[ch] += s; + + while( s > 0 ) + { + last++; + ll8[last] = ch; + s--; + } + ; + + if( last >= limitLast ) + blockOverrun(); + continue; + } + else + { + char tmp; + last++; + if( last >= limitLast ) + blockOverrun(); + + tmp = yy[nextSym - 1]; + unzftab[seqToUnseq[tmp]]++; + ll8[last] = seqToUnseq[tmp]; + + /* + * This loop is hammered during decompression, + * hence the unrolling. + * for (j = nextSym-1; j > 0; j--) yy[j] = yy[j-1]; + */ + j = nextSym - 1; + for( ; j > 3; j -= 4 ) + { + yy[j] = yy[j - 1]; + yy[j - 1] = yy[j - 2]; + yy[j - 2] = yy[j - 3]; + yy[j - 3] = yy[j - 4]; + } + for( ; j > 0; j-- ) + yy[j] = yy[j - 1]; + + yy[0] = tmp; + { + int zt; + int zn; + int zvec; + int zj; + if( groupPos == 0 ) + { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR( zn ); + while( zvec > limit[zt][zn] ) + { + zn++; + { + { + while( bsLive < 1 ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + zj = ( bsBuff >> ( bsLive - 1 ) ) & 1; + bsLive--; + } + zvec = ( zvec << 1 ) | zj; + } + ; + nextSym = perm[zt][zvec - base[zt][zn]]; + } + continue; + } + } + } + + private void bsFinishedWithStream() + { + bsStream = null; + } + + private int bsGetInt32() + { + return ( int )bsGetint(); + } + + private int bsGetIntVS( int numBits ) + { + return ( int )bsR( numBits ); + } + + private char bsGetUChar() + { + return ( char )bsR( 8 ); + } + + private int bsGetint() + { + int u = 0; + u = ( u << 8 ) | bsR( 8 ); + u = ( u << 8 ) | bsR( 8 ); + u = ( u << 8 ) | bsR( 8 ); + u = ( u << 8 ) | bsR( 8 ); + return u; + } + + private int bsR( int n ) + { + int v; + { + while( bsLive < n ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + if( thech == -1 ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + + v = ( bsBuff >> ( bsLive - n ) ) & ( ( 1 << n ) - 1 ); + bsLive -= n; + return v; + } + + private void bsSetStream( InputStream f ) + { + bsStream = f; + bsLive = 0; + bsBuff = 0; + bytesOut = 0; + bytesIn = 0; + } + + private void complete() + { + storedCombinedCRC = bsGetInt32(); + if( storedCombinedCRC != computedCombinedCRC ) + crcError(); + + bsFinishedWithStream(); + streamEnd = true; + } + + private void endBlock() + { + computedBlockCRC = mCrc.getFinalCRC(); + /* + * A bad CRC is considered a fatal error. + */ + if( storedBlockCRC != computedBlockCRC ) + crcError(); + + computedCombinedCRC = ( computedCombinedCRC << 1 ) + | ( computedCombinedCRC >>> 31 ); + computedCombinedCRC ^= computedBlockCRC; + } + + private void hbCreateDecodeTables( int[] limit, int[] base, + int[] perm, char[] length, + int minLen, int maxLen, int alphaSize ) + { + int pp; + int i; + int j; + int vec; + + pp = 0; + for( i = minLen; i <= maxLen; i++ ) + for( j = 0; j < alphaSize; j++ ) + if( length[j] == i ) + { + perm[pp] = j; + pp++; + } + ; + + for( i = 0; i < MAX_CODE_LEN; i++ ) + base[i] = 0; + for( i = 0; i < alphaSize; i++ ) + base[length[i] + 1]++; + + for( i = 1; i < MAX_CODE_LEN; i++ ) + base[i] += base[i - 1]; + + for( i = 0; i < MAX_CODE_LEN; i++ ) + limit[i] = 0; + vec = 0; + + for( i = minLen; i <= maxLen; i++ ) + { + vec += ( base[i + 1] - base[i] ); + limit[i] = vec - 1; + vec <<= 1; + } + for( i = minLen + 1; i <= maxLen; i++ ) + base[i] = ( ( limit[i - 1] + 1 ) << 1 ) - base[i]; + } + + private void initBlock() + { + char magic1; + char magic2; + char magic3; + char magic4; + char magic5; + char magic6; + magic1 = bsGetUChar(); + magic2 = bsGetUChar(); + magic3 = bsGetUChar(); + magic4 = bsGetUChar(); + magic5 = bsGetUChar(); + magic6 = bsGetUChar(); + if( magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 + && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90 ) + { + complete(); + return; + } + + if( magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 + || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59 ) + { + badBlockHeader(); + streamEnd = true; + return; + } + + storedBlockCRC = bsGetInt32(); + + if( bsR( 1 ) == 1 ) + blockRandomised = true; + else + blockRandomised = false; + + // currBlockNo++; + getAndMoveToFrontDecode(); + + mCrc.initialiseCRC(); + currentState = START_BLOCK_STATE; + } + + private void initialize() + { + char magic3; + char magic4; + magic3 = bsGetUChar(); + magic4 = bsGetUChar(); + if( magic3 != 'h' || magic4 < '1' || magic4 > '9' ) + { + bsFinishedWithStream(); + streamEnd = true; + return; + } + + setDecompressStructureSizes( magic4 - '0' ); + computedCombinedCRC = 0; + } + + private void makeMaps() + { + int i; + nInUse = 0; + for( i = 0; i < 256; i++ ) + if( inUse[i] ) + { + seqToUnseq[nInUse] = ( char )i; + unseqToSeq[i] = ( char )nInUse; + nInUse++; + } + } + + private void recvDecodingTables() + { + char len[][] = new char[N_GROUPS][MAX_ALPHA_SIZE]; + int i; + int j; + int t; + int nGroups; + int nSelectors; + int alphaSize; + int minLen; + int maxLen; + boolean inUse16[] = new boolean[16]; + + /* + * Receive the mapping table + */ + for( i = 0; i < 16; i++ ) + if( bsR( 1 ) == 1 ) + inUse16[i] = true; + else + inUse16[i] = false; + + for( i = 0; i < 256; i++ ) + inUse[i] = false; + + for( i = 0; i < 16; i++ ) + if( inUse16[i] ) + for( j = 0; j < 16; j++ ) + if( bsR( 1 ) == 1 ) + inUse[i * 16 + j] = true; + + makeMaps(); + alphaSize = nInUse + 2; + + /* + * Now the selectors + */ + nGroups = bsR( 3 ); + nSelectors = bsR( 15 ); + for( i = 0; i < nSelectors; i++ ) + { + j = 0; + while( bsR( 1 ) == 1 ) + j++; + selectorMtf[i] = ( char )j; + } + { + /* + * Undo the MTF values for the selectors. + */ + char pos[] = new char[N_GROUPS]; + char tmp; + char v; + for( v = 0; v < nGroups; v++ ) + pos[v] = v; + + for( i = 0; i < nSelectors; i++ ) + { + v = selectorMtf[i]; + tmp = pos[v]; + while( v > 0 ) + { + pos[v] = pos[v - 1]; + v--; + } + pos[0] = tmp; + selector[i] = tmp; + } + } + + /* + * Now the coding tables + */ + for( t = 0; t < nGroups; t++ ) + { + int curr = bsR( 5 ); + for( i = 0; i < alphaSize; i++ ) + { + while( bsR( 1 ) == 1 ) + { + if( bsR( 1 ) == 0 ) + curr++; + else + curr--; + } + len[t][i] = ( char )curr; + } + } + + /* + * Create the Huffman decoding tables + */ + for( t = 0; t < nGroups; t++ ) + { + minLen = 32; + maxLen = 0; + for( i = 0; i < alphaSize; i++ ) + { + if( len[t][i] > maxLen ) + maxLen = len[t][i]; + if( len[t][i] < minLen ) + minLen = len[t][i]; + } + hbCreateDecodeTables( limit[t], base[t], perm[t], len[t], minLen, + maxLen, alphaSize ); + minLens[t] = minLen; + } + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2OutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2OutputStream.java new file mode 100644 index 000000000..7ee53781c --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/bzip2/CBZip2OutputStream.java @@ -0,0 +1,1807 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; +import java.io.*; + +/** + * An output stream that compresses into the BZip2 format (without the file + * header chars) into another stream. + * + * @author Keiron Liddle TODO: Update to + * BZip2 1.0.1 + */ +public class CBZip2OutputStream extends OutputStream implements BZip2Constants +{ + protected final static int SETMASK = ( 1 << 21 ); + protected final static int CLEARMASK = ( ~SETMASK ); + protected final static int GREATER_ICOST = 15; + protected final static int LESSER_ICOST = 0; + protected final static int SMALL_THRESH = 20; + protected final static int DEPTH_THRESH = 10; + + /* + * If you are ever unlucky/improbable enough + * to get a stack overflow whilst sorting, + * increase the following constant and try + * again. In practice I have never seen the + * stack go above 27 elems, so the following + * limit seems very generous. + */ + protected final static int QSORT_STACK_SIZE = 1000; + CRC mCrc = new CRC(); + + private boolean inUse[] = new boolean[256]; + + private char seqToUnseq[] = new char[256]; + private char unseqToSeq[] = new char[256]; + + private char selector[] = new char[MAX_SELECTORS]; + private char selectorMtf[] = new char[MAX_SELECTORS]; + + private int mtfFreq[] = new int[MAX_ALPHA_SIZE]; + + private int currentChar = -1; + private int runLength = 0; + + boolean closed = false; + + /* + * Knuth's increments seem to work better + * than Incerpi-Sedgewick here. Possibly + * because the number of elems to sort is + * usually small, typically <= 20. + */ + private int incs[] = {1, 4, 13, 40, 121, 364, 1093, 3280, + 9841, 29524, 88573, 265720, + 797161, 2391484}; + + boolean blockRandomised; + + /* + * always: in the range 0 .. 9. + * The current block size is 100000 * this number. + */ + int blockSize100k; + int bsBuff; + int bsLive; + + int bytesIn; + int bytesOut; + + /* + * index of the last char in the block, so + * the block size == last + 1. + */ + int last; + + /* + * index in zptr[] of original string after sorting. + */ + int origPtr; + + private int allowableBlockSize; + + private char block[]; + + private int blockCRC, combinedCRC; + + private OutputStream bsStream; + private boolean firstAttempt; + private int ftab[]; + private int nBlocksRandomised; + private int nInUse; + + private int nMTF; + private int quadrant[]; + private short szptr[]; + private int workDone; + + /* + * Used when sorting. If too many long comparisons + * happen, we stop sorting, randomise the block + * slightly, and try again. + */ + private int workFactor; + private int workLimit; + private int zptr[]; + + public CBZip2OutputStream( OutputStream inStream ) + throws IOException + { + this( inStream, 9 ); + } + + public CBZip2OutputStream( OutputStream inStream, int inBlockSize ) + throws IOException + { + block = null; + quadrant = null; + zptr = null; + ftab = null; + + bsSetStream( inStream ); + + workFactor = 50; + if( inBlockSize > 9 ) + { + inBlockSize = 9; + } + if( inBlockSize < 1 ) + { + inBlockSize = 1; + } + blockSize100k = inBlockSize; + allocateCompressStructures(); + initialize(); + initBlock(); + } + + protected static void hbMakeCodeLengths( char[] len, int[] freq, + int alphaSize, int maxLen ) + { + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int nNodes; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int nHeap; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int n1; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int n2; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int i; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int j; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int k; + boolean tooLong; + + int heap[] = new int[MAX_ALPHA_SIZE + 2]; + int weight[] = new int[MAX_ALPHA_SIZE * 2]; + int parent[] = new int[MAX_ALPHA_SIZE * 2]; + + for( i = 0; i < alphaSize; i++ ) + weight[i + 1] = ( freq[i] == 0 ? 1 : freq[i] ) << 8; + + while( true ) + { + nNodes = alphaSize; + nHeap = 0; + + heap[0] = 0; + weight[0] = 0; + parent[0] = -2; + + for( i = 1; i <= alphaSize; i++ ) + { + parent[i] = -1; + nHeap++; + heap[nHeap] = i; + { + int zz; + int tmp; + zz = nHeap; + tmp = heap[zz]; + while( weight[tmp] < weight[heap[zz >> 1]] ) + { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + } + if( !( nHeap < ( MAX_ALPHA_SIZE + 2 ) ) ) + panic(); + + while( nHeap > 1 ) + { + n1 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + { + int zz = 0; + int yy = 0; + int tmp = 0; + zz = 1; + tmp = heap[zz]; + while( true ) + { + yy = zz << 1; + if( yy > nHeap ) + break; + if( yy < nHeap && + weight[heap[yy + 1]] < weight[heap[yy]] ) + yy++; + if( weight[tmp] < weight[heap[yy]] ) + break; + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + } + n2 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + { + int zz = 0; + int yy = 0; + int tmp = 0; + zz = 1; + tmp = heap[zz]; + while( true ) + { + yy = zz << 1; + if( yy > nHeap ) + break; + if( yy < nHeap && + weight[heap[yy + 1]] < weight[heap[yy]] ) + yy++; + if( weight[tmp] < weight[heap[yy]] ) + break; + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + } + nNodes++; + parent[n1] = parent[n2] = nNodes; + + weight[nNodes] = ( ( weight[n1] & 0xffffff00 ) + + ( weight[n2] & 0xffffff00 ) ) + | ( 1 + ( ( ( weight[n1] & 0x000000ff ) > + ( weight[n2] & 0x000000ff ) ) ? + ( weight[n1] & 0x000000ff ) : + ( weight[n2] & 0x000000ff ) ) ); + + parent[nNodes] = -1; + nHeap++; + heap[nHeap] = nNodes; + { + int zz = 0; + int tmp = 0; + zz = nHeap; + tmp = heap[zz]; + while( weight[tmp] < weight[heap[zz >> 1]] ) + { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + } + if( !( nNodes < ( MAX_ALPHA_SIZE * 2 ) ) ) + panic(); + + tooLong = false; + for( i = 1; i <= alphaSize; i++ ) + { + j = 0; + k = i; + while( parent[k] >= 0 ) + { + k = parent[k]; + j++; + } + len[i - 1] = ( char )j; + if( j > maxLen ) + tooLong = true; + } + + if( !tooLong ) + break; + + for( i = 1; i < alphaSize; i++ ) + { + j = weight[i] >> 8; + j = 1 + ( j / 2 ); + weight[i] = j << 8; + } + } + } + + private static void panic() + { + System.out.println( "panic" ); + //throw new CError(); + } + + public void close() + throws IOException + { + if( closed ) + return; + + if( runLength > 0 ) + writeRun(); + currentChar = -1; + endBlock(); + endCompression(); + closed = true; + super.close(); + bsStream.close(); + } + + public void finalize() + throws Throwable + { + close(); + } + + public void flush() + throws IOException + { + super.flush(); + bsStream.flush(); + } + + /** + * modified by Oliver Merkel, 010128 + * + * @param bv Description of Parameter + * @exception IOException Description of Exception + */ + public void write( int bv ) + throws IOException + { + int b = ( 256 + bv ) % 256; + if( currentChar != -1 ) + { + if( currentChar == b ) + { + runLength++; + if( runLength > 254 ) + { + writeRun(); + currentChar = -1; + runLength = 0; + } + } + else + { + writeRun(); + runLength = 1; + currentChar = b; + } + } + else + { + currentChar = b; + runLength++; + } + } + + private void allocateCompressStructures() + { + int n = baseBlockSize * blockSize100k; + block = new char[( n + 1 + NUM_OVERSHOOT_BYTES )]; + quadrant = new int[( n + NUM_OVERSHOOT_BYTES )]; + zptr = new int[n]; + ftab = new int[65537]; + + if( block == null || quadrant == null || zptr == null + || ftab == null ) + { + //int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537; + //compressOutOfMemory ( totalDraw, n ); + } + + /* + * The back end needs a place to store the MTF values + * whilst it calculates the coding tables. We could + * put them in the zptr array. However, these values + * will fit in a short, so we overlay szptr at the + * start of zptr, in the hope of reducing the number + * of cache misses induced by the multiple traversals + * of the MTF values when calculating coding tables. + * Seems to improve compression speed by about 1%. + */ + // szptr = zptr; + + szptr = new short[2 * n]; + } + + private void bsFinishedWithStream() + throws IOException + { + while( bsLive > 0 ) + { + int ch = ( bsBuff >> 24 ); + try + { + bsStream.write( ch );// write 8-bit + } + catch( IOException e ) + { + throw e; + } + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + } + + private void bsPutIntVS( int numBits, int c ) + throws IOException + { + bsW( numBits, c ); + } + + private void bsPutUChar( int c ) + throws IOException + { + bsW( 8, c ); + } + + private void bsPutint( int u ) + throws IOException + { + bsW( 8, ( u >> 24 ) & 0xff ); + bsW( 8, ( u >> 16 ) & 0xff ); + bsW( 8, ( u >> 8 ) & 0xff ); + bsW( 8, u & 0xff ); + } + + private void bsSetStream( OutputStream f ) + { + bsStream = f; + bsLive = 0; + bsBuff = 0; + bytesOut = 0; + bytesIn = 0; + } + + private void bsW( int n, int v ) + throws IOException + { + while( bsLive >= 8 ) + { + int ch = ( bsBuff >> 24 ); + try + { + bsStream.write( ch );// write 8-bit + } + catch( IOException e ) + { + throw e; + } + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + bsBuff |= ( v << ( 32 - bsLive - n ) ); + bsLive += n; + } + + private void doReversibleTransformation() + { + int i; + + workLimit = workFactor * last; + workDone = 0; + blockRandomised = false; + firstAttempt = true; + + mainSort(); + + if( workDone > workLimit && firstAttempt ) + { + randomiseBlock(); + workLimit = workDone = 0; + blockRandomised = true; + firstAttempt = false; + mainSort(); + } + + origPtr = -1; + for( i = 0; i <= last; i++ ) + if( zptr[i] == 0 ) + { + origPtr = i; + break; + } + ; + + if( origPtr == -1 ) + panic(); + } + + private void endBlock() + throws IOException + { + blockCRC = mCrc.getFinalCRC(); + combinedCRC = ( combinedCRC << 1 ) | ( combinedCRC >>> 31 ); + combinedCRC ^= blockCRC; + + /* + * sort the block and establish posn of original string + */ + doReversibleTransformation(); + + /* + * A 6-byte block header, the value chosen arbitrarily + * as 0x314159265359 :-). A 32 bit value does not really + * give a strong enough guarantee that the value will not + * appear by chance in the compressed datastream. Worst-case + * probability of this event, for a 900k block, is about + * 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits. + * For a compressed file of size 100Gb -- about 100000 blocks -- + * only a 48-bit marker will do. NB: normal compression/ + * decompression do *not* rely on these statistical properties. + * They are only important when trying to recover blocks from + * damaged files. + */ + bsPutUChar( 0x31 ); + bsPutUChar( 0x41 ); + bsPutUChar( 0x59 ); + bsPutUChar( 0x26 ); + bsPutUChar( 0x53 ); + bsPutUChar( 0x59 ); + + /* + * Now the block's CRC, so it is in a known place. + */ + bsPutint( blockCRC ); + + /* + * Now a single bit indicating randomisation. + */ + if( blockRandomised ) + { + bsW( 1, 1 ); + nBlocksRandomised++; + } + else + { + bsW( 1, 0 ); + } + + /* + * Finally, block's contents proper. + */ + moveToFrontCodeAndSend(); + } + + private void endCompression() + throws IOException + { + /* + * Now another magic 48-bit number, 0x177245385090, to + * indicate the end of the last block. (sqrt(pi), if + * you want to know. I did want to use e, but it contains + * too much repetition -- 27 18 28 18 28 46 -- for me + * to feel statistically comfortable. Call me paranoid.) + */ + bsPutUChar( 0x17 ); + bsPutUChar( 0x72 ); + bsPutUChar( 0x45 ); + bsPutUChar( 0x38 ); + bsPutUChar( 0x50 ); + bsPutUChar( 0x90 ); + + bsPutint( combinedCRC ); + + bsFinishedWithStream(); + } + + private boolean fullGtU( int i1, int i2 ) + { + int k; + char c1; + char c2; + int s1; + int s2; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + k = last + 1; + + do + { + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + if( i1 > last ) + { + i1 -= last; + i1--; + } + ; + if( i2 > last ) + { + i2 -= last; + i2--; + } + ; + + k -= 4; + workDone++; + }while ( k >= 0 ); + + return false; + } + + private void generateMTFValues() + { + char yy[] = new char[256]; + int i; + int j; + char tmp; + char tmp2; + int zPend; + int wr; + int EOB; + + makeMaps(); + EOB = nInUse + 1; + + for( i = 0; i <= EOB; i++ ) + mtfFreq[i] = 0; + + wr = 0; + zPend = 0; + for( i = 0; i < nInUse; i++ ) + yy[i] = ( char )i; + + for( i = 0; i <= last; i++ ) + { + char ll_i; + + ll_i = unseqToSeq[block[zptr[i]]]; + + j = 0; + tmp = yy[j]; + while( ll_i != tmp ) + { + j++; + tmp2 = tmp; + tmp = yy[j]; + yy[j] = tmp2; + } + ; + yy[0] = tmp; + + if( j == 0 ) + { + zPend++; + } + else + { + if( zPend > 0 ) + { + zPend--; + while( true ) + { + switch ( zPend % 2 ) + { + case 0: + szptr[wr] = ( short )RUNA; + wr++; + mtfFreq[RUNA]++; + break; + case 1: + szptr[wr] = ( short )RUNB; + wr++; + mtfFreq[RUNB]++; + break; + } + ; + if( zPend < 2 ) + break; + zPend = ( zPend - 2 ) / 2; + } + ; + zPend = 0; + } + szptr[wr] = ( short )( j + 1 ); + wr++; + mtfFreq[j + 1]++; + } + } + + if( zPend > 0 ) + { + zPend--; + while( true ) + { + switch ( zPend % 2 ) + { + case 0: + szptr[wr] = ( short )RUNA; + wr++; + mtfFreq[RUNA]++; + break; + case 1: + szptr[wr] = ( short )RUNB; + wr++; + mtfFreq[RUNB]++; + break; + } + if( zPend < 2 ) + break; + zPend = ( zPend - 2 ) / 2; + } + } + + szptr[wr] = ( short )EOB; + wr++; + mtfFreq[EOB]++; + + nMTF = wr; + } + + private void hbAssignCodes( int[] code, char[] length, int minLen, + int maxLen, int alphaSize ) + { + int n; + int vec; + int i; + + vec = 0; + for( n = minLen; n <= maxLen; n++ ) + { + for( i = 0; i < alphaSize; i++ ) + if( length[i] == n ) + { + code[i] = vec; + vec++; + } + ; + vec <<= 1; + } + } + + private void initBlock() + { + // blockNo++; + mCrc.initialiseCRC(); + last = -1; + // ch = 0; + + for( int i = 0; i < 256; i++ ) + inUse[i] = false; + + /* + * 20 is just a paranoia constant + */ + allowableBlockSize = baseBlockSize * blockSize100k - 20; + } + + private void initialize() + throws IOException + { + bytesIn = 0; + bytesOut = 0; + nBlocksRandomised = 0; + + /* + * Write `magic' bytes h indicating file-format == huffmanised, + * followed by a digit indicating blockSize100k. + */ + bsPutUChar( 'h' ); + bsPutUChar( '0' + blockSize100k ); + + combinedCRC = 0; + } + + private void mainSort() + { + int i; + int j; + int ss; + int sb; + int runningOrder[] = new int[256]; + int copy[] = new int[256]; + boolean bigDone[] = new boolean[256]; + int c1; + int c2; + int numQSorted; + + /* + * In the various block-sized structures, live data runs + * from 0 to last+NUM_OVERSHOOT_BYTES inclusive. First, + * set up the overshoot area for block. + */ + // if (verbosity >= 4) fprintf ( stderr, " sort initialise ...\n" ); + for( i = 0; i < NUM_OVERSHOOT_BYTES; i++ ) + block[last + i + 2] = block[( i % ( last + 1 ) ) + 1]; + for( i = 0; i <= last + NUM_OVERSHOOT_BYTES; i++ ) + quadrant[i] = 0; + + block[0] = ( char )( block[last + 1] ); + + if( last < 4000 ) + { + /* + * Use simpleSort(), since the full sorting mechanism + * has quite a large constant overhead. + */ + for( i = 0; i <= last; i++ ) + zptr[i] = i; + firstAttempt = false; + workDone = workLimit = 0; + simpleSort( 0, last, 0 ); + } + else + { + numQSorted = 0; + for( i = 0; i <= 255; i++ ) + bigDone[i] = false; + + for( i = 0; i <= 65536; i++ ) + ftab[i] = 0; + + c1 = block[0]; + for( i = 0; i <= last; i++ ) + { + c2 = block[i + 1]; + ftab[( c1 << 8 ) + c2]++; + c1 = c2; + } + + for( i = 1; i <= 65536; i++ ) + ftab[i] += ftab[i - 1]; + + c1 = block[1]; + for( i = 0; i < last; i++ ) + { + c2 = block[i + 2]; + j = ( c1 << 8 ) + c2; + c1 = c2; + ftab[j]--; + zptr[ftab[j]] = i; + } + + j = ( ( block[last + 1] ) << 8 ) + ( block[1] ); + ftab[j]--; + zptr[ftab[j]] = last; + + /* + * Now ftab contains the first loc of every small bucket. + * Calculate the running order, from smallest to largest + * big bucket. + */ + for( i = 0; i <= 255; i++ ) + runningOrder[i] = i; + { + int vv; + int h = 1; + do + h = 3 * h + 1; +while ( h <= 256 ); + do + { + h = h / 3; + for( i = h; i <= 255; i++ ) + { + vv = runningOrder[i]; + j = i; + while( ( ftab[( ( runningOrder[j - h] ) + 1 ) << 8] + - ftab[( runningOrder[j - h] ) << 8] ) > + ( ftab[( ( vv ) + 1 ) << 8] - ftab[( vv ) << 8] ) ) + { + runningOrder[j] = runningOrder[j - h]; + j = j - h; + if( j <= ( h - 1 ) ) + break; + } + runningOrder[j] = vv; + } + }while ( h != 1 ); + } + + /* + * The main sorting loop. + */ + for( i = 0; i <= 255; i++ ) + { + + /* + * Process big buckets, starting with the least full. + */ + ss = runningOrder[i]; + + /* + * Complete the big bucket [ss] by quicksorting + * any unsorted small buckets [ss, j]. Hopefully + * previous pointer-scanning phases have already + * completed many of the small buckets [ss, j], so + * we don't have to sort them at all. + */ + for( j = 0; j <= 255; j++ ) + { + sb = ( ss << 8 ) + j; + if( !( ( ftab[sb] & SETMASK ) == SETMASK ) ) + { + int lo = ftab[sb] & CLEARMASK; + int hi = ( ftab[sb + 1] & CLEARMASK ) - 1; + if( hi > lo ) + { + qSort3( lo, hi, 2 ); + numQSorted += ( hi - lo + 1 ); + if( workDone > workLimit && firstAttempt ) + return; + } + ftab[sb] |= SETMASK; + } + } + + /* + * The ss big bucket is now done. Record this fact, + * and update the quadrant descriptors. Remember to + * update quadrants in the overshoot area too, if + * necessary. The "if (i < 255)" test merely skips + * this updating for the last bucket processed, since + * updating for the last bucket is pointless. + */ + bigDone[ss] = true; + + if( i < 255 ) + { + int bbStart = ftab[ss << 8] & CLEARMASK; + int bbSize = ( ftab[( ss + 1 ) << 8] & CLEARMASK ) - bbStart; + int shifts = 0; + + while( ( bbSize >> shifts ) > 65534 ) + shifts++; + + for( j = 0; j < bbSize; j++ ) + { + int a2update = zptr[bbStart + j]; + int qVal = ( j >> shifts ); + quadrant[a2update] = qVal; + if( a2update < NUM_OVERSHOOT_BYTES ) + quadrant[a2update + last + 1] = qVal; + } + + if( !( ( ( bbSize - 1 ) >> shifts ) <= 65535 ) ) + panic(); + } + + /* + * Now scan this big bucket so as to synthesise the + * sorted order for small buckets [t, ss] for all t != ss. + */ + for( j = 0; j <= 255; j++ ) + copy[j] = ftab[( j << 8 ) + ss] & CLEARMASK; + + for( j = ftab[ss << 8] & CLEARMASK; + j < ( ftab[( ss + 1 ) << 8] & CLEARMASK ); j++ ) + { + c1 = block[zptr[j]]; + if( !bigDone[c1] ) + { + zptr[copy[c1]] = zptr[j] == 0 ? last : zptr[j] - 1; + copy[c1]++; + } + } + + for( j = 0; j <= 255; j++ ) + ftab[( j << 8 ) + ss] |= SETMASK; + } + } + } + + private void makeMaps() + { + int i; + nInUse = 0; + for( i = 0; i < 256; i++ ) + if( inUse[i] ) + { + seqToUnseq[nInUse] = ( char )i; + unseqToSeq[i] = ( char )nInUse; + nInUse++; + } + } + + private char med3( char a, char b, char c ) + { + char t; + if( a > b ) + { + t = a; + a = b; + b = t; + } + if( b > c ) + { + t = b; + b = c; + c = t; + } + if( a > b ) + b = a; + return b; + } + + private void moveToFrontCodeAndSend() + throws IOException + { + bsPutIntVS( 24, origPtr ); + generateMTFValues(); + sendMTFValues(); + } + + private void qSort3( int loSt, int hiSt, int dSt ) + { + int unLo; + int unHi; + int ltLo; + int gtHi; + int med; + int n; + int m; + int sp; + int lo; + int hi; + int d; + StackElem[] stack = new StackElem[QSORT_STACK_SIZE]; + for( int count = 0; count < QSORT_STACK_SIZE; count++ ) + stack[count] = new StackElem(); + + sp = 0; + + stack[sp].ll = loSt; + stack[sp].hh = hiSt; + stack[sp].dd = dSt; + sp++; + + while( sp > 0 ) + { + if( sp >= QSORT_STACK_SIZE ) + panic(); + + sp--; + lo = stack[sp].ll; + hi = stack[sp].hh; + d = stack[sp].dd; + + if( hi - lo < SMALL_THRESH || d > DEPTH_THRESH ) + { + simpleSort( lo, hi, d ); + if( workDone > workLimit && firstAttempt ) + return; + continue; + } + + med = med3( block[zptr[lo] + d + 1], + block[zptr[hi] + d + 1], + block[zptr[( lo + hi ) >> 1] + d + 1] ); + + unLo = ltLo = lo; + unHi = gtHi = hi; + + while( true ) + { + while( true ) + { + if( unLo > unHi ) + break; + n = ( ( int )block[zptr[unLo] + d + 1] ) - med; + if( n == 0 ) + { + int temp = 0; + temp = zptr[unLo]; + zptr[unLo] = zptr[ltLo]; + zptr[ltLo] = temp; + ltLo++; + unLo++; + continue; + } + ; + if( n > 0 ) + break; + unLo++; + } + while( true ) + { + if( unLo > unHi ) + break; + n = ( ( int )block[zptr[unHi] + d + 1] ) - med; + if( n == 0 ) + { + int temp = 0; + temp = zptr[unHi]; + zptr[unHi] = zptr[gtHi]; + zptr[gtHi] = temp; + gtHi--; + unHi--; + continue; + } + ; + if( n < 0 ) + break; + unHi--; + } + if( unLo > unHi ) + break; + int temp = 0; + temp = zptr[unLo]; + zptr[unLo] = zptr[unHi]; + zptr[unHi] = temp; + unLo++; + unHi--; + } + + if( gtHi < ltLo ) + { + stack[sp].ll = lo; + stack[sp].hh = hi; + stack[sp].dd = d + 1; + sp++; + continue; + } + + n = ( ( ltLo - lo ) < ( unLo - ltLo ) ) ? ( ltLo - lo ) : ( unLo - ltLo ); + vswap( lo, unLo - n, n ); + m = ( ( hi - gtHi ) < ( gtHi - unHi ) ) ? ( hi - gtHi ) : ( gtHi - unHi ); + vswap( unLo, hi - m + 1, m ); + + n = lo + unLo - ltLo - 1; + m = hi - ( gtHi - unHi ) + 1; + + stack[sp].ll = lo; + stack[sp].hh = n; + stack[sp].dd = d; + sp++; + + stack[sp].ll = n + 1; + stack[sp].hh = m - 1; + stack[sp].dd = d + 1; + sp++; + + stack[sp].ll = m; + stack[sp].hh = hi; + stack[sp].dd = d; + sp++; + } + } + + private void randomiseBlock() + { + int i; + int rNToGo = 0; + int rTPos = 0; + for( i = 0; i < 256; i++ ) + inUse[i] = false; + + for( i = 0; i <= last; i++ ) + { + if( rNToGo == 0 ) + { + rNToGo = ( char )rNums[rTPos]; + rTPos++; + if( rTPos == 512 ) + rTPos = 0; + } + rNToGo--; + block[i + 1] ^= ( ( rNToGo == 1 ) ? 1 : 0 ); + // handle 16 bit signed numbers + block[i + 1] &= 0xFF; + + inUse[block[i + 1]] = true; + } + } + + private void sendMTFValues() + throws IOException + { + char len[][] = new char[N_GROUPS][MAX_ALPHA_SIZE]; + + int v; + + int t; + + int i; + + int j; + + int gs; + + int ge; + + int totc; + + int bt; + + int bc; + + int iter; + int nSelectors = 0; + int alphaSize; + int minLen; + int maxLen; + int selCtr; + int nGroups; + int nBytes; + + alphaSize = nInUse + 2; + for( t = 0; t < N_GROUPS; t++ ) + for( v = 0; v < alphaSize; v++ ) + len[t][v] = ( char )GREATER_ICOST; + + /* + * Decide how many coding tables to use + */ + if( nMTF <= 0 ) + panic(); + + if( nMTF < 200 ) + nGroups = 2; + else if( nMTF < 600 ) + nGroups = 3; + else if( nMTF < 1200 ) + nGroups = 4; + else if( nMTF < 2400 ) + nGroups = 5; + else + nGroups = 6; + { + /* + * Generate an initial set of coding tables + */ + int nPart; + int remF; + int tFreq; + int aFreq; + + nPart = nGroups; + remF = nMTF; + gs = 0; + while( nPart > 0 ) + { + tFreq = remF / nPart; + ge = gs - 1; + aFreq = 0; + while( aFreq < tFreq && ge < alphaSize - 1 ) + { + ge++; + aFreq += mtfFreq[ge]; + } + + if( ge > gs && nPart != nGroups && nPart != 1 + && ( ( nGroups - nPart ) % 2 == 1 ) ) + { + aFreq -= mtfFreq[ge]; + ge--; + } + + for( v = 0; v < alphaSize; v++ ) + if( v >= gs && v <= ge ) + len[nPart - 1][v] = ( char )LESSER_ICOST; + else + len[nPart - 1][v] = ( char )GREATER_ICOST; + + nPart--; + gs = ge + 1; + remF -= aFreq; + } + } + + int rfreq[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + int fave[] = new int[N_GROUPS]; + short cost[] = new short[N_GROUPS]; + /* + * Iterate up to N_ITERS times to improve the tables. + */ + for( iter = 0; iter < N_ITERS; iter++ ) + { + for( t = 0; t < nGroups; t++ ) + fave[t] = 0; + + for( t = 0; t < nGroups; t++ ) + for( v = 0; v < alphaSize; v++ ) + rfreq[t][v] = 0; + + nSelectors = 0; + totc = 0; + gs = 0; + while( true ) + { + + /* + * Set group start & end marks. + */ + if( gs >= nMTF ) + break; + ge = gs + G_SIZE - 1; + if( ge >= nMTF ) + ge = nMTF - 1; + + /* + * Calculate the cost of this group as coded + * by each of the coding tables. + */ + for( t = 0; t < nGroups; t++ ) + cost[t] = 0; + + if( nGroups == 6 ) + { + short cost0; + short cost1; + short cost2; + short cost3; + short cost4; + short cost5; + cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0; + for( i = gs; i <= ge; i++ ) + { + short icv = szptr[i]; + cost0 += len[0][icv]; + cost1 += len[1][icv]; + cost2 += len[2][icv]; + cost3 += len[3][icv]; + cost4 += len[4][icv]; + cost5 += len[5][icv]; + } + cost[0] = cost0; + cost[1] = cost1; + cost[2] = cost2; + cost[3] = cost3; + cost[4] = cost4; + cost[5] = cost5; + } + else + { + for( i = gs; i <= ge; i++ ) + { + short icv = szptr[i]; + for( t = 0; t < nGroups; t++ ) + cost[t] += len[t][icv]; + } + } + + /* + * Find the coding table which is best for this group, + * and record its identity in the selector table. + */ + bc = 999999999; + bt = -1; + for( t = 0; t < nGroups; t++ ) + if( cost[t] < bc ) + { + bc = cost[t]; + bt = t; + } + ; + totc += bc; + fave[bt]++; + selector[nSelectors] = ( char )bt; + nSelectors++; + + /* + * Increment the symbol frequencies for the selected table. + */ + for( i = gs; i <= ge; i++ ) + rfreq[bt][szptr[i]]++; + + gs = ge + 1; + } + + /* + * Recompute the tables based on the accumulated frequencies. + */ + for( t = 0; t < nGroups; t++ ) + hbMakeCodeLengths( len[t], rfreq[t], alphaSize, 20 ); + } + + rfreq = null; + fave = null; + cost = null; + + if( !( nGroups < 8 ) ) + panic(); + if( !( nSelectors < 32768 && nSelectors <= ( 2 + ( 900000 / G_SIZE ) ) ) ) + panic(); + { + /* + * Compute MTF values for the selectors. + */ + char pos[] = new char[N_GROUPS]; + char ll_i; + char tmp2; + char tmp; + for( i = 0; i < nGroups; i++ ) + pos[i] = ( char )i; + for( i = 0; i < nSelectors; i++ ) + { + ll_i = selector[i]; + j = 0; + tmp = pos[j]; + while( ll_i != tmp ) + { + j++; + tmp2 = tmp; + tmp = pos[j]; + pos[j] = tmp2; + } + pos[0] = tmp; + selectorMtf[i] = ( char )j; + } + } + + int code[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + + /* + * Assign actual codes for the tables. + */ + for( t = 0; t < nGroups; t++ ) + { + minLen = 32; + maxLen = 0; + for( i = 0; i < alphaSize; i++ ) + { + if( len[t][i] > maxLen ) + maxLen = len[t][i]; + if( len[t][i] < minLen ) + minLen = len[t][i]; + } + if( maxLen > 20 ) + panic(); + if( minLen < 1 ) + panic(); + hbAssignCodes( code[t], len[t], minLen, maxLen, alphaSize ); + } + { + /* + * Transmit the mapping table. + */ + boolean inUse16[] = new boolean[16]; + for( i = 0; i < 16; i++ ) + { + inUse16[i] = false; + for( j = 0; j < 16; j++ ) + if( inUse[i * 16 + j] ) + inUse16[i] = true; + } + + nBytes = bytesOut; + for( i = 0; i < 16; i++ ) + if( inUse16[i] ) + bsW( 1, 1 ); + else + bsW( 1, 0 ); + + for( i = 0; i < 16; i++ ) + if( inUse16[i] ) + for( j = 0; j < 16; j++ ) + if( inUse[i * 16 + j] ) + bsW( 1, 1 ); + else + bsW( 1, 0 ); + + } + + /* + * Now the selectors. + */ + nBytes = bytesOut; + bsW( 3, nGroups ); + bsW( 15, nSelectors ); + for( i = 0; i < nSelectors; i++ ) + { + for( j = 0; j < selectorMtf[i]; j++ ) + bsW( 1, 1 ); + bsW( 1, 0 ); + } + + /* + * Now the coding tables. + */ + nBytes = bytesOut; + + for( t = 0; t < nGroups; t++ ) + { + int curr = len[t][0]; + bsW( 5, curr ); + for( i = 0; i < alphaSize; i++ ) + { + while( curr < len[t][i] ) + { + bsW( 2, 2 ); + curr++; + /* + * 10 + */ + } + while( curr > len[t][i] ) + { + bsW( 2, 3 ); + curr--; + /* + * 11 + */ + } + bsW( 1, 0 ); + } + } + + /* + * And finally, the block data proper + */ + nBytes = bytesOut; + selCtr = 0; + gs = 0; + while( true ) + { + if( gs >= nMTF ) + break; + ge = gs + G_SIZE - 1; + if( ge >= nMTF ) + ge = nMTF - 1; + for( i = gs; i <= ge; i++ ) + { + bsW( len[selector[selCtr]][szptr[i]], + code[selector[selCtr]][szptr[i]] ); + } + + gs = ge + 1; + selCtr++; + } + if( !( selCtr == nSelectors ) ) + panic(); + } + + private void simpleSort( int lo, int hi, int d ) + { + int i; + int j; + int h; + int bigN; + int hp; + int v; + + bigN = hi - lo + 1; + if( bigN < 2 ) + return; + + hp = 0; + while( incs[hp] < bigN ) + hp++; + hp--; + + for( ; hp >= 0; hp-- ) + { + h = incs[hp]; + + i = lo + h; + while( true ) + { + /* + * copy 1 + */ + if( i > hi ) + break; + v = zptr[i]; + j = i; + while( fullGtU( zptr[j - h] + d, v + d ) ) + { + zptr[j] = zptr[j - h]; + j = j - h; + if( j <= ( lo + h - 1 ) ) + break; + } + zptr[j] = v; + i++; + + /* + * copy 2 + */ + if( i > hi ) + break; + v = zptr[i]; + j = i; + while( fullGtU( zptr[j - h] + d, v + d ) ) + { + zptr[j] = zptr[j - h]; + j = j - h; + if( j <= ( lo + h - 1 ) ) + break; + } + zptr[j] = v; + i++; + + /* + * copy 3 + */ + if( i > hi ) + break; + v = zptr[i]; + j = i; + while( fullGtU( zptr[j - h] + d, v + d ) ) + { + zptr[j] = zptr[j - h]; + j = j - h; + if( j <= ( lo + h - 1 ) ) + break; + } + zptr[j] = v; + i++; + + if( workDone > workLimit && firstAttempt ) + return; + } + } + } + + private void vswap( int p1, int p2, int n ) + { + int temp = 0; + while( n > 0 ) + { + temp = zptr[p1]; + zptr[p1] = zptr[p2]; + zptr[p2] = temp; + p1++; + p2++; + n--; + } + } + + private void writeRun() + throws IOException + { + if( last < allowableBlockSize ) + { + inUse[currentChar] = true; + for( int i = 0; i < runLength; i++ ) + { + mCrc.updateCRC( ( char )currentChar ); + } + switch ( runLength ) + { + case 1: + last++; + block[last + 1] = ( char )currentChar; + break; + case 2: + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + break; + case 3: + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + break; + default: + inUse[runLength - 4] = true; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )( runLength - 4 ); + break; + } + } + else + { + endBlock(); + initBlock(); + writeRun(); + } + } + + private class StackElem + { + int dd; + int hh; + int ll; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/bzip2/CRC.java b/proposal/myrmidon/src/main/org/apache/tools/bzip2/CRC.java new file mode 100644 index 000000000..f12b482a6 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/bzip2/CRC.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; + +/** + * A simple class the hold and calculate the CRC for sanity checking of the + * data. + * + * @author Keiron Liddle + */ +class CRC +{ + public static int crc32Table[] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, + 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, + 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, + 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, + 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, + 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, + 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, + 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, + 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, + 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, + 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, + 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, + 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, + 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, + 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, + 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, + 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, + 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 + }; + + int globalCrc; + + public CRC() + { + initialiseCRC(); + } + + void setGlobalCRC( int newCrc ) + { + globalCrc = newCrc; + } + + int getFinalCRC() + { + return ~globalCrc; + } + + int getGlobalCRC() + { + return globalCrc; + } + + void initialiseCRC() + { + globalCrc = 0xffffffff; + } + + void updateCRC( int inCh ) + { + int temp = ( globalCrc >> 24 ) ^ inCh; + if( temp < 0 ) + temp = 256 + temp; + globalCrc = ( globalCrc << 8 ) ^ CRC.crc32Table[temp]; + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/mail/MailMessage.java b/proposal/myrmidon/src/main/org/apache/tools/mail/MailMessage.java new file mode 100644 index 000000000..d8520d6c6 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/mail/MailMessage.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.mail; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.InetAddress; +import java.net.Socket; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +/** + * A class to help send SMTP email. This class is an improvement on the + * sun.net.smtp.SmtpClient class found in the JDK. This version has extra + * functionality, and can be used with JVMs that did not extend from the JDK. + * It's not as robust as the JavaMail Standard Extension classes, but it's + * easier to use and easier to install, and has an Open Source license.

          + * + * It can be used like this:

          + * String mailhost = "localhost";  // or another mail host
          + * String from = "Mail Message Servlet <MailMessage@server.com>";
          + * String to = "to@you.com";
          + * String cc1 = "cc1@you.com";
          + * String cc2 = "cc2@you.com";
          + * String bcc = "bcc@you.com";
          + *  
          + * MailMessage msg = new MailMessage(mailhost);
          + * msg.setPort(25);
          + * msg.from(from);
          + * msg.to(to);
          + * msg.cc(cc1);
          + * msg.cc(cc2);
          + * msg.bcc(bcc);
          + * msg.setSubject("Test subject");
          + * PrintStream out = msg.getPrintStream();
          + *  
          + * Enumeration enum = req.getParameterNames();
          + * while (enum.hasMoreElements()) {
          + *   String name = (String)enum.nextElement();
          + *   String value = req.getParameter(name);
          + *   out.println(name + " = " + value);
          + * }
          + *  
          + * msg.sendAndClose();
          + * 

          + * + * Be sure to set the from address, then set the recepient addresses, then set + * the subject and other headers, then get the PrintStream, then write the + * message, and finally send and close. The class does minimal error checking + * internally; it counts on the mail host to complain if there's any + * malformatted input or out of order execution.

          + * + * An attachment mechanism based on RFC 1521 could be implemented on top of this + * class. In the meanwhile, JavaMail is the best solution for sending email with + * attachments.

          + * + * Still to do: + *

            + *
          • Figure out how to close the connection in case of error + *
          + * + * + * @author Jason Hunter + * @version 1.1, 2000/03/19, added angle brackets to address, helps some servers + * version 1.0, 1999/12/29 + */ +public class MailMessage +{ + + /** + * default port for SMTP: 25 + */ + public final static int DEFAULT_PORT = 25; + + /** + * host port for the mail server + */ + private int port = DEFAULT_PORT; + + /** + * list of email addresses to cc to + */ + private Vector cc; + + /** + * sender email address + */ + private String from; + + /** + * headers to send in the mail + */ + private Hashtable headers; + + /** + * host name for the mail server + */ + private String host; + + private SmtpResponseReader in; + + private MailPrintStream out; + + private Socket socket; + + /** + * list of email addresses to send to + */ + private Vector to; + + /** + * Constructs a new MailMessage to send an email. Use localhost as the mail + * server. + * + * @exception IOException if there's any problem contacting the mail server + */ + public MailMessage() + throws IOException + { + this( "localhost" ); + } + + /** + * Constructs a new MailMessage to send an email. Use the given host as the + * mail server. + * + * @param host the mail server to use + * @exception IOException if there's any problem contacting the mail server + */ + public MailMessage( String host ) + throws IOException + { + this.host = host; + to = new Vector(); + cc = new Vector(); + headers = new Hashtable(); + setHeader( "X-Mailer", "org.apache.tools.mail.MailMessage (jakarta.apache.org)" ); + connect(); + sendHelo(); + } + + // Make a limited attempt to extract a sanitized email address + // Prefer text in , ignore anything in (parentheses) + static String sanitizeAddress( String s ) + { + int paramDepth = 0; + int start = 0; + int end = 0; + int len = s.length(); + + for( int i = 0; i < len; i++ ) + { + char c = s.charAt( i ); + if( c == '(' ) + { + paramDepth++; + if( start == 0 ) + { + end = i;// support "address (name)" + } + } + else if( c == ')' ) + { + paramDepth--; + if( end == 0 ) + { + start = i + 1;// support "(name) address" + } + } + else if( paramDepth == 0 && c == '<' ) + { + start = i + 1; + } + else if( paramDepth == 0 && c == '>' ) + { + end = i; + } + } + + if( end == 0 ) + { + end = len; + } + + return s.substring( start, end ); + } + + /** + * Sets the named header to the given value. RFC 822 provides the rules for + * what text may constitute a header name and value. + * + * @param name The new Header value + * @param value The new Header value + */ + public void setHeader( String name, String value ) + { + // Blindly trust the user doesn't set any invalid headers + headers.put( name, value ); + } + + /** + * Set the port to connect to the SMTP host. + * + * @param port the port to use for connection. + * @see #DEFAULT_PORT + */ + public void setPort( int port ) + { + this.port = port; + } + + /** + * Sets the subject of the mail message. Actually sets the "Subject" header. + * + * @param subj The new Subject value + */ + public void setSubject( String subj ) + { + headers.put( "Subject", subj ); + } + + /** + * Returns a PrintStream that can be used to write the body of the message. + * A stream is used since email bodies are byte-oriented. A writer could be + * wrapped on top if necessary for internationalization. + * + * @return The PrintStream value + * @exception IOException if there's any problem reported by the mail server + */ + public PrintStream getPrintStream() + throws IOException + { + setFromHeader(); + setToHeader(); + setCcHeader(); + sendData(); + flushHeaders(); + return out; + } + + /** + * Sets the bcc address. Does NOT set any header since it's a *blind* copy. + * This method may be called multiple times. + * + * @param bcc Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void bcc( String bcc ) + throws IOException + { + sendRcpt( bcc ); + // No need to keep track of Bcc'd addresses + } + + /** + * Sets the cc address. Also sets the "Cc" header. This method may be called + * multiple times. + * + * @param cc Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void cc( String cc ) + throws IOException + { + sendRcpt( cc ); + this.cc.addElement( cc ); + } + + /** + * Sets the from address. Also sets the "From" header. This method should be + * called only once. + * + * @param from Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void from( String from ) + throws IOException + { + sendFrom( from ); + this.from = from; + } + + /** + * Sends the message and closes the connection to the server. The + * MailMessage object cannot be reused. + * + * @exception IOException if there's any problem reported by the mail server + */ + public void sendAndClose() + throws IOException + { + sendDot(); + sendQuit(); + disconnect(); + } + + /** + * Sets the to address. Also sets the "To" header. This method may be called + * multiple times. + * + * @param to Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void to( String to ) + throws IOException + { + sendRcpt( to ); + this.to.addElement( to ); + } + + void setCcHeader() + { + setHeader( "Cc", vectorToList( cc ) ); + } + + void setFromHeader() + { + setHeader( "From", from ); + } + + void setToHeader() + { + setHeader( "To", vectorToList( to ) ); + } + + void getReady() + throws IOException + { + String response = in.getResponse(); + int[] ok = {220}; + if( !isResponseOK( response, ok ) ) + { + throw new IOException( + "Didn't get introduction from server: " + response ); + } + } + + boolean isResponseOK( String response, int[] ok ) + { + // Check that the response is one of the valid codes + for( int i = 0; i < ok.length; i++ ) + { + if( response.startsWith( "" + ok[i] ) ) + { + return true; + } + } + return false; + } + + // * * * * * Raw protocol methods below here * * * * * + + void connect() + throws IOException + { + socket = new Socket( host, port ); + out = new MailPrintStream( + new BufferedOutputStream( + socket.getOutputStream() ) ); + in = new SmtpResponseReader( socket.getInputStream() ); + getReady(); + } + + void disconnect() + throws IOException + { + if( out != null ) + out.close(); + if( in != null ) + in.close(); + if( socket != null ) + socket.close(); + } + + void flushHeaders() + throws IOException + { + // XXX Should I care about order here? + Enumeration e = headers.keys(); + while( e.hasMoreElements() ) + { + String name = ( String )e.nextElement(); + String value = ( String )headers.get( name ); + out.println( name + ": " + value ); + } + out.println(); + out.flush(); + } + + void send( String msg, int[] ok ) + throws IOException + { + out.rawPrint( msg + "\r\n" );// raw supports . + //System.out.println("S: " + msg); + String response = in.getResponse(); + //System.out.println("R: " + response); + if( !isResponseOK( response, ok ) ) + { + throw new IOException( + "Unexpected reply to command: " + msg + ": " + response ); + } + } + + void sendData() + throws IOException + { + int[] ok = {354}; + send( "DATA", ok ); + } + + void sendDot() + throws IOException + { + int[] ok = {250}; + send( "\r\n.", ok );// make sure dot is on new line + } + + void sendFrom( String from ) + throws IOException + { + int[] ok = {250}; + send( "MAIL FROM: " + "<" + sanitizeAddress( from ) + ">", ok ); + } + + void sendHelo() + throws IOException + { + String local = InetAddress.getLocalHost().getHostName(); + int[] ok = {250}; + send( "HELO " + local, ok ); + } + + void sendQuit() + throws IOException + { + int[] ok = {221}; + send( "QUIT", ok ); + } + + void sendRcpt( String rcpt ) + throws IOException + { + int[] ok = {250, 251}; + send( "RCPT TO: " + "<" + sanitizeAddress( rcpt ) + ">", ok ); + } + + String vectorToList( Vector v ) + { + StringBuffer buf = new StringBuffer(); + Enumeration e = v.elements(); + while( e.hasMoreElements() ) + { + buf.append( e.nextElement() ); + if( e.hasMoreElements() ) + { + buf.append( ", " ); + } + } + return buf.toString(); + } +} + +// This PrintStream subclass makes sure that . becomes .. +// per RFC 821. It also ensures that new lines are always \r\n. +// +class MailPrintStream extends PrintStream +{ + + int lastChar; + + public MailPrintStream( OutputStream out ) + { + super( out, true );// deprecated, but email is byte-oriented + } + + // Mac does \n\r, but that's tough to distinguish from Windows \r\n\r\n. + // Don't tackle that problem right now. + public void write( int b ) + { + if( b == '\n' && lastChar != '\r' ) + { + rawWrite( '\r' );// ensure always \r\n + rawWrite( b ); + } + else if( b == '.' && lastChar == '\n' ) + { + rawWrite( '.' );// add extra dot + rawWrite( b ); + } + else + { + rawWrite( b ); + } + lastChar = b; + } + + public void write( byte buf[], int off, int len ) + { + for( int i = 0; i < len; i++ ) + { + write( buf[off + i] ); + } + } + + void rawPrint( String s ) + { + int len = s.length(); + for( int i = 0; i < len; i++ ) + { + rawWrite( s.charAt( i ) ); + } + } + + void rawWrite( int b ) + { + super.write( b ); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/mail/SmtpResponseReader.java b/proposal/myrmidon/src/main/org/apache/tools/mail/SmtpResponseReader.java new file mode 100644 index 000000000..73f935bfc --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/mail/SmtpResponseReader.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.mail; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * A wrapper around the raw input from the SMTP server that assembles multi line + * responses into a single String.

          + * + * The same rules used here would apply to FTP and other Telnet based protocols + * as well.

          + * + * @author Stefan Bodewig + */ +public class SmtpResponseReader +{ + + protected BufferedReader reader = null; + private StringBuffer result = new StringBuffer(); + + /** + * Wrap this input stream. + * + * @param in Description of Parameter + */ + public SmtpResponseReader( InputStream in ) + { + reader = new BufferedReader( new InputStreamReader( in ) ); + } + + /** + * Read until the server indicates that the response is complete. + * + * @return Responsecode (3 digits) + Blank + Text from all response line + * concatenated (with blanks replacing the \r\n sequences). + * @exception IOException Description of Exception + */ + public String getResponse() + throws IOException + { + result.setLength( 0 ); + String line = reader.readLine(); + if( line != null && line.length() >= 3 ) + { + result.append( line.substring( 0, 3 ) ); + result.append( " " ); + } + + while( line != null ) + { + append( line ); + if( !hasMoreLines( line ) ) + { + break; + } + line = reader.readLine(); + } + return result.toString().trim(); + } + + /** + * Closes the underlying stream. + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + reader.close(); + } + + /** + * Should we expect more input? + * + * @param line Description of Parameter + * @return Description of the Returned Value + */ + protected boolean hasMoreLines( String line ) + { + return line.length() > 3 && line.charAt( 3 ) == '-'; + } + + /** + * Append the text from this line of the resonse. + * + * @param line Description of Parameter + */ + private void append( String line ) + { + if( line.length() > 4 ) + { + result.append( line.substring( 4 ) ); + result.append( " " ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/tar/TarBuffer.java b/proposal/myrmidon/src/main/org/apache/tools/tar/TarBuffer.java new file mode 100644 index 000000000..a580a3377 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/tar/TarBuffer.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The TarBuffer class implements the tar archive concept of a buffered input + * stream. This concept goes back to the days of blocked tape drives and special + * io devices. In the Java universe, the only real function that this class + * performs is to ensure that files have the correct "block" size, or other tars + * will complain.

          + * + * You should never have a need to access this class directly. TarBuffers are + * created by Tar IO Streams. + * + * @author Timothy Gerard Endres time@ice.com + */ + +public class TarBuffer +{ + + public final static int DEFAULT_RCDSIZE = ( 512 ); + public final static int DEFAULT_BLKSIZE = ( DEFAULT_RCDSIZE * 20 ); + private byte[] blockBuffer; + private int blockSize; + private int currBlkIdx; + private int currRecIdx; + private boolean debug; + + private InputStream inStream; + private OutputStream outStream; + private int recordSize; + private int recsPerBlock; + + public TarBuffer( InputStream inStream ) + { + this( inStream, TarBuffer.DEFAULT_BLKSIZE ); + } + + public TarBuffer( InputStream inStream, int blockSize ) + { + this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarBuffer( InputStream inStream, int blockSize, int recordSize ) + { + this.inStream = inStream; + this.outStream = null; + + this.initialize( blockSize, recordSize ); + } + + public TarBuffer( OutputStream outStream ) + { + this( outStream, TarBuffer.DEFAULT_BLKSIZE ); + } + + public TarBuffer( OutputStream outStream, int blockSize ) + { + this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarBuffer( OutputStream outStream, int blockSize, int recordSize ) + { + this.inStream = null; + this.outStream = outStream; + + this.initialize( blockSize, recordSize ); + } + + /** + * Set the debugging flag for the buffer. + * + * @param debug If true, print debugging output. + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Get the TAR Buffer's block size. Blocks consist of multiple records. + * + * @return The BlockSize value + */ + public int getBlockSize() + { + return this.blockSize; + } + + /** + * Get the current block number, zero based. + * + * @return The current zero based block number. + */ + public int getCurrentBlockNum() + { + return this.currBlkIdx; + } + + /** + * Get the current record number, within the current block, zero based. + * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum. + * + * @return The current zero based record number. + */ + public int getCurrentRecordNum() + { + return this.currRecIdx - 1; + } + + /** + * Get the TAR Buffer's record size. + * + * @return The RecordSize value + */ + public int getRecordSize() + { + return this.recordSize; + } + + /** + * Determine if an archive record indicate End of Archive. End of archive is + * indicated by a record that consists entirely of null bytes. + * + * @param record The record data to check. + * @return The EOFRecord value + */ + public boolean isEOFRecord( byte[] record ) + { + for( int i = 0, sz = this.getRecordSize(); i < sz; ++i ) + { + if( record[i] != 0 ) + { + return false; + } + } + + return true; + } + + /** + * Close the TarBuffer. If this is an output buffer, also flush the current + * block before closing. + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + if( this.debug ) + { + System.err.println( "TarBuffer.closeBuffer()." ); + } + + if( this.outStream != null ) + { + this.flushBlock(); + + if( this.outStream != System.out + && this.outStream != System.err ) + { + this.outStream.close(); + + this.outStream = null; + } + } + else if( this.inStream != null ) + { + if( this.inStream != System.in ) + { + this.inStream.close(); + + this.inStream = null; + } + } + } + + /** + * Read a record from the input stream and return the data. + * + * @return The record data. + * @exception IOException Description of Exception + */ + public byte[] readRecord() + throws IOException + { + if( this.debug ) + { + System.err.println( "ReadRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.inStream == null ) + { + throw new IOException( "reading from an output buffer" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + if( !this.readBlock() ) + { + return null; + } + } + + byte[] result = new byte[this.recordSize]; + + System.arraycopy( this.blockBuffer, + ( this.currRecIdx * this.recordSize ), result, 0, + this.recordSize ); + + this.currRecIdx++; + + return result; + } + + /** + * Skip over a record on the input stream. + * + * @exception IOException Description of Exception + */ + public void skipRecord() + throws IOException + { + if( this.debug ) + { + System.err.println( "SkipRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.inStream == null ) + { + throw new IOException( "reading (via skip) from an output buffer" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + if( !this.readBlock() ) + { + return;// UNDONE + } + } + + this.currRecIdx++; + } + + /** + * Write an archive record to the archive. + * + * @param record The record data to write to the archive. + * @exception IOException Description of Exception + */ + public void writeRecord( byte[] record ) + throws IOException + { + if( this.debug ) + { + System.err.println( "WriteRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + if( record.length != this.recordSize ) + { + throw new IOException( "record to write has length '" + + record.length + + "' which is not the record size of '" + + this.recordSize + "'" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + this.writeBlock(); + } + + System.arraycopy( record, 0, this.blockBuffer, + ( this.currRecIdx * this.recordSize ), + this.recordSize ); + + this.currRecIdx++; + } + + /** + * Write an archive record to the archive, where the record may be inside of + * a larger array buffer. The buffer must be "offset plus record size" long. + * + * @param buf The buffer containing the record data to write. + * @param offset The offset of the record data within buf. + * @exception IOException Description of Exception + */ + public void writeRecord( byte[] buf, int offset ) + throws IOException + { + if( this.debug ) + { + System.err.println( "WriteRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + if( ( offset + this.recordSize ) > buf.length ) + { + throw new IOException( "record has length '" + buf.length + + "' with offset '" + offset + + "' which is less than the record size of '" + + this.recordSize + "'" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + this.writeBlock(); + } + + System.arraycopy( buf, offset, this.blockBuffer, + ( this.currRecIdx * this.recordSize ), + this.recordSize ); + + this.currRecIdx++; + } + + /** + * Flush the current data block if it has any data in it. + * + * @exception IOException Description of Exception + */ + private void flushBlock() + throws IOException + { + if( this.debug ) + { + System.err.println( "TarBuffer.flushBlock() called." ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + if( this.currRecIdx > 0 ) + { + this.writeBlock(); + } + } + + /** + * Initialization common to all constructors. + * + * @param blockSize Description of Parameter + * @param recordSize Description of Parameter + */ + private void initialize( int blockSize, int recordSize ) + { + this.debug = false; + this.blockSize = blockSize; + this.recordSize = recordSize; + this.recsPerBlock = ( this.blockSize / this.recordSize ); + this.blockBuffer = new byte[this.blockSize]; + + if( this.inStream != null ) + { + this.currBlkIdx = -1; + this.currRecIdx = this.recsPerBlock; + } + else + { + this.currBlkIdx = 0; + this.currRecIdx = 0; + } + } + + /** + * @return false if End-Of-File, else true + * @exception IOException Description of Exception + */ + private boolean readBlock() + throws IOException + { + if( this.debug ) + { + System.err.println( "ReadBlock: blkIdx = " + this.currBlkIdx ); + } + + if( this.inStream == null ) + { + throw new IOException( "reading from an output buffer" ); + } + + this.currRecIdx = 0; + + int offset = 0; + int bytesNeeded = this.blockSize; + + while( bytesNeeded > 0 ) + { + long numBytes = this.inStream.read( this.blockBuffer, offset, + bytesNeeded ); + + // + // NOTE + // We have fit EOF, and the block is not full! + // + // This is a broken archive. It does not follow the standard + // blocking algorithm. However, because we are generous, and + // it requires little effort, we will simply ignore the error + // and continue as if the entire block were read. This does + // not appear to break anything upstream. We used to return + // false in this case. + // + // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix. + // + if( numBytes == -1 ) + { + break; + } + + offset += numBytes; + bytesNeeded -= numBytes; + + if( numBytes != this.blockSize ) + { + if( this.debug ) + { + System.err.println( "ReadBlock: INCOMPLETE READ " + + numBytes + " of " + this.blockSize + + " bytes read." ); + } + } + } + + this.currBlkIdx++; + + return true; + } + + /** + * Write a TarBuffer block to the archive. + * + * @exception IOException Description of Exception + */ + private void writeBlock() + throws IOException + { + if( this.debug ) + { + System.err.println( "WriteBlock: blkIdx = " + this.currBlkIdx ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + this.outStream.write( this.blockBuffer, 0, this.blockSize ); + this.outStream.flush(); + + this.currRecIdx = 0; + this.currBlkIdx++; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/tar/TarConstants.java b/proposal/myrmidon/src/main/org/apache/tools/tar/TarConstants.java new file mode 100644 index 000000000..6203d31d1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/tar/TarConstants.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; + +/** + * This interface contains all the definitions used in the package. + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ + +public interface TarConstants +{ + + /** + * The length of the name field in a header buffer. + */ + int NAMELEN = 100; + + /** + * The length of the mode field in a header buffer. + */ + int MODELEN = 8; + + /** + * The length of the user id field in a header buffer. + */ + int UIDLEN = 8; + + /** + * The length of the group id field in a header buffer. + */ + int GIDLEN = 8; + + /** + * The length of the checksum field in a header buffer. + */ + int CHKSUMLEN = 8; + + /** + * The length of the size field in a header buffer. + */ + int SIZELEN = 12; + + /** + * The length of the magic field in a header buffer. + */ + int MAGICLEN = 8; + + /** + * The length of the modification time field in a header buffer. + */ + int MODTIMELEN = 12; + + /** + * The length of the user name field in a header buffer. + */ + int UNAMELEN = 32; + + /** + * The length of the group name field in a header buffer. + */ + int GNAMELEN = 32; + + /** + * The length of the devices field in a header buffer. + */ + int DEVLEN = 8; + + /** + * LF_ constants represent the "link flag" of an entry, or more commonly, + * the "entry type". This is the "old way" of indicating a normal file. + */ + byte LF_OLDNORM = 0; + + /** + * Normal file type. + */ + byte LF_NORMAL = ( byte )'0'; + + /** + * Link file type. + */ + byte LF_LINK = ( byte )'1'; + + /** + * Symbolic link file type. + */ + byte LF_SYMLINK = ( byte )'2'; + + /** + * Character device file type. + */ + byte LF_CHR = ( byte )'3'; + + /** + * Block device file type. + */ + byte LF_BLK = ( byte )'4'; + + /** + * Directory file type. + */ + byte LF_DIR = ( byte )'5'; + + /** + * FIFO (pipe) file type. + */ + byte LF_FIFO = ( byte )'6'; + + /** + * Contiguous file type. + */ + byte LF_CONTIG = ( byte )'7'; + + /** + * The magic tag representing a POSIX tar archive. + */ + String TMAGIC = "ustar"; + + /** + * The magic tag representing a GNU tar archive. + */ + String GNU_TMAGIC = "ustar "; + + /** + * The namr of the GNU tar entry which contains a long name. + */ + String GNU_LONGLINK = "././@LongLink"; + + /** + * Identifies the *next* file on the tape as having a long name. + */ + byte LF_GNUTYPE_LONGNAME = ( byte )'L'; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/tar/TarEntry.java b/proposal/myrmidon/src/main/org/apache/tools/tar/TarEntry.java new file mode 100644 index 000000000..7914dd132 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/tar/TarEntry.java @@ -0,0 +1,654 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.File; +import java.util.Date; + +/** + * This class represents an entry in a Tar archive. It consists of the entry's + * header, as well as the entry's File. Entries can be instantiated in one of + * three ways, depending on how they are to be used.

          + * + * TarEntries that are created from the header bytes read from an archive are + * instantiated with the TarEntry( byte[] ) constructor. These entries will be + * used when extracting from or listing the contents of an archive. These + * entries have their header filled in using the header bytes. They also set the + * File to null, since they reference an archive entry not a file.

          + * + * TarEntries that are created from Files that are to be written into an archive + * are instantiated with the TarEntry( File ) constructor. These entries have + * their header filled in using the File's information. They also keep a + * reference to the File for convenience when writing entries.

          + * + * Finally, TarEntries can be constructed from nothing but a name. This allows + * the programmer to construct the entry by hand, for instance when only an + * InputStream is available for writing to the archive, and the header + * information is constructed from other information. In this case the header + * fields are set to defaults and the File is set to null.

          + * + * The C structure for a Tar Entry's header is:

          + * struct header {
          + * char name[NAMSIZ];
          + * char mode[8];
          + * char uid[8];
          + * char gid[8];
          + * char size[12];
          + * char mtime[12];
          + * char chksum[8];
          + * char linkflag;
          + * char linkname[NAMSIZ];
          + * char magic[8];
          + * char uname[TUNMLEN];
          + * char gname[TGNMLEN];
          + * char devmajor[8];
          + * char devminor[8];
          + * } header;
          + * 
          + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ + +public class TarEntry implements TarConstants +{ + /** + * The entry's modification time. + */ + private int checkSum; + /** + * The entry's group name. + */ + private int devMajor; + /** + * The entry's major device number. + */ + private int devMinor; + /** + * The entry's minor device number. + */ + private File file; + /** + * The entry's user id. + */ + private int groupId; + /** + * The entry's user name. + */ + private StringBuffer groupName; + /** + * The entry's checksum. + */ + private byte linkFlag; + /** + * The entry's link flag. + */ + private StringBuffer linkName; + /** + * The entry's link name. + */ + private StringBuffer magic; + /** + * The entry's size. + */ + private long modTime; + /** + * The entry's name. + */ + private int mode; + + private StringBuffer name; + /** + * The entry's group id. + */ + private long size; + /** + * The entry's permission mode. + */ + private int userId; + /** + * The entry's magic tag. + */ + private StringBuffer userName; + + /** + * Construct an entry with only a name. This allows the programmer to + * construct the entry's header "by hand". File is set to null. + * + * @param name Description of Parameter + */ + public TarEntry( String name ) + { + this(); + + boolean isDir = name.endsWith( "/" ); + + this.checkSum = 0; + this.devMajor = 0; + this.devMinor = 0; + this.name = new StringBuffer( name ); + this.mode = isDir ? 040755 : 0100644; + this.linkFlag = isDir ? LF_DIR : LF_NORMAL; + this.userId = 0; + this.groupId = 0; + this.size = 0; + this.checkSum = 0; + this.modTime = ( new Date() ).getTime() / 1000; + this.linkName = new StringBuffer( "" ); + this.userName = new StringBuffer( "" ); + this.groupName = new StringBuffer( "" ); + this.devMajor = 0; + this.devMinor = 0; + + } + + /** + * Construct an entry with a name an a link flag. + * + * @param name Description of Parameter + * @param linkFlag Description of Parameter + */ + public TarEntry( String name, byte linkFlag ) + { + this( name ); + this.linkFlag = linkFlag; + } + + /** + * Construct an entry for a file. File is set to file, and the header is + * constructed from information from the file. + * + * @param file The file that the entry represents. + */ + public TarEntry( File file ) + { + this(); + + this.file = file; + + String name = file.getPath(); + String osname = System.getProperty( "os.name" ); + + if( osname != null ) + { + + // Strip off drive letters! + // REVIEW Would a better check be "(File.separator == '\')"? + String win32Prefix = "Windows"; + String prefix = osname.substring( 0, win32Prefix.length() ); + + if( prefix.equalsIgnoreCase( win32Prefix ) ) + { + if( name.length() > 2 ) + { + char ch1 = name.charAt( 0 ); + char ch2 = name.charAt( 1 ); + + if( ch2 == ':' + && ( ( ch1 >= 'a' && ch1 <= 'z' ) + || ( ch1 >= 'A' && ch1 <= 'Z' ) ) ) + { + name = name.substring( 2 ); + } + } + } + else if( osname.toLowerCase().indexOf( "netware" ) > -1 ) + { + int colon = name.indexOf( ':' ); + if( colon != -1 ) + { + name = name.substring( colon + 1 ); + } + } + } + + name = name.replace( File.separatorChar, '/' ); + + // No absolute pathnames + // Windows (and Posix?) paths can start with "\\NetworkDrive\", + // so we loop on starting /'s. + while( name.startsWith( "/" ) ) + { + name = name.substring( 1 ); + } + + this.linkName = new StringBuffer( "" ); + this.name = new StringBuffer( name ); + + if( file.isDirectory() ) + { + this.mode = 040755; + this.linkFlag = LF_DIR; + + if( this.name.charAt( this.name.length() - 1 ) != '/' ) + { + this.name.append( "/" ); + } + } + else + { + this.mode = 0100644; + this.linkFlag = LF_NORMAL; + } + + this.size = file.length(); + this.modTime = file.lastModified() / 1000; + this.checkSum = 0; + this.devMajor = 0; + this.devMinor = 0; + } + + /** + * Construct an entry from an archive's header bytes. File is set to null. + * + * @param headerBuf The header bytes from a tar archive entry. + */ + public TarEntry( byte[] headerBuf ) + { + this(); + this.parseTarHeader( headerBuf ); + } + + /** + * The entry's file reference + */ + + /** + * Construct an empty entry and prepares the header values. + */ + private TarEntry() + { + this.magic = new StringBuffer( TMAGIC ); + this.name = new StringBuffer(); + this.linkName = new StringBuffer(); + + String user = System.getProperty( "user.name", "" ); + + if( user.length() > 31 ) + { + user = user.substring( 0, 31 ); + } + + this.userId = 0; + this.groupId = 0; + this.userName = new StringBuffer( user ); + this.groupName = new StringBuffer( "" ); + this.file = null; + } + + /** + * Set this entry's group id. + * + * @param groupId This entry's new group id. + */ + public void setGroupId( int groupId ) + { + this.groupId = groupId; + } + + /** + * Set this entry's group name. + * + * @param groupName This entry's new group name. + */ + public void setGroupName( String groupName ) + { + this.groupName = new StringBuffer( groupName ); + } + + /** + * Convenience method to set this entry's group and user ids. + * + * @param userId This entry's new user id. + * @param groupId This entry's new group id. + */ + public void setIds( int userId, int groupId ) + { + this.setUserId( userId ); + this.setGroupId( groupId ); + } + + /** + * Set this entry's modification time. The parameter passed to this method + * is in "Java time". + * + * @param time This entry's new modification time. + */ + public void setModTime( long time ) + { + this.modTime = time / 1000; + } + + /** + * Set this entry's modification time. + * + * @param time This entry's new modification time. + */ + public void setModTime( Date time ) + { + this.modTime = time.getTime() / 1000; + } + + /** + * Set the mode for this entry + * + * @param mode The new Mode value + */ + public void setMode( int mode ) + { + this.mode = mode; + } + + /** + * Set this entry's name. + * + * @param name This entry's new name. + */ + public void setName( String name ) + { + this.name = new StringBuffer( name ); + } + + /** + * Convenience method to set this entry's group and user names. + * + * @param userName This entry's new user name. + * @param groupName This entry's new group name. + */ + public void setNames( String userName, String groupName ) + { + this.setUserName( userName ); + this.setGroupName( groupName ); + } + + /** + * Set this entry's file size. + * + * @param size This entry's new file size. + */ + public void setSize( long size ) + { + this.size = size; + } + + /** + * Set this entry's user id. + * + * @param userId This entry's new user id. + */ + public void setUserId( int userId ) + { + this.userId = userId; + } + + /** + * Set this entry's user name. + * + * @param userName This entry's new user name. + */ + public void setUserName( String userName ) + { + this.userName = new StringBuffer( userName ); + } + + /** + * If this entry represents a file, and the file is a directory, return an + * array of TarEntries for this entry's children. + * + * @return An array of TarEntry's for this entry's children. + */ + public TarEntry[] getDirectoryEntries() + { + if( this.file == null || !this.file.isDirectory() ) + { + return new TarEntry[0]; + } + + String[] list = this.file.list(); + TarEntry[] result = new TarEntry[list.length]; + + for( int i = 0; i < list.length; ++i ) + { + result[i] = new TarEntry( new File( this.file, list[i] ) ); + } + + return result; + } + + /** + * Get this entry's file. + * + * @return This entry's file. + */ + public File getFile() + { + return this.file; + } + + /** + * Get this entry's group id. + * + * @return This entry's group id. + */ + public int getGroupId() + { + return this.groupId; + } + + /** + * Get this entry's group name. + * + * @return This entry's group name. + */ + public String getGroupName() + { + return this.groupName.toString(); + } + + /** + * Set this entry's modification time. + * + * @return The ModTime value + */ + public Date getModTime() + { + return new Date( this.modTime * 1000 ); + } + + /** + * Get this entry's mode. + * + * @return This entry's mode. + */ + public int getMode() + { + return this.mode; + } + + /** + * Get this entry's name. + * + * @return This entry's name. + */ + public String getName() + { + return this.name.toString(); + } + + /** + * Get this entry's file size. + * + * @return This entry's file size. + */ + public long getSize() + { + return this.size; + } + + + /** + * Get this entry's user id. + * + * @return This entry's user id. + */ + public int getUserId() + { + return this.userId; + } + + /** + * Get this entry's user name. + * + * @return This entry's user name. + */ + public String getUserName() + { + return this.userName.toString(); + } + + /** + * Determine if the given entry is a descendant of this entry. Descendancy + * is determined by the name of the descendant starting with this entry's + * name. + * + * @param desc Entry to be checked as a descendent of this. + * @return True if entry is a descendant of this. + */ + public boolean isDescendent( TarEntry desc ) + { + return desc.getName().startsWith( this.getName() ); + } + + /** + * Return whether or not this entry represents a directory. + * + * @return True if this entry is a directory. + */ + public boolean isDirectory() + { + if( this.file != null ) + { + return this.file.isDirectory(); + } + + if( this.linkFlag == LF_DIR ) + { + return true; + } + + if( this.getName().endsWith( "/" ) ) + { + return true; + } + + return false; + } + + + /** + * Indicate if this entry is a GNU long name block + * + * @return true if this is a long name extension provided by GNU tar + */ + public boolean isGNULongNameEntry() + { + return linkFlag == LF_GNUTYPE_LONGNAME && + name.toString().equals( GNU_LONGLINK ); + } + + /** + * Determine if the two entries are equal. Equality is determined by the + * header names being equal. + * + * @param it Description of Parameter + * @return it Entry to be checked for equality. + * @return True if the entries are equal. + */ + public boolean equals( TarEntry it ) + { + return this.getName().equals( it.getName() ); + } + + /** + * Parse an entry's header information from a header buffer. + * + * @param header The tar entry header buffer to get information from. + */ + public void parseTarHeader( byte[] header ) + { + int offset = 0; + + this.name = TarUtils.parseName( header, offset, NAMELEN ); + offset += NAMELEN; + this.mode = ( int )TarUtils.parseOctal( header, offset, MODELEN ); + offset += MODELEN; + this.userId = ( int )TarUtils.parseOctal( header, offset, UIDLEN ); + offset += UIDLEN; + this.groupId = ( int )TarUtils.parseOctal( header, offset, GIDLEN ); + offset += GIDLEN; + this.size = TarUtils.parseOctal( header, offset, SIZELEN ); + offset += SIZELEN; + this.modTime = TarUtils.parseOctal( header, offset, MODTIMELEN ); + offset += MODTIMELEN; + this.checkSum = ( int )TarUtils.parseOctal( header, offset, CHKSUMLEN ); + offset += CHKSUMLEN; + this.linkFlag = header[offset++]; + this.linkName = TarUtils.parseName( header, offset, NAMELEN ); + offset += NAMELEN; + this.magic = TarUtils.parseName( header, offset, MAGICLEN ); + offset += MAGICLEN; + this.userName = TarUtils.parseName( header, offset, UNAMELEN ); + offset += UNAMELEN; + this.groupName = TarUtils.parseName( header, offset, GNAMELEN ); + offset += GNAMELEN; + this.devMajor = ( int )TarUtils.parseOctal( header, offset, DEVLEN ); + offset += DEVLEN; + this.devMinor = ( int )TarUtils.parseOctal( header, offset, DEVLEN ); + } + + /** + * Write an entry's header information to a header buffer. + * + * @param outbuf The tar entry header buffer to fill in. + */ + public void writeEntryHeader( byte[] outbuf ) + { + int offset = 0; + + offset = TarUtils.getNameBytes( this.name, outbuf, offset, NAMELEN ); + offset = TarUtils.getOctalBytes( this.mode, outbuf, offset, MODELEN ); + offset = TarUtils.getOctalBytes( this.userId, outbuf, offset, UIDLEN ); + offset = TarUtils.getOctalBytes( this.groupId, outbuf, offset, GIDLEN ); + offset = TarUtils.getLongOctalBytes( this.size, outbuf, offset, SIZELEN ); + offset = TarUtils.getLongOctalBytes( this.modTime, outbuf, offset, MODTIMELEN ); + + int csOffset = offset; + + for( int c = 0; c < CHKSUMLEN; ++c ) + { + outbuf[offset++] = ( byte )' '; + } + + outbuf[offset++] = this.linkFlag; + offset = TarUtils.getNameBytes( this.linkName, outbuf, offset, NAMELEN ); + offset = TarUtils.getNameBytes( this.magic, outbuf, offset, MAGICLEN ); + offset = TarUtils.getNameBytes( this.userName, outbuf, offset, UNAMELEN ); + offset = TarUtils.getNameBytes( this.groupName, outbuf, offset, GNAMELEN ); + offset = TarUtils.getOctalBytes( this.devMajor, outbuf, offset, DEVLEN ); + offset = TarUtils.getOctalBytes( this.devMinor, outbuf, offset, DEVLEN ); + + while( offset < outbuf.length ) + { + outbuf[offset++] = 0; + } + + long checkSum = TarUtils.computeCheckSum( outbuf ); + + TarUtils.getCheckSumOctalBytes( checkSum, outbuf, csOffset, CHKSUMLEN ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/tar/TarInputStream.java b/proposal/myrmidon/src/main/org/apache/tools/tar/TarInputStream.java new file mode 100644 index 000000000..5e340e6ae --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/tar/TarInputStream.java @@ -0,0 +1,439 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The TarInputStream reads a UNIX tar archive as an InputStream. methods are + * provided to position at each successive entry in the archive, and the read + * each entry as a normal input stream using read(). + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ +public class TarInputStream extends FilterInputStream +{ + protected TarBuffer buffer; + protected TarEntry currEntry; + + protected boolean debug; + protected int entryOffset; + protected int entrySize; + protected boolean hasHitEOF; + protected byte[] oneBuf; + protected byte[] readBuf; + + public TarInputStream( InputStream is ) + { + this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarInputStream( InputStream is, int blockSize ) + { + this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarInputStream( InputStream is, int blockSize, int recordSize ) + { + super( is ); + + this.buffer = new TarBuffer( is, blockSize, recordSize ); + this.readBuf = null; + this.oneBuf = new byte[1]; + this.debug = false; + this.hasHitEOF = false; + } + + /** + * Sets the debugging flag. + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + this.buffer.setDebug( debug ); + } + + /** + * Get the next entry in this tar archive. This will skip over any remaining + * data in the current entry, if there is one, and place the input stream at + * the header of the next entry, and read the header and instantiate a new + * TarEntry from the header bytes and return that entry. If there are no + * more entries in the archive, null will be returned to indicate that the + * end of the archive has been reached. + * + * @return The next TarEntry in the archive, or null. + * @exception IOException Description of Exception + */ + public TarEntry getNextEntry() + throws IOException + { + if( this.hasHitEOF ) + { + return null; + } + + if( this.currEntry != null ) + { + int numToSkip = this.entrySize - this.entryOffset; + + if( this.debug ) + { + System.err.println( "TarInputStream: SKIP currENTRY '" + + this.currEntry.getName() + "' SZ " + + this.entrySize + " OFF " + + this.entryOffset + " skipping " + + numToSkip + " bytes" ); + } + + if( numToSkip > 0 ) + { + this.skip( numToSkip ); + } + + this.readBuf = null; + } + + byte[] headerBuf = this.buffer.readRecord(); + + if( headerBuf == null ) + { + if( this.debug ) + { + System.err.println( "READ NULL RECORD" ); + } + this.hasHitEOF = true; + } + else if( this.buffer.isEOFRecord( headerBuf ) ) + { + if( this.debug ) + { + System.err.println( "READ EOF RECORD" ); + } + this.hasHitEOF = true; + } + + if( this.hasHitEOF ) + { + this.currEntry = null; + } + else + { + this.currEntry = new TarEntry( headerBuf ); + + if( !( headerBuf[257] == 'u' && headerBuf[258] == 's' + && headerBuf[259] == 't' && headerBuf[260] == 'a' + && headerBuf[261] == 'r' ) ) + { + this.entrySize = 0; + this.entryOffset = 0; + this.currEntry = null; + + throw new IOException( "bad header in block " + + this.buffer.getCurrentBlockNum() + + " record " + + this.buffer.getCurrentRecordNum() + + ", " + + "header magic is not 'ustar', but '" + + headerBuf[257] + + headerBuf[258] + + headerBuf[259] + + headerBuf[260] + + headerBuf[261] + + "', or (dec) " + + ( ( int )headerBuf[257] ) + + ", " + + ( ( int )headerBuf[258] ) + + ", " + + ( ( int )headerBuf[259] ) + + ", " + + ( ( int )headerBuf[260] ) + + ", " + + ( ( int )headerBuf[261] ) ); + } + + if( this.debug ) + { + System.err.println( "TarInputStream: SET CURRENTRY '" + + this.currEntry.getName() + + "' size = " + + this.currEntry.getSize() ); + } + + this.entryOffset = 0; + + // REVIEW How do we resolve this discrepancy?! + this.entrySize = ( int )this.currEntry.getSize(); + } + + if( this.currEntry != null && this.currEntry.isGNULongNameEntry() ) + { + // read in the name + StringBuffer longName = new StringBuffer(); + byte[] buffer = new byte[256]; + int length = 0; + while( ( length = read( buffer ) ) >= 0 ) + { + longName.append( new String( buffer, 0, length ) ); + } + getNextEntry(); + this.currEntry.setName( longName.toString() ); + } + + return this.currEntry; + } + + /** + * Get the record size being used by this stream's TarBuffer. + * + * @return The TarBuffer record size. + */ + public int getRecordSize() + { + return this.buffer.getRecordSize(); + } + + /** + * Get the available data that can be read from the current entry in the + * archive. This does not indicate how much data is left in the entire + * archive, only in the current entry. This value is determined from the + * entry's size header field and the amount of data already read from the + * current entry. + * + * @return The number of available bytes for the current entry. + * @exception IOException Description of Exception + */ + public int available() + throws IOException + { + return this.entrySize - this.entryOffset; + } + + /** + * Closes this stream. Calls the TarBuffer's close() method. + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + this.buffer.close(); + } + + /** + * Copies the contents of the current tar archive entry directly into an + * output stream. + * + * @param out The OutputStream into which to write the entry's data. + * @exception IOException Description of Exception + */ + public void copyEntryContents( OutputStream out ) + throws IOException + { + byte[] buf = new byte[32 * 1024]; + + while( true ) + { + int numRead = this.read( buf, 0, buf.length ); + + if( numRead == -1 ) + { + break; + } + + out.write( buf, 0, numRead ); + } + } + + /** + * Since we do not support marking just yet, we do nothing. + * + * @param markLimit The limit to mark. + */ + public void mark( int markLimit ) { } + + /** + * Since we do not support marking just yet, we return false. + * + * @return False. + */ + public boolean markSupported() + { + return false; + } + + /** + * Reads a byte from the current tar archive entry. This method simply calls + * read( byte[], int, int ). + * + * @return The byte read, or -1 at EOF. + * @exception IOException Description of Exception + */ + public int read() + throws IOException + { + int num = this.read( this.oneBuf, 0, 1 ); + + if( num == -1 ) + { + return num; + } + else + { + return ( int )this.oneBuf[0]; + } + } + + /** + * Reads bytes from the current tar archive entry. This method simply calls + * read( byte[], int, int ). + * + * @param buf The buffer into which to place bytes read. + * @return The number of bytes read, or -1 at EOF. + * @exception IOException Description of Exception + */ + public int read( byte[] buf ) + throws IOException + { + return this.read( buf, 0, buf.length ); + } + + /** + * Reads bytes from the current tar archive entry. This method is aware of + * the boundaries of the current entry in the archive and will deal with + * them as if they were this stream's start and EOF. + * + * @param buf The buffer into which to place bytes read. + * @param offset The offset at which to place bytes read. + * @param numToRead The number of bytes to read. + * @return The number of bytes read, or -1 at EOF. + * @exception IOException Description of Exception + */ + public int read( byte[] buf, int offset, int numToRead ) + throws IOException + { + int totalRead = 0; + + if( this.entryOffset >= this.entrySize ) + { + return -1; + } + + if( ( numToRead + this.entryOffset ) > this.entrySize ) + { + numToRead = ( this.entrySize - this.entryOffset ); + } + + if( this.readBuf != null ) + { + int sz = ( numToRead > this.readBuf.length ) ? this.readBuf.length + : numToRead; + + System.arraycopy( this.readBuf, 0, buf, offset, sz ); + + if( sz >= this.readBuf.length ) + { + this.readBuf = null; + } + else + { + int newLen = this.readBuf.length - sz; + byte[] newBuf = new byte[newLen]; + + System.arraycopy( this.readBuf, sz, newBuf, 0, newLen ); + + this.readBuf = newBuf; + } + + totalRead += sz; + numToRead -= sz; + offset += sz; + } + + while( numToRead > 0 ) + { + byte[] rec = this.buffer.readRecord(); + + if( rec == null ) + { + // Unexpected EOF! + throw new IOException( "unexpected EOF with " + numToRead + + " bytes unread" ); + } + + int sz = numToRead; + int recLen = rec.length; + + if( recLen > sz ) + { + System.arraycopy( rec, 0, buf, offset, sz ); + + this.readBuf = new byte[recLen - sz]; + + System.arraycopy( rec, sz, this.readBuf, 0, recLen - sz ); + } + else + { + sz = recLen; + + System.arraycopy( rec, 0, buf, offset, recLen ); + } + + totalRead += sz; + numToRead -= sz; + offset += sz; + } + + this.entryOffset += totalRead; + + return totalRead; + } + + /** + * Since we do not support marking just yet, we do nothing. + */ + public void reset() { } + + /** + * Skip bytes in the input buffer. This skips bytes in the current entry's + * data, not the entire archive, and will stop at the end of the current + * entry's data if the number to skip extends beyond that point. + * + * @param numToSkip The number of bytes to skip. + * @exception IOException Description of Exception + */ + public void skip( int numToSkip ) + throws IOException + { + + // REVIEW + // This is horribly inefficient, but it ensures that we + // properly skip over bytes via the TarBuffer... + // + byte[] skipBuf = new byte[8 * 1024]; + + for( int num = numToSkip; num > 0; ) + { + int numRead = this.read( skipBuf, 0, + ( num > skipBuf.length ? skipBuf.length + : num ) ); + + if( numRead == -1 ) + { + break; + } + + num -= numRead; + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/tar/TarOutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/tar/TarOutputStream.java new file mode 100644 index 000000000..92914c329 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/tar/TarOutputStream.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * The TarOutputStream writes a UNIX tar archive as an OutputStream. Methods are + * provided to put entries, and then write their contents by writing to this + * stream using write(). + * + * @author Timothy Gerard Endres time@ice.com + */ +public class TarOutputStream extends FilterOutputStream +{ + public final static int LONGFILE_ERROR = 0; + public final static int LONGFILE_TRUNCATE = 1; + public final static int LONGFILE_GNU = 2; + protected int longFileMode = LONGFILE_ERROR; + protected byte[] assemBuf; + protected int assemLen; + protected TarBuffer buffer; + protected int currBytes; + protected int currSize; + + protected boolean debug; + protected byte[] oneBuf; + protected byte[] recordBuf; + + public TarOutputStream( OutputStream os ) + { + this( os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarOutputStream( OutputStream os, int blockSize ) + { + this( os, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarOutputStream( OutputStream os, int blockSize, int recordSize ) + { + super( os ); + + this.buffer = new TarBuffer( os, blockSize, recordSize ); + this.debug = false; + this.assemLen = 0; + this.assemBuf = new byte[recordSize]; + this.recordBuf = new byte[recordSize]; + this.oneBuf = new byte[1]; + } + + /** + * Sets the debugging flag in this stream's TarBuffer. + * + * @param debug The new BufferDebug value + */ + public void setBufferDebug( boolean debug ) + { + this.buffer.setDebug( debug ); + } + + + /** + * Sets the debugging flag. + * + * @param debugF True to turn on debugging. + */ + public void setDebug( boolean debugF ) + { + this.debug = debugF; + } + + public void setLongFileMode( int longFileMode ) + { + this.longFileMode = longFileMode; + } + + /** + * Get the record size being used by this stream's TarBuffer. + * + * @return The TarBuffer record size. + */ + public int getRecordSize() + { + return this.buffer.getRecordSize(); + } + + /** + * Ends the TAR archive and closes the underlying OutputStream. This means + * that finish() is called followed by calling the TarBuffer's close(). + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + this.finish(); + this.buffer.close(); + } + + /** + * Close an entry. This method MUST be called for all file entries that + * contain data. The reason is that we must buffer data written to the + * stream in order to satisfy the buffer's record based writes. Thus, there + * may be data fragments still being assembled that must be written to the + * output stream before this entry is closed and the next entry written. + * + * @exception IOException Description of Exception + */ + public void closeEntry() + throws IOException + { + if( this.assemLen > 0 ) + { + for( int i = this.assemLen; i < this.assemBuf.length; ++i ) + { + this.assemBuf[i] = 0; + } + + this.buffer.writeRecord( this.assemBuf ); + + this.currBytes += this.assemLen; + this.assemLen = 0; + } + + if( this.currBytes < this.currSize ) + { + throw new IOException( "entry closed at '" + this.currBytes + + "' before the '" + this.currSize + + "' bytes specified in the header were written" ); + } + } + + /** + * Ends the TAR archive without closing the underlying OutputStream. The + * result is that the EOF record of nulls is written. + * + * @exception IOException Description of Exception + */ + public void finish() + throws IOException + { + this.writeEOFRecord(); + } + + /** + * Put an entry on the output stream. This writes the entry's header record + * and positions the output stream for writing the contents of the entry. + * Once this method is called, the stream is ready for calls to write() to + * write the entry's contents. Once the contents are written, closeEntry() + * MUST be called to ensure that all buffered data is completely + * written to the output stream. + * + * @param entry The TarEntry to be written to the archive. + * @exception IOException Description of Exception + */ + public void putNextEntry( TarEntry entry ) + throws IOException + { + if( entry.getName().length() >= TarConstants.NAMELEN ) + { + + if( longFileMode == LONGFILE_GNU ) + { + // create a TarEntry for the LongLink, the contents + // of which are the entry's name + TarEntry longLinkEntry = new TarEntry( TarConstants.GNU_LONGLINK, + TarConstants.LF_GNUTYPE_LONGNAME ); + + longLinkEntry.setSize( entry.getName().length() + 1 ); + putNextEntry( longLinkEntry ); + write( entry.getName().getBytes() ); + write( 0 ); + closeEntry(); + } + else if( longFileMode != LONGFILE_TRUNCATE ) + { + throw new RuntimeException( "file name '" + entry.getName() + + "' is too long ( > " + + TarConstants.NAMELEN + " bytes)" ); + } + } + + entry.writeEntryHeader( this.recordBuf ); + this.buffer.writeRecord( this.recordBuf ); + + this.currBytes = 0; + + if( entry.isDirectory() ) + { + this.currSize = 0; + } + else + { + this.currSize = ( int )entry.getSize(); + } + } + + /** + * Writes a byte to the current tar archive entry. This method simply calls + * read( byte[], int, int ). + * + * @param b The byte written. + * @exception IOException Description of Exception + */ + public void write( int b ) + throws IOException + { + this.oneBuf[0] = ( byte )b; + + this.write( this.oneBuf, 0, 1 ); + } + + /** + * Writes bytes to the current tar archive entry. This method simply calls + * write( byte[], int, int ). + * + * @param wBuf The buffer to write to the archive. + * @exception IOException Description of Exception + */ + public void write( byte[] wBuf ) + throws IOException + { + this.write( wBuf, 0, wBuf.length ); + } + + /** + * Writes bytes to the current tar archive entry. This method is aware of + * the current entry and will throw an exception if you attempt to write + * bytes past the length specified for the current entry. The method is also + * (painfully) aware of the record buffering required by TarBuffer, and + * manages buffers that are not a multiple of recordsize in length, + * including assembling records from small buffers. + * + * @param wBuf The buffer to write to the archive. + * @param wOffset The offset in the buffer from which to get bytes. + * @param numToWrite The number of bytes to write. + * @exception IOException Description of Exception + */ + public void write( byte[] wBuf, int wOffset, int numToWrite ) + throws IOException + { + if( ( this.currBytes + numToWrite ) > this.currSize ) + { + throw new IOException( "request to write '" + numToWrite + + "' bytes exceeds size in header of '" + + this.currSize + "' bytes" ); + // + // We have to deal with assembly!!! + // The programmer can be writing little 32 byte chunks for all + // we know, and we must assemble complete records for writing. + // REVIEW Maybe this should be in TarBuffer? Could that help to + // eliminate some of the buffer copying. + // + } + + if( this.assemLen > 0 ) + { + if( ( this.assemLen + numToWrite ) >= this.recordBuf.length ) + { + int aLen = this.recordBuf.length - this.assemLen; + + System.arraycopy( this.assemBuf, 0, this.recordBuf, 0, + this.assemLen ); + System.arraycopy( wBuf, wOffset, this.recordBuf, + this.assemLen, aLen ); + this.buffer.writeRecord( this.recordBuf ); + + this.currBytes += this.recordBuf.length; + wOffset += aLen; + numToWrite -= aLen; + this.assemLen = 0; + } + else + { + System.arraycopy( wBuf, wOffset, this.assemBuf, this.assemLen, + numToWrite ); + + wOffset += numToWrite; + this.assemLen += numToWrite; + numToWrite -= numToWrite; + } + } + + // + // When we get here we have EITHER: + // o An empty "assemble" buffer. + // o No bytes to write (numToWrite == 0) + // + while( numToWrite > 0 ) + { + if( numToWrite < this.recordBuf.length ) + { + System.arraycopy( wBuf, wOffset, this.assemBuf, this.assemLen, + numToWrite ); + + this.assemLen += numToWrite; + + break; + } + + this.buffer.writeRecord( wBuf, wOffset ); + + int num = this.recordBuf.length; + + this.currBytes += num; + numToWrite -= num; + wOffset += num; + } + } + + /** + * Write an EOF (end of archive) record to the tar archive. An EOF record + * consists of a record of all zeros. + * + * @exception IOException Description of Exception + */ + private void writeEOFRecord() + throws IOException + { + for( int i = 0; i < this.recordBuf.length; ++i ) + { + this.recordBuf[i] = 0; + } + + this.buffer.writeRecord( this.recordBuf ); + } +} + diff --git a/proposal/myrmidon/src/main/org/apache/tools/tar/TarUtils.java b/proposal/myrmidon/src/main/org/apache/tools/tar/TarUtils.java new file mode 100644 index 000000000..40bf84d59 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/tar/TarUtils.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; + +/** + * This class provides static utility methods to work with byte streams. + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ +public class TarUtils +{ + + /** + * Parse the checksum octal integer from a header buffer. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param value Description of Parameter + * @param buf Description of Parameter + * @return The integer value of the entry's checksum. + */ + public static int getCheckSumOctalBytes( long value, byte[] buf, int offset, int length ) + { + getOctalBytes( value, buf, offset, length ); + + buf[offset + length - 1] = ( byte )' '; + buf[offset + length - 2] = 0; + + return offset + length; + } + + /** + * Parse an octal long integer from a header buffer. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param value Description of Parameter + * @param buf Description of Parameter + * @return The long value of the octal bytes. + */ + public static int getLongOctalBytes( long value, byte[] buf, int offset, int length ) + { + byte[] temp = new byte[length + 1]; + + getOctalBytes( value, temp, 0, length + 1 ); + System.arraycopy( temp, 0, buf, offset, length ); + + return offset + length; + } + + /** + * Determine the number of bytes in an entry name. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param name Description of Parameter + * @param buf Description of Parameter + * @return The number of bytes in a header's entry name. + */ + public static int getNameBytes( StringBuffer name, byte[] buf, int offset, int length ) + { + int i; + + for( i = 0; i < length && i < name.length(); ++i ) + { + buf[offset + i] = ( byte )name.charAt( i ); + } + + for( ; i < length; ++i ) + { + buf[offset + i] = 0; + } + + return offset + length; + } + + /** + * Parse an octal integer from a header buffer. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param value Description of Parameter + * @param buf Description of Parameter + * @return The integer value of the octal bytes. + */ + public static int getOctalBytes( long value, byte[] buf, int offset, int length ) + { + byte[] result = new byte[length]; + int idx = length - 1; + + buf[offset + idx] = 0; + --idx; + buf[offset + idx] = ( byte )' '; + --idx; + + if( value == 0 ) + { + buf[offset + idx] = ( byte )'0'; + --idx; + } + else + { + for( long val = value; idx >= 0 && val > 0; --idx ) + { + buf[offset + idx] = ( byte )( ( byte )'0' + ( byte )( val & 7 ) ); + val = val >> 3; + } + } + + for( ; idx >= 0; --idx ) + { + buf[offset + idx] = ( byte )' '; + } + + return offset + length; + } + + /** + * Compute the checksum of a tar entry header. + * + * @param buf The tar entry's header buffer. + * @return The computed checksum. + */ + public static long computeCheckSum( byte[] buf ) + { + long sum = 0; + + for( int i = 0; i < buf.length; ++i ) + { + sum += 255 & buf[i]; + } + + return sum; + } + + /** + * Parse an entry name from a header buffer. + * + * @param header The header buffer from which to parse. + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @return The header's entry name. + */ + public static StringBuffer parseName( byte[] header, int offset, int length ) + { + StringBuffer result = new StringBuffer( length ); + int end = offset + length; + + for( int i = offset; i < end; ++i ) + { + if( header[i] == 0 ) + { + break; + } + + result.append( ( char )header[i] ); + } + + return result; + } + + /** + * Parse an octal string from a header buffer. This is used for the file + * permission mode value. + * + * @param header The header buffer from which to parse. + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @return The long value of the octal string. + */ + public static long parseOctal( byte[] header, int offset, int length ) + { + long result = 0; + boolean stillPadding = true; + int end = offset + length; + + for( int i = offset; i < end; ++i ) + { + if( header[i] == 0 ) + { + break; + } + + if( header[i] == ( byte )' ' || header[i] == '0' ) + { + if( stillPadding ) + { + continue; + } + + if( header[i] == ( byte )' ' ) + { + break; + } + } + + stillPadding = false; + result = ( result << 3 ) + ( header[i] - '0' ); + } + + return result; + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/AsiExtraField.java b/proposal/myrmidon/src/main/org/apache/tools/zip/AsiExtraField.java new file mode 100644 index 000000000..e2e3c9122 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/AsiExtraField.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.util.zip.CRC32; +import java.util.zip.ZipException; + +/** + * Adds Unix file permission and UID/GID fields as well as symbolic link + * handling.

          + * + * This class uses the ASi extra field in the format:

          + *         Value         Size            Description
          + *         -----         ----            -----------
          + * (Unix3) 0x756e        Short           tag for this extra block type
          + *         TSize         Short           total data size for this block
          + *         CRC           Long            CRC-32 of the remaining data
          + *         Mode          Short           file permissions
          + *         SizDev        Long            symlink'd size OR major/minor dev num
          + *         UID           Short           user ID
          + *         GID           Short           group ID
          + *         (var.)        variable        symbolic link filename
          + * 
          taken from appnote.iz (Info-ZIP note, 981119) found at + * ftp://ftp.uu.net/pub/archiving/zip/doc/

          + * + * Short is two bytes and Long is four bytes in big endian byte and word order, + * device numbers are currently not supported.

          + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable +{ + + private final static ZipShort HEADER_ID = new ZipShort( 0x756E ); + + /** + * Standard Unix stat(2) file mode. + * + * @since 1.1 + */ + private int mode = 0; + /** + * User ID. + * + * @since 1.1 + */ + private int uid = 0; + /** + * Group ID. + * + * @since 1.1 + */ + private int gid = 0; + /** + * File this entry points to, if it is a symbolic link.

          + * + * empty string - if entry is not a symbolic link.

          + * + * @since 1.1 + */ + private String link = ""; + /** + * Is this an entry for a directory? + * + * @since 1.1 + */ + private boolean dirFlag = false; + + /** + * Instance used to calculate checksums. + * + * @since 1.1 + */ + private CRC32 crc = new CRC32(); + + public AsiExtraField() { } + + /** + * Indicate whether this entry is a directory. + * + * @param dirFlag The new Directory value + * @since 1.1 + */ + public void setDirectory( boolean dirFlag ) + { + this.dirFlag = dirFlag; + mode = getMode( mode ); + } + + /** + * Set the group id. + * + * @param gid The new GroupId value + * @since 1.1 + */ + public void setGroupId( int gid ) + { + this.gid = gid; + } + + /** + * Indicate that this entry is a symbolic link to the given filename. + * + * @param name Name of the file this entry links to, empty String if it is + * not a symbolic link. + * @since 1.1 + */ + public void setLinkedFile( String name ) + { + link = name; + mode = getMode( mode ); + } + + /** + * File mode of this file. + * + * @param mode The new Mode value + * @since 1.1 + */ + public void setMode( int mode ) + { + this.mode = getMode( mode ); + } + + /** + * Set the user id. + * + * @param uid The new UserId value + * @since 1.1 + */ + public void setUserId( int uid ) + { + this.uid = uid; + } + + /** + * Delegate to local file data. + * + * @return The CentralDirectoryData value + * @since 1.1 + */ + public byte[] getCentralDirectoryData() + { + return getLocalFileDataData(); + } + + /** + * Delegate to local file data. + * + * @return The CentralDirectoryLength value + * @since 1.1 + */ + public ZipShort getCentralDirectoryLength() + { + return getLocalFileDataLength(); + } + + /** + * Get the group id. + * + * @return The GroupId value + * @since 1.1 + */ + public int getGroupId() + { + return gid; + } + + /** + * The Header-ID. + * + * @return The HeaderId value + * @since 1.1 + */ + public ZipShort getHeaderId() + { + return HEADER_ID; + } + + /** + * Name of linked file + * + * @return name of the file this entry links to if it is a symbolic link, + * the empty string otherwise. + * @since 1.1 + */ + public String getLinkedFile() + { + return link; + } + + /** + * The actual data to put into local file data - without Header-ID or length + * specifier. + * + * @return The LocalFileDataData value + * @since 1.1 + */ + public byte[] getLocalFileDataData() + { + // CRC will be added later + byte[] data = new byte[getLocalFileDataLength().getValue() - 4]; + System.arraycopy( ( new ZipShort( getMode() ) ).getBytes(), 0, data, 0, 2 ); + + byte[] linkArray = getLinkedFile().getBytes(); + System.arraycopy( ( new ZipLong( linkArray.length ) ).getBytes(), + 0, data, 2, 4 ); + + System.arraycopy( ( new ZipShort( getUserId() ) ).getBytes(), + 0, data, 6, 2 ); + System.arraycopy( ( new ZipShort( getGroupId() ) ).getBytes(), + 0, data, 8, 2 ); + + System.arraycopy( linkArray, 0, data, 10, linkArray.length ); + + crc.reset(); + crc.update( data ); + long checksum = crc.getValue(); + + byte[] result = new byte[data.length + 4]; + System.arraycopy( ( new ZipLong( checksum ) ).getBytes(), 0, result, 0, 4 ); + System.arraycopy( data, 0, result, 4, data.length ); + return result; + } + + /** + * Length of the extra field in the local file data - without Header-ID or + * length specifier. + * + * @return The LocalFileDataLength value + * @since 1.1 + */ + public ZipShort getLocalFileDataLength() + { + return new ZipShort( 4// CRC + + 2// Mode + + 4// SizDev + + 2// UID + + 2// GID + + getLinkedFile().getBytes().length ); + } + + /** + * File mode of this file. + * + * @return The Mode value + * @since 1.1 + */ + public int getMode() + { + return mode; + } + + /** + * Get the user id. + * + * @return The UserId value + * @since 1.1 + */ + public int getUserId() + { + return uid; + } + + /** + * Is this entry a directory? + * + * @return The Directory value + * @since 1.1 + */ + public boolean isDirectory() + { + return dirFlag && !isLink(); + } + + /** + * Is this entry a symbolic link? + * + * @return The Link value + * @since 1.1 + */ + public boolean isLink() + { + return getLinkedFile().length() != 0; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param data Description of Parameter + * @param offset Description of Parameter + * @param length Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + public void parseFromLocalFileData( byte[] data, int offset, int length ) + throws ZipException + { + + long givenChecksum = ( new ZipLong( data, offset ) ).getValue(); + byte[] tmp = new byte[length - 4]; + System.arraycopy( data, offset + 4, tmp, 0, length - 4 ); + crc.reset(); + crc.update( tmp ); + long realChecksum = crc.getValue(); + if( givenChecksum != realChecksum ) + { + throw new ZipException( "bad CRC checksum " + + Long.toHexString( givenChecksum ) + + " instead of " + + Long.toHexString( realChecksum ) ); + } + + int newMode = ( new ZipShort( tmp, 0 ) ).getValue(); + byte[] linkArray = new byte[( int )( new ZipLong( tmp, 2 ) ).getValue()]; + uid = ( new ZipShort( tmp, 6 ) ).getValue(); + gid = ( new ZipShort( tmp, 8 ) ).getValue(); + + if( linkArray.length == 0 ) + { + link = ""; + } + else + { + System.arraycopy( tmp, 10, linkArray, 0, linkArray.length ); + link = new String( linkArray ); + } + setDirectory( ( newMode & DIR_FLAG ) != 0 ); + setMode( newMode ); + } + + /** + * Get the file mode for given permissions with the correct file type. + * + * @param mode Description of Parameter + * @return The Mode value + * @since 1.1 + */ + protected int getMode( int mode ) + { + int type = FILE_FLAG; + if( isLink() ) + { + type = LINK_FLAG; + } + else if( isDirectory() ) + { + type = DIR_FLAG; + } + return type | ( mode & PERM_MASK ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/ExtraFieldUtils.java b/proposal/myrmidon/src/main/org/apache/tools/zip/ExtraFieldUtils.java new file mode 100644 index 000000000..0b97ce6a7 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/ExtraFieldUtils.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.util.Hashtable; +import java.util.Vector; +import java.util.zip.ZipException; + +/** + * ZipExtraField related methods + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ExtraFieldUtils +{ + + /** + * Static registry of known extra fields. + * + * @since 1.1 + */ + private static Hashtable implementations; + + static + { + implementations = new Hashtable(); + register( AsiExtraField.class ); + } + + /** + * Create an instance of the approriate ExtraField, falls back to {@link + * UnrecognizedExtraField UnrecognizedExtraField}. + * + * @param headerId Description of Parameter + * @return Description of the Returned Value + * @exception InstantiationException Description of Exception + * @exception IllegalAccessException Description of Exception + * @since 1.1 + */ + public static ZipExtraField createExtraField( ZipShort headerId ) + throws InstantiationException, IllegalAccessException + { + Class c = ( Class )implementations.get( headerId ); + if( c != null ) + { + return ( ZipExtraField )c.newInstance(); + } + UnrecognizedExtraField u = new UnrecognizedExtraField(); + u.setHeaderId( headerId ); + return u; + } + + /** + * Merges the central directory fields of the given ZipExtraFields. + * + * @param data Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public static byte[] mergeCentralDirectoryData( ZipExtraField[] data ) + { + int sum = 4 * data.length; + for( int i = 0; i < data.length; i++ ) + { + sum += data[i].getCentralDirectoryLength().getValue(); + } + byte[] result = new byte[sum]; + int start = 0; + for( int i = 0; i < data.length; i++ ) + { + System.arraycopy( data[i].getHeaderId().getBytes(), + 0, result, start, 2 ); + System.arraycopy( data[i].getCentralDirectoryLength().getBytes(), + 0, result, start + 2, 2 ); + byte[] local = data[i].getCentralDirectoryData(); + System.arraycopy( local, 0, result, start + 4, local.length ); + start += ( local.length + 4 ); + } + return result; + } + + /** + * Merges the local file data fields of the given ZipExtraFields. + * + * @param data Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public static byte[] mergeLocalFileDataData( ZipExtraField[] data ) + { + int sum = 4 * data.length; + for( int i = 0; i < data.length; i++ ) + { + sum += data[i].getLocalFileDataLength().getValue(); + } + byte[] result = new byte[sum]; + int start = 0; + for( int i = 0; i < data.length; i++ ) + { + System.arraycopy( data[i].getHeaderId().getBytes(), + 0, result, start, 2 ); + System.arraycopy( data[i].getLocalFileDataLength().getBytes(), + 0, result, start + 2, 2 ); + byte[] local = data[i].getLocalFileDataData(); + System.arraycopy( local, 0, result, start + 4, local.length ); + start += ( local.length + 4 ); + } + return result; + } + + /** + * Split the array into ExtraFields and populate them with the give data. + * + * @param data Description of Parameter + * @return Description of the Returned Value + * @exception ZipException Description of Exception + * @since 1.1 + */ + public static ZipExtraField[] parse( byte[] data ) + throws ZipException + { + Vector v = new Vector(); + int start = 0; + while( start <= data.length - 4 ) + { + ZipShort headerId = new ZipShort( data, start ); + int length = ( new ZipShort( data, start + 2 ) ).getValue(); + if( start + 4 + length > data.length ) + { + throw new ZipException( "data starting at " + start + " is in unknown format" ); + } + try + { + ZipExtraField ze = createExtraField( headerId ); + ze.parseFromLocalFileData( data, start + 4, length ); + v.addElement( ze ); + } + catch( InstantiationException ie ) + { + throw new ZipException( ie.getMessage() ); + } + catch( IllegalAccessException iae ) + { + throw new ZipException( iae.getMessage() ); + } + start += ( length + 4 ); + } + if( start != data.length ) + {// array not exhausted + throw new ZipException( "data starting at " + start + " is in unknown format" ); + } + + ZipExtraField[] result = new ZipExtraField[v.size()]; + v.copyInto( result ); + return result; + } + + /** + * Register a ZipExtraField implementation.

          + * + * The given class must have a no-arg constructor and implement the {@link + * ZipExtraField ZipExtraField interface}.

          + * + * @param c Description of Parameter + * @since 1.1 + */ + public static void register( Class c ) + { + try + { + ZipExtraField ze = ( ZipExtraField )c.newInstance(); + implementations.put( ze.getHeaderId(), c ); + } + catch( ClassCastException cc ) + { + throw new RuntimeException( c + + " doesn\'t implement ZipExtraField" ); + } + catch( InstantiationException ie ) + { + throw new RuntimeException( c + " is not a concrete class" ); + } + catch( IllegalAccessException ie ) + { + throw new RuntimeException( c + + "\'s no-arg constructor is not public" ); + } + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/UnixStat.java b/proposal/myrmidon/src/main/org/apache/tools/zip/UnixStat.java new file mode 100644 index 000000000..e238ed32e --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/UnixStat.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Constants from stat.h on Unix systems. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public interface UnixStat +{ + + /** + * Bits used for permissions (and sticky bit) + * + * @since 1.1 + */ + int PERM_MASK = 07777; + /** + * Indicates symbolic links. + * + * @since 1.1 + */ + int LINK_FLAG = 0120000; + /** + * Indicates plain files. + * + * @since 1.1 + */ + int FILE_FLAG = 0100000; + /** + * Indicates directories. + * + * @since 1.1 + */ + int DIR_FLAG = 040000; + + // ---------------------------------------------------------- + // somewhat arbitrary choices that are quite common for shared + // installations + // ----------------------------------------------------------- + + /** + * Default permissions for symbolic links. + * + * @since 1.1 + */ + int DEFAULT_LINK_PERM = 0777; + /** + * Default permissions for directories. + * + * @since 1.1 + */ + int DEFAULT_DIR_PERM = 0755; + /** + * Default permissions for plain files. + * + * @since 1.1 + */ + int DEFAULT_FILE_PERM = 0644; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/UnrecognizedExtraField.java b/proposal/myrmidon/src/main/org/apache/tools/zip/UnrecognizedExtraField.java new file mode 100644 index 000000000..092e1c60b --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/UnrecognizedExtraField.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Simple placeholder for all those extra fields we don't want to deal with.

          + * + * Assumes local file data and central directory entries are identical - unless + * told the opposite.

          + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class UnrecognizedExtraField implements ZipExtraField +{ + + /** + * Extra field data in central directory - without Header-ID or length + * specifier. + * + * @since 1.1 + */ + private byte[] centralData; + + /** + * The Header-ID. + * + * @since 1.1 + */ + private ZipShort headerId; + + /** + * Extra field data in local file data - without Header-ID or length + * specifier. + * + * @since 1.1 + */ + private byte[] localData; + + public void setCentralDirectoryData( byte[] data ) + { + centralData = data; + } + + public void setHeaderId( ZipShort headerId ) + { + this.headerId = headerId; + } + + public void setLocalFileDataData( byte[] data ) + { + localData = data; + } + + public byte[] getCentralDirectoryData() + { + if( centralData != null ) + { + return centralData; + } + return getLocalFileDataData(); + } + + public ZipShort getCentralDirectoryLength() + { + if( centralData != null ) + { + return new ZipShort( centralData.length ); + } + return getLocalFileDataLength(); + } + + public ZipShort getHeaderId() + { + return headerId; + } + + public byte[] getLocalFileDataData() + { + return localData; + } + + public ZipShort getLocalFileDataLength() + { + return new ZipShort( localData.length ); + } + + public void parseFromLocalFileData( byte[] data, int offset, int length ) + { + byte[] tmp = new byte[length]; + System.arraycopy( data, offset, tmp, 0, length ); + setLocalFileDataData( tmp ); + } +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/ZipEntry.java b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipEntry.java new file mode 100644 index 000000000..078203ae2 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipEntry.java @@ -0,0 +1,435 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Vector; +import java.util.zip.ZipException; + +/** + * Extension that adds better handling of extra fields and provides access to + * the internal and external file attributes. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipEntry extends java.util.zip.ZipEntry +{ + + /** + * Helper for JDK 1.1 + * + * @since 1.2 + */ + private static Method setCompressedSizeMethod = null; + /** + * Helper for JDK 1.1 + * + * @since 1.2 + */ + private static Object lockReflection = new Object(); + /** + * Helper for JDK 1.1 + * + * @since 1.2 + */ + private static boolean triedToGetMethod = false; + + private int internalAttributes = 0; + private long externalAttributes = 0; + private Vector extraFields = new Vector(); + + /** + * Helper for JDK 1.1 <-> 1.2 incompatibility. + * + * @since 1.2 + */ + private Long compressedSize = null; + + /** + * Creates a new zip entry with the specified name. + * + * @param name Description of Parameter + * @since 1.1 + */ + public ZipEntry( String name ) + { + super( name ); + } + + /** + * Creates a new zip entry with fields taken from the specified zip entry. + * + * @param entry Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + public ZipEntry( java.util.zip.ZipEntry entry ) + throws ZipException + { + /* + * REVISIT: call super(entry) instead of this stuff in Ant2, + * "copy constructor" has not been available in JDK 1.1 + */ + super( entry.getName() ); + + setComment( entry.getComment() ); + setMethod( entry.getMethod() ); + setTime( entry.getTime() ); + + long size = entry.getSize(); + if( size > 0 ) + { + setSize( size ); + } + long cSize = entry.getCompressedSize(); + if( cSize > 0 ) + { + setComprSize( cSize ); + } + long crc = entry.getCrc(); + if( crc > 0 ) + { + setCrc( crc ); + } + + byte[] extra = entry.getExtra(); + if( extra != null ) + { + setExtraFields( ExtraFieldUtils.parse( extra ) ); + } + else + { + // initializes extra data to an empty byte array + setExtra(); + } + } + + /** + * Creates a new zip entry with fields taken from the specified zip entry. + * + * @param entry Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + public ZipEntry( ZipEntry entry ) + throws ZipException + { + this( ( java.util.zip.ZipEntry )entry ); + setInternalAttributes( entry.getInternalAttributes() ); + setExternalAttributes( entry.getExternalAttributes() ); + setExtraFields( entry.getExtraFields() ); + } + + /** + * Try to get a handle to the setCompressedSize method. + * + * @since 1.2 + */ + private static void checkSCS() + { + if( !triedToGetMethod ) + { + synchronized( lockReflection ) + { + triedToGetMethod = true; + try + { + setCompressedSizeMethod = + java.util.zip.ZipEntry.class.getMethod( "setCompressedSize", + new Class[]{Long.TYPE} ); + } + catch( NoSuchMethodException nse ) + { + } + } + } + } + + /** + * Are we running JDK 1.2 or higher? + * + * @return Description of the Returned Value + * @since 1.2 + */ + private static boolean haveSetCompressedSize() + { + checkSCS(); + return setCompressedSizeMethod != null; + } + + /** + * Invoke setCompressedSize via reflection. + * + * @param ze Description of Parameter + * @param size Description of Parameter + * @since 1.2 + */ + private static void performSetCompressedSize( ZipEntry ze, long size ) + { + Long[] s = {new Long( size )}; + try + { + setCompressedSizeMethod.invoke( ze, s ); + } + catch( InvocationTargetException ite ) + { + Throwable nested = ite.getTargetException(); + throw new RuntimeException( "Exception setting the compressed size " + + "of " + ze + ": " + + nested.getMessage() ); + } + catch( Throwable other ) + { + throw new RuntimeException( "Exception setting the compressed size " + + "of " + ze + ": " + + other.getMessage() ); + } + } + + /** + * Make this class work in JDK 1.1 like a 1.2 class.

          + * + * This either stores the size for later usage or invokes setCompressedSize + * via reflection.

          + * + * @param size The new ComprSize value + * @since 1.2 + */ + public void setComprSize( long size ) + { + if( haveSetCompressedSize() ) + { + performSetCompressedSize( this, size ); + } + else + { + compressedSize = new Long( size ); + } + } + + /** + * Sets the external file attributes. + * + * @param value The new ExternalAttributes value + * @since 1.1 + */ + public void setExternalAttributes( long value ) + { + externalAttributes = value; + } + + /** + * Throws an Exception if extra data cannot be parsed into extra fields. + * + * @param extra The new Extra value + * @exception RuntimeException Description of Exception + * @since 1.1 + */ + public void setExtra( byte[] extra ) + throws RuntimeException + { + try + { + setExtraFields( ExtraFieldUtils.parse( extra ) ); + } + catch( Exception e ) + { + throw new RuntimeException( e.getMessage() ); + } + } + + /** + * Replaces all currently attached extra fields with the new array. + * + * @param fields The new ExtraFields value + * @since 1.1 + */ + public void setExtraFields( ZipExtraField[] fields ) + { + extraFields.removeAllElements(); + for( int i = 0; i < fields.length; i++ ) + { + extraFields.addElement( fields[i] ); + } + setExtra(); + } + + /** + * Sets the internal file attributes. + * + * @param value The new InternalAttributes value + * @since 1.1 + */ + public void setInternalAttributes( int value ) + { + internalAttributes = value; + } + + /** + * Retrieves the extra data for the central directory. + * + * @return The CentralDirectoryExtra value + * @since 1.1 + */ + public byte[] getCentralDirectoryExtra() + { + return ExtraFieldUtils.mergeCentralDirectoryData( getExtraFields() ); + } + + /** + * Override to make this class work in JDK 1.1 like a 1.2 class. + * + * @return The CompressedSize value + * @since 1.2 + */ + public long getCompressedSize() + { + if( compressedSize != null ) + { + // has been set explicitly and we are running in a 1.1 VM + return compressedSize.longValue(); + } + return super.getCompressedSize(); + } + + /** + * Retrieves the external file attributes. + * + * @return The ExternalAttributes value + * @since 1.1 + */ + public long getExternalAttributes() + { + return externalAttributes; + } + + /** + * Retrieves extra fields. + * + * @return The ExtraFields value + * @since 1.1 + */ + public ZipExtraField[] getExtraFields() + { + ZipExtraField[] result = new ZipExtraField[extraFields.size()]; + extraFields.copyInto( result ); + return result; + } + + /** + * Retrieves the internal file attributes. + * + * @return The InternalAttributes value + * @since 1.1 + */ + public int getInternalAttributes() + { + return internalAttributes; + } + + /** + * Retrieves the extra data for the local file data. + * + * @return The LocalFileDataExtra value + * @since 1.1 + */ + public byte[] getLocalFileDataExtra() + { + byte[] extra = getExtra(); + return extra != null ? extra : new byte[0]; + } + + /** + * Adds an extra fields - replacing an already present extra field of the + * same type. + * + * @param ze The feature to be added to the ExtraField attribute + * @since 1.1 + */ + public void addExtraField( ZipExtraField ze ) + { + ZipShort type = ze.getHeaderId(); + boolean done = false; + for( int i = 0; !done && i < extraFields.size(); i++ ) + { + if( ( ( ZipExtraField )extraFields.elementAt( i ) ).getHeaderId().equals( type ) ) + { + extraFields.setElementAt( ze, i ); + done = true; + } + } + if( !done ) + { + extraFields.addElement( ze ); + } + setExtra(); + } + + /** + * Overwrite clone + * + * @return Description of the Returned Value + * @since 1.1 + */ + public Object clone() + { + ZipEntry e = null; + try + { + e = new ZipEntry( ( java.util.zip.ZipEntry )super.clone() ); + } + catch( Exception ex ) + { + // impossible as extra data is in correct format + ex.printStackTrace(); + } + e.setInternalAttributes( getInternalAttributes() ); + e.setExternalAttributes( getExternalAttributes() ); + e.setExtraFields( getExtraFields() ); + return e; + } + + /** + * Remove an extra fields. + * + * @param type Description of Parameter + * @since 1.1 + */ + public void removeExtraField( ZipShort type ) + { + boolean done = false; + for( int i = 0; !done && i < extraFields.size(); i++ ) + { + if( ( ( ZipExtraField )extraFields.elementAt( i ) ).getHeaderId().equals( type ) ) + { + extraFields.removeElementAt( i ); + done = true; + } + } + if( !done ) + { + throw new java.util.NoSuchElementException(); + } + setExtra(); + } + + /** + * Unfortunately {@link java.util.zip.ZipOutputStream + * java.util.zip.ZipOutputStream} seems to access the extra data directly, + * so overriding getExtra doesn't help - we need to modify super's data + * directly. + * + * @since 1.1 + */ + protected void setExtra() + { + super.setExtra( ExtraFieldUtils.mergeLocalFileDataData( getExtraFields() ) ); + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/ZipExtraField.java b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipExtraField.java new file mode 100644 index 000000000..534530706 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipExtraField.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.util.zip.ZipException; + +/** + * General format of extra field data.

          + * + * Extra fields usually appear twice per file, once in the local file data and + * once in the central directory. Usually they are the same, but they don't have + * to be. {@link java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} + * will only use the local file data in both places.

          + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public interface ZipExtraField +{ + + /** + * The Header-ID. + * + * @return The HeaderId value + * @since 1.1 + */ + ZipShort getHeaderId(); + + /** + * Length of the extra field in the local file data - without Header-ID or + * length specifier. + * + * @return The LocalFileDataLength value + * @since 1.1 + */ + ZipShort getLocalFileDataLength(); + + /** + * Length of the extra field in the central directory - without Header-ID or + * length specifier. + * + * @return The CentralDirectoryLength value + * @since 1.1 + */ + ZipShort getCentralDirectoryLength(); + + /** + * The actual data to put into local file data - without Header-ID or length + * specifier. + * + * @return The LocalFileDataData value + * @since 1.1 + */ + byte[] getLocalFileDataData(); + + /** + * The actual data to put central directory - without Header-ID or length + * specifier. + * + * @return The CentralDirectoryData value + * @since 1.1 + */ + byte[] getCentralDirectoryData(); + + /** + * Populate data from this array as if it was in local file data. + * + * @param data Description of Parameter + * @param offset Description of Parameter + * @param length Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + void parseFromLocalFileData( byte[] data, int offset, int length ) + throws ZipException; +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/ZipLong.java b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipLong.java new file mode 100644 index 000000000..4b3573770 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipLong.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Utility class that represents a four byte integer with conversion rules for + * the big endian byte order of ZIP files. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipLong implements Cloneable +{ + + private long value; + + /** + * Create instance from a number. + * + * @param value Description of Parameter + * @since 1.1 + */ + public ZipLong( long value ) + { + this.value = value; + } + + /** + * Create instance from bytes. + * + * @param bytes Description of Parameter + * @since 1.1 + */ + public ZipLong( byte[] bytes ) + { + this( bytes, 0 ); + } + + /** + * Create instance from the four bytes starting at offset. + * + * @param bytes Description of Parameter + * @param offset Description of Parameter + * @since 1.1 + */ + public ZipLong( byte[] bytes, int offset ) + { + value = ( bytes[offset + 3] << 24 ) & 0xFF000000l; + value += ( bytes[offset + 2] << 16 ) & 0xFF0000; + value += ( bytes[offset + 1] << 8 ) & 0xFF00; + value += ( bytes[offset] & 0xFF ); + } + + /** + * Get value as two bytes in big endian byte order. + * + * @return The Bytes value + * @since 1.1 + */ + public byte[] getBytes() + { + byte[] result = new byte[4]; + result[0] = ( byte )( ( value & 0xFF ) ); + result[1] = ( byte )( ( value & 0xFF00 ) >> 8 ); + result[2] = ( byte )( ( value & 0xFF0000 ) >> 16 ); + result[3] = ( byte )( ( value & 0xFF000000l ) >> 24 ); + return result; + } + + /** + * Get value as Java int. + * + * @return The Value value + * @since 1.1 + */ + public long getValue() + { + return value; + } + + /** + * Override to make two instances with same value equal. + * + * @param o Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public boolean equals( Object o ) + { + if( o == null || !( o instanceof ZipLong ) ) + { + return false; + } + return value == ( ( ZipLong )o ).getValue(); + } + + /** + * Override to make two instances with same value equal. + * + * @return Description of the Returned Value + * @since 1.1 + */ + public int hashCode() + { + return ( int )value; + } + +}// ZipLong diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/ZipOutputStream.java b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipOutputStream.java new file mode 100644 index 000000000..5ab382814 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipOutputStream.java @@ -0,0 +1,712 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Date; +import java.util.Hashtable; +import java.util.Vector; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.ZipException; + +/** + * Reimplementation of {@link java.util.zip.ZipOutputStream + * java.util.zip.ZipOutputStream} that does handle the extended functionality of + * this package, especially internal/external file attributes and extra fields + * with different layouts for local file data and central directory entries.

          + * + * This implementation will use a Data Descriptor to store size and CRC + * information for DEFLATED entries, this means, you don't need to calculate + * them yourself. Unfortunately this is not possible for the STORED method, here + * setting the CRC and uncompressed size information is required before {@link + * #putNextEntry putNextEntry} will be called.

          + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipOutputStream extends DeflaterOutputStream +{ + + /** + * Helper, a 0 as ZipShort. + * + * @since 1.1 + */ + private final static byte[] ZERO = {0, 0}; + + /** + * Helper, a 0 as ZipLong. + * + * @since 1.1 + */ + private final static byte[] LZERO = {0, 0, 0, 0}; + + /** + * Compression method for deflated entries. + * + * @since 1.1 + */ + public final static int DEFLATED = ZipEntry.DEFLATED; + + /** + * Compression method for deflated entries. + * + * @since 1.1 + */ + public final static int STORED = ZipEntry.STORED; + + /* + * Various ZIP constants + */ + /** + * local file header signature + * + * @since 1.1 + */ + protected final static ZipLong LFH_SIG = new ZipLong( 0X04034B50L ); + /** + * data descriptor signature + * + * @since 1.1 + */ + protected final static ZipLong DD_SIG = new ZipLong( 0X08074B50L ); + /** + * central file header signature + * + * @since 1.1 + */ + protected final static ZipLong CFH_SIG = new ZipLong( 0X02014B50L ); + /** + * end of central dir signature + * + * @since 1.1 + */ + protected final static ZipLong EOCD_SIG = new ZipLong( 0X06054B50L ); + + /** + * Smallest date/time ZIP can handle. + * + * @since 1.1 + */ + private final static ZipLong DOS_TIME_MIN = new ZipLong( 0x00002100L ); + + /** + * The file comment. + * + * @since 1.1 + */ + private String comment = ""; + + /** + * Compression level for next entry. + * + * @since 1.1 + */ + private int level = Deflater.DEFAULT_COMPRESSION; + + /** + * Default compression method for next entry. + * + * @since 1.1 + */ + private int method = DEFLATED; + + /** + * List of ZipEntries written so far. + * + * @since 1.1 + */ + private Vector entries = new Vector(); + + /** + * CRC instance to avoid parsing DEFLATED data twice. + * + * @since 1.1 + */ + private CRC32 crc = new CRC32(); + + /** + * Count the bytes written to out. + * + * @since 1.1 + */ + private long written = 0; + + /** + * Data for current entry started here. + * + * @since 1.1 + */ + private long dataStart = 0; + + /** + * Start of central directory. + * + * @since 1.1 + */ + private ZipLong cdOffset = new ZipLong( 0 ); + + /** + * Length of central directory. + * + * @since 1.1 + */ + private ZipLong cdLength = new ZipLong( 0 ); + + /** + * Holds the offsets of the LFH starts for each entry + * + * @since 1.1 + */ + private Hashtable offsets = new Hashtable(); + + /** + * The encoding to use for filenames and the file comment.

          + * + * For a list of possible values see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * . Defaults to the platform's default character encoding.

          + * + * @since 1.3 + */ + private String encoding = null; + + /** + * Current entry. + * + * @since 1.1 + */ + private ZipEntry entry; + + /** + * Creates a new ZIP OutputStream filtering the underlying stream. + * + * @param out Description of Parameter + * @since 1.1 + */ + public ZipOutputStream( OutputStream out ) + { + super( out, new Deflater( Deflater.DEFAULT_COMPRESSION, true ) ); + } + + /** + * Convert a Date object to a DOS date/time field.

          + * + * Stolen from InfoZip's fileio.c

          + * + * @param time Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + protected static ZipLong toDosTime( Date time ) + { + int year = time.getYear() + 1900; + int month = time.getMonth() + 1; + if( year < 1980 ) + { + return DOS_TIME_MIN; + } + long value = ( ( year - 1980 ) << 25 ) + | ( month << 21 ) + | ( time.getDate() << 16 ) + | ( time.getHours() << 11 ) + | ( time.getMinutes() << 5 ) + | ( time.getSeconds() >> 1 ); + + byte[] result = new byte[4]; + result[0] = ( byte )( ( value & 0xFF ) ); + result[1] = ( byte )( ( value & 0xFF00 ) >> 8 ); + result[2] = ( byte )( ( value & 0xFF0000 ) >> 16 ); + result[3] = ( byte )( ( value & 0xFF000000l ) >> 24 ); + return new ZipLong( result ); + } + + /** + * Set the file comment. + * + * @param comment The new Comment value + * @since 1.1 + */ + public void setComment( String comment ) + { + this.comment = comment; + } + + /** + * The encoding to use for filenames and the file comment.

          + * + * For a list of possible values see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * . Defaults to the platform's default character encoding.

          + * + * @param encoding The new Encoding value + * @since 1.3 + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Sets the compression level for subsequent entries.

          + * + * Default is Deflater.DEFAULT_COMPRESSION.

          + * + * @param level The new Level value + * @since 1.1 + */ + public void setLevel( int level ) + { + this.level = level; + } + + /** + * Sets the default compression method for subsequent entries.

          + * + * Default is DEFLATED.

          + * + * @param method The new Method value + * @since 1.1 + */ + public void setMethod( int method ) + { + this.method = method; + } + + /** + * The encoding to use for filenames and the file comment. + * + * @return null if using the platform's default character encoding. + * @since 1.3 + */ + public String getEncoding() + { + return encoding; + } + + /** + * Writes all necessary data for this entry. + * + * @exception IOException Description of Exception + * @since 1.1 + */ + public void closeEntry() + throws IOException + { + if( entry == null ) + { + return; + } + + long realCrc = crc.getValue(); + crc.reset(); + + if( entry.getMethod() == DEFLATED ) + { + def.finish(); + while( !def.finished() ) + { + deflate(); + } + + entry.setSize( def.getTotalIn() ); + entry.setComprSize( def.getTotalOut() ); + entry.setCrc( realCrc ); + + def.reset(); + + written += entry.getCompressedSize(); + } + else + { + if( entry.getCrc() != realCrc ) + { + throw new ZipException( "bad CRC checksum for entry " + + entry.getName() + ": " + + Long.toHexString( entry.getCrc() ) + + " instead of " + + Long.toHexString( realCrc ) ); + } + + if( entry.getSize() != written - dataStart ) + { + throw new ZipException( "bad size for entry " + + entry.getName() + ": " + + entry.getSize() + + " instead of " + + ( written - dataStart ) ); + } + + } + + writeDataDescriptor( entry ); + entry = null; + } + + /* + * Found out by experiment, that DeflaterOutputStream.close() + * will call finish() - so we don't need to override close + * ourselves. + */ + /** + * Finishs writing the contents and closes this as well as the underlying + * stream. + * + * @exception IOException Description of Exception + * @since 1.1 + */ + public void finish() + throws IOException + { + closeEntry(); + cdOffset = new ZipLong( written ); + for( int i = 0; i < entries.size(); i++ ) + { + writeCentralFileHeader( ( ZipEntry )entries.elementAt( i ) ); + } + cdLength = new ZipLong( written - cdOffset.getValue() ); + writeCentralDirectoryEnd(); + offsets.clear(); + entries.removeAllElements(); + } + + /** + * Begin writing next entry. + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + public void putNextEntry( ZipEntry ze ) + throws IOException + { + closeEntry(); + + entry = ze; + entries.addElement( entry ); + + if( entry.getMethod() == -1 ) + {// not specified + entry.setMethod( method ); + } + + if( entry.getTime() == -1 ) + {// not specified + entry.setTime( System.currentTimeMillis() ); + } + + if( entry.getMethod() == STORED ) + { + if( entry.getSize() == -1 ) + { + throw new ZipException( "uncompressed size is required for STORED method" ); + } + if( entry.getCrc() == -1 ) + { + throw new ZipException( "crc checksum is required for STORED method" ); + } + entry.setComprSize( entry.getSize() ); + } + else + { + def.setLevel( level ); + } + writeLocalFileHeader( entry ); + } + + /** + * Writes bytes to ZIP entry.

          + * + * Override is necessary to support STORED entries, as well as calculationg + * CRC automatically for DEFLATED entries.

          + * + * @param b Description of Parameter + * @param offset Description of Parameter + * @param length Description of Parameter + * @exception IOException Description of Exception + */ + public void write( byte[] b, int offset, int length ) + throws IOException + { + if( entry.getMethod() == DEFLATED ) + { + super.write( b, offset, length ); + } + else + { + out.write( b, offset, length ); + written += length; + } + crc.update( b, offset, length ); + } + + /** + * Retrieve the bytes for the given String in the encoding set for this + * Stream. + * + * @param name Description of Parameter + * @return The Bytes value + * @exception ZipException Description of Exception + * @since 1.3 + */ + protected byte[] getBytes( String name ) + throws ZipException + { + if( encoding == null ) + { + return name.getBytes(); + } + else + { + try + { + return name.getBytes( encoding ); + } + catch( UnsupportedEncodingException uee ) + { + throw new ZipException( uee.getMessage() ); + } + } + } + + /** + * Writes the "End of central dir record" + * + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeCentralDirectoryEnd() + throws IOException + { + out.write( EOCD_SIG.getBytes() ); + + // disk numbers + out.write( ZERO ); + out.write( ZERO ); + + // number of entries + byte[] num = ( new ZipShort( entries.size() ) ).getBytes(); + out.write( num ); + out.write( num ); + + // length and location of CD + out.write( cdLength.getBytes() ); + out.write( cdOffset.getBytes() ); + + // ZIP file comment + byte[] data = getBytes( comment ); + out.write( ( new ZipShort( data.length ) ).getBytes() ); + out.write( data ); + } + + /** + * Writes the central file header entry + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeCentralFileHeader( ZipEntry ze ) + throws IOException + { + out.write( CFH_SIG.getBytes() ); + written += 4; + + // version made by + out.write( ( new ZipShort( 20 ) ).getBytes() ); + written += 2; + + // version needed to extract + // general purpose bit flag + if( ze.getMethod() == DEFLATED ) + { + // requires version 2 as we are going to store length info + // in the data descriptor + out.write( ( new ZipShort( 20 ) ).getBytes() ); + + // bit3 set to signal, we use a data descriptor + out.write( ( new ZipShort( 8 ) ).getBytes() ); + } + else + { + out.write( ( new ZipShort( 10 ) ).getBytes() ); + out.write( ZERO ); + } + written += 4; + + // compression method + out.write( ( new ZipShort( ze.getMethod() ) ).getBytes() ); + written += 2; + + // last mod. time and date + out.write( toDosTime( new Date( ze.getTime() ) ).getBytes() ); + written += 4; + + // CRC + // compressed length + // uncompressed length + out.write( ( new ZipLong( ze.getCrc() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getCompressedSize() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); + written += 12; + + // file name length + byte[] name = getBytes( ze.getName() ); + out.write( ( new ZipShort( name.length ) ).getBytes() ); + written += 2; + + // extra field length + byte[] extra = ze.getCentralDirectoryExtra(); + out.write( ( new ZipShort( extra.length ) ).getBytes() ); + written += 2; + + // file comment length + String comm = ze.getComment(); + if( comm == null ) + { + comm = ""; + } + byte[] comment = getBytes( comm ); + out.write( ( new ZipShort( comment.length ) ).getBytes() ); + written += 2; + + // disk number start + out.write( ZERO ); + written += 2; + + // internal file attributes + out.write( ( new ZipShort( ze.getInternalAttributes() ) ).getBytes() ); + written += 2; + + // external file attributes + out.write( ( new ZipLong( ze.getExternalAttributes() ) ).getBytes() ); + written += 4; + + // relative offset of LFH + out.write( ( ( ZipLong )offsets.get( ze ) ).getBytes() ); + written += 4; + + // file name + out.write( name ); + written += name.length; + + // extra field + out.write( extra ); + written += extra.length; + + // file comment + out.write( comment ); + written += comment.length; + } + + /** + * Writes the data descriptor entry + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeDataDescriptor( ZipEntry ze ) + throws IOException + { + if( ze.getMethod() != DEFLATED ) + { + return; + } + out.write( DD_SIG.getBytes() ); + out.write( ( new ZipLong( entry.getCrc() ) ).getBytes() ); + out.write( ( new ZipLong( entry.getCompressedSize() ) ).getBytes() ); + out.write( ( new ZipLong( entry.getSize() ) ).getBytes() ); + written += 16; + } + + /** + * Writes the local file header entry + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeLocalFileHeader( ZipEntry ze ) + throws IOException + { + offsets.put( ze, new ZipLong( written ) ); + + out.write( LFH_SIG.getBytes() ); + written += 4; + + // version needed to extract + // general purpose bit flag + if( ze.getMethod() == DEFLATED ) + { + // requires version 2 as we are going to store length info + // in the data descriptor + out.write( ( new ZipShort( 20 ) ).getBytes() ); + + // bit3 set to signal, we use a data descriptor + out.write( ( new ZipShort( 8 ) ).getBytes() ); + } + else + { + out.write( ( new ZipShort( 10 ) ).getBytes() ); + out.write( ZERO ); + } + written += 4; + + // compression method + out.write( ( new ZipShort( ze.getMethod() ) ).getBytes() ); + written += 2; + + // last mod. time and date + out.write( toDosTime( new Date( ze.getTime() ) ).getBytes() ); + written += 4; + + // CRC + // compressed length + // uncompressed length + if( ze.getMethod() == DEFLATED ) + { + out.write( LZERO ); + out.write( LZERO ); + out.write( LZERO ); + } + else + { + out.write( ( new ZipLong( ze.getCrc() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); + } + written += 12; + + // file name length + byte[] name = getBytes( ze.getName() ); + out.write( ( new ZipShort( name.length ) ).getBytes() ); + written += 2; + + // extra field length + byte[] extra = ze.getLocalFileDataExtra(); + out.write( ( new ZipShort( extra.length ) ).getBytes() ); + written += 2; + + // file name + out.write( name ); + written += name.length; + + // extra field + out.write( extra ); + written += extra.length; + + dataStart = written; + } + +} diff --git a/proposal/myrmidon/src/main/org/apache/tools/zip/ZipShort.java b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipShort.java new file mode 100644 index 000000000..b06f040e1 --- /dev/null +++ b/proposal/myrmidon/src/main/org/apache/tools/zip/ZipShort.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Utility class that represents a two byte integer with conversion rules for + * the big endian byte order of ZIP files. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipShort implements Cloneable +{ + + private int value; + + /** + * Create instance from a number. + * + * @param value Description of Parameter + * @since 1.1 + */ + public ZipShort( int value ) + { + this.value = value; + } + + /** + * Create instance from bytes. + * + * @param bytes Description of Parameter + * @since 1.1 + */ + public ZipShort( byte[] bytes ) + { + this( bytes, 0 ); + } + + /** + * Create instance from the two bytes starting at offset. + * + * @param bytes Description of Parameter + * @param offset Description of Parameter + * @since 1.1 + */ + public ZipShort( byte[] bytes, int offset ) + { + value = ( bytes[offset + 1] << 8 ) & 0xFF00; + value += ( bytes[offset] & 0xFF ); + } + + /** + * Get value as two bytes in big endian byte order. + * + * @return The Bytes value + * @since 1.1 + */ + public byte[] getBytes() + { + byte[] result = new byte[2]; + result[0] = ( byte )( value & 0xFF ); + result[1] = ( byte )( ( value & 0xFF00 ) >> 8 ); + return result; + } + + /** + * Get value as Java int. + * + * @return The Value value + * @since 1.1 + */ + public int getValue() + { + return value; + } + + /** + * Override to make two instances with same value equal. + * + * @param o Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public boolean equals( Object o ) + { + if( o == null || !( o instanceof ZipShort ) ) + { + return false; + } + return value == ( ( ZipShort )o ).getValue(); + } + + /** + * Override to make two instances with same value equal. + * + * @return Description of the Returned Value + * @since 1.1 + */ + public int hashCode() + { + return value; + } + +}// ZipShort diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/AntClassLoader.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/AntClassLoader.java new file mode 100644 index 000000000..d2ef10528 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/AntClassLoader.java @@ -0,0 +1,1088 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.apache.tools.ant.types.Path; + +/** + * Used to load classes within ant with a different claspath from that used to + * start ant. Note that it is possible to force a class into this loader even + * when that class is on the system classpath by using the forceLoadClass + * method. Any subsequent classes loaded by that class will then use this loader + * rather than the system class loader. + * + * @author Conor MacNeill + * @author Jesse Glick + */ +public class AntClassLoader extends ClassLoader implements BuildListener +{ + + /** + * The size of buffers to be used in this classloader. + */ + private final static int BUFFER_SIZE = 8192; + + private static Method getProtectionDomain = null; + private static Method defineClassProtectionDomain = null; + private static Method getContextClassLoader = null; + private static Method setContextClassLoader = null; + + /** + * The components of the classpath that the classloader searches for classes + */ + Vector pathComponents = new Vector(); + + /** + * Indicates whether the parent class loader should be consulted before + * trying to load with this class loader. + */ + private boolean parentFirst = true; + + /** + * These are the package roots that are to be loaded by the parent class + * loader regardless of whether the parent class loader is being searched + * first or not. + */ + private Vector systemPackages = new Vector(); + + /** + * These are the package roots that are to be loaded by this class loader + * regardless of whether the parent class loader is being searched first or + * not. + */ + private Vector loaderPackages = new Vector(); + + /** + * This flag indicates that the classloader will ignore the base classloader + * if it can't find a class. + */ + private boolean ignoreBase = false; + + /** + * The parent class loader, if one is given or can be determined + */ + private ClassLoader parent = null; + + /** + * A hashtable of zip files opened by the classloader + */ + private Hashtable zipFiles = new Hashtable(); + + /** + * The context loader saved when setting the thread's current context + * loader. + */ + private ClassLoader savedContextLoader = null; + private boolean isContextLoaderSaved = false; + + /** + * The project to which this class loader belongs. + */ + private Project project; + static + { + try + { + getProtectionDomain = Class.class.getMethod( "getProtectionDomain", new Class[0] ); + Class protectionDomain = Class.forName( "java.security.ProtectionDomain" ); + Class[] args = new Class[]{String.class, byte[].class, Integer.TYPE, Integer.TYPE, protectionDomain}; + defineClassProtectionDomain = ClassLoader.class.getDeclaredMethod( "defineClass", args ); + + getContextClassLoader = Thread.class.getMethod( "getContextClassLoader", new Class[0] ); + args = new Class[]{ClassLoader.class}; + setContextClassLoader = Thread.class.getMethod( "setContextClassLoader", args ); + } + catch( Exception e ) + {} + } + + + /** + * Create a classloader for the given project using the classpath given. + * + * @param project the project to which this classloader is to belong. + * @param classpath the classpath to use to load the classes. This is + * combined with the system classpath in a manner determined by the + * value of ${build.sysclasspath} + */ + public AntClassLoader( Project project, Path classpath ) + { + parent = AntClassLoader.class.getClassLoader(); + this.project = project; + project.addBuildListener( this ); + if( classpath != null ) + { + Path actualClasspath = classpath.concatSystemClasspath( "ignore" ); + String[] pathElements = actualClasspath.list(); + for( int i = 0; i < pathElements.length; ++i ) + { + try + { + addPathElement( ( String )pathElements[i] ); + } + catch( BuildException e ) + { + // ignore path elements which are invalid relative to the project + } + } + } + } + + /** + * Create a classloader for the given project using the classpath given. + * + * @param parent the parent classloader to which unsatisfied loading + * attempts are delgated + * @param project the project to which this classloader is to belong. + * @param classpath the classpath to use to load the classes. + * @param parentFirst if true indicates that the parent classloader should + * be consulted before trying to load the a class through this loader. + */ + public AntClassLoader( ClassLoader parent, Project project, Path classpath, + boolean parentFirst ) + { + this( project, classpath ); + if( parent != null ) + { + this.parent = parent; + } + this.parentFirst = parentFirst; + addSystemPackageRoot( "java" ); + addSystemPackageRoot( "javax" ); + } + + + /** + * Create a classloader for the given project using the classpath given. + * + * @param project the project to which this classloader is to belong. + * @param classpath the classpath to use to load the classes. + * @param parentFirst if true indicates that the parent classloader should + * be consulted before trying to load the a class through this loader. + */ + public AntClassLoader( Project project, Path classpath, boolean parentFirst ) + { + this( null, project, classpath, parentFirst ); + } + + /** + * Create an empty class loader. The classloader should be configured with + * path elements to specify where the loader is to look for classes. + * + * @param parent the parent classloader to which unsatisfied loading + * attempts are delgated + * @param parentFirst if true indicates that the parent classloader should + * be consulted before trying to load the a class through this loader. + */ + public AntClassLoader( ClassLoader parent, boolean parentFirst ) + { + if( parent != null ) + { + this.parent = parent; + } + else + { + parent = AntClassLoader.class.getClassLoader(); + } + project = null; + this.parentFirst = parentFirst; + } + + /** + * Force initialization of a class in a JDK 1.1 compatible, albeit hacky way + * + * @param theClass Description of Parameter + */ + public static void initializeClass( Class theClass ) + { + // ***HACK*** We try to create an instance to force the VM to run the + // class' static initializer. We don't care if the instance can't + // be created - we are just interested in the side effect. + try + { + theClass.newInstance(); + } + catch( Throwable t ) + { + //ignore - our work is done + } + } + + /** + * Set this classloader to run in isolated mode. In isolated mode, classes + * not found on the given classpath will not be referred to the base class + * loader but will cause a classNotFoundException. + * + * @param isolated The new Isolated value + */ + public void setIsolated( boolean isolated ) + { + ignoreBase = isolated; + } + + /** + * Set the current thread's context loader to this classloader, storing the + * current loader value for later resetting + */ + public void setThreadContextLoader() + { + if( isContextLoaderSaved ) + { + throw new BuildException( "Context loader has not been reset" ); + } + if( getContextClassLoader != null && setContextClassLoader != null ) + { + try + { + savedContextLoader + = ( ClassLoader )getContextClassLoader.invoke( Thread.currentThread(), new Object[0] ); + Object[] args = new Object[]{this}; + setContextClassLoader.invoke( Thread.currentThread(), args ); + isContextLoaderSaved = true; + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + throw new BuildException( t.toString() ); + } + catch( Exception e ) + { + throw new BuildException( e.toString() ); + } + } + } + + /** + * Finds the resource with the given name. A resource is some data (images, + * audio, text, etc) that can be accessed by class code in a way that is + * independent of the location of the code. + * + * @param name the name of the resource for which a stream is required. + * @return a URL for reading the resource, or null if the resource could not + * be found or the caller doesn't have adequate privileges to get the + * resource. + */ + public URL getResource( String name ) + { + // we need to search the components of the path to see if we can find the + // class we want. + URL url = null; + if( isParentFirst( name ) ) + { + url = ( parent == null ) ? super.getResource( name ) : parent.getResource( name ); + } + + if( url != null ) + { + log( "Resource " + name + " loaded from parent loader", + Project.MSG_DEBUG ); + + } + else + { + // try and load from this loader if the parent either didn't find + // it or wasn't consulted. + for( Enumeration e = pathComponents.elements(); e.hasMoreElements() && url == null; ) + { + File pathComponent = ( File )e.nextElement(); + url = getResourceURL( pathComponent, name ); + if( url != null ) + { + log( "Resource " + name + + " loaded from ant loader", + Project.MSG_DEBUG ); + } + } + } + + if( url == null && !isParentFirst( name ) ) + { + // this loader was first but it didn't find it - try the parent + + url = ( parent == null ) ? super.getResource( name ) : parent.getResource( name ); + if( url != null ) + { + log( "Resource " + name + " loaded from parent loader", + Project.MSG_DEBUG ); + } + } + + if( url == null ) + { + log( "Couldn't load Resource " + name, Project.MSG_DEBUG ); + } + + return url; + } + + /** + * Get a stream to read the requested resource name. + * + * @param name the name of the resource for which a stream is required. + * @return a stream to the required resource or null if the resource cannot + * be found on the loader's classpath. + */ + public InputStream getResourceAsStream( String name ) + { + + InputStream resourceStream = null; + if( isParentFirst( name ) ) + { + resourceStream = loadBaseResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from parent loader", Project.MSG_DEBUG ); + + } + else + { + resourceStream = loadResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from ant loader", Project.MSG_DEBUG ); + } + } + } + else + { + resourceStream = loadResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from ant loader", Project.MSG_DEBUG ); + + } + else + { + resourceStream = loadBaseResource( name ); + if( resourceStream != null ) + { + log( "ResourceStream for " + name + + " loaded from parent loader", Project.MSG_DEBUG ); + } + } + } + + if( resourceStream == null ) + { + log( "Couldn't load ResourceStream for " + name, + Project.MSG_DEBUG ); + } + + return resourceStream; + } + + /** + * Add a package root to the list of packages which must be loaded using + * this loader. All subpackages are also included. + * + * @param packageRoot the root of akll packages to be included. + */ + public void addLoaderPackageRoot( String packageRoot ) + { + loaderPackages.addElement( packageRoot + "." ); + } + + + /** + * Add an element to the classpath to be searched + * + * @param pathElement The feature to be added to the PathElement attribute + * @exception BuildException Description of Exception + */ + public void addPathElement( String pathElement ) + throws BuildException + { + File pathComponent + = project != null ? project.resolveFile( pathElement ) + : new File( pathElement ); + pathComponents.addElement( pathComponent ); + } + + /** + * Add a package root to the list of packages which must be loaded on the + * parent loader. All subpackages are also included. + * + * @param packageRoot the root of all packages to be included. + */ + public void addSystemPackageRoot( String packageRoot ) + { + systemPackages.addElement( packageRoot + "." ); + } + + public void buildFinished( BuildEvent event ) + { + cleanup(); + } + + public void buildStarted( BuildEvent event ) { } + + public void cleanup() + { + pathComponents = null; + project = null; + for( Enumeration e = zipFiles.elements(); e.hasMoreElements(); ) + { + ZipFile zipFile = ( ZipFile )e.nextElement(); + try + { + zipFile.close(); + } + catch( IOException ioe ) + { + // ignore + } + } + zipFiles = new Hashtable(); + } + + /** + * Search for and load a class on the classpath of this class loader. + * + * @param name the classname to be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * this loader's classpath. + */ + public Class findClass( String name ) + throws ClassNotFoundException + { + log( "Finding class " + name, Project.MSG_DEBUG ); + + return findClassInComponents( name ); + } + + + /** + * Load a class through this class loader even if that class is available on + * the parent classpath. This ensures that any classes which are loaded by + * the returned class will use this classloader. + * + * @param classname the classname to be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * this loader's classpath. + */ + public Class forceLoadClass( String classname ) + throws ClassNotFoundException + { + log( "force loading " + classname, Project.MSG_DEBUG ); + + Class theClass = findLoadedClass( classname ); + + if( theClass == null ) + { + theClass = findClass( classname ); + } + + return theClass; + } + + /** + * Load a class through this class loader but defer to the parent class + * loader This ensures that instances of the returned class will be + * compatible with instances which which have already been loaded on the + * parent loader. + * + * @param classname the classname to be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * this loader's classpath. + */ + public Class forceLoadSystemClass( String classname ) + throws ClassNotFoundException + { + log( "force system loading " + classname, Project.MSG_DEBUG ); + + Class theClass = findLoadedClass( classname ); + + if( theClass == null ) + { + theClass = findBaseClass( classname ); + } + + return theClass; + } + + public void messageLogged( BuildEvent event ) { } + + /** + * Reset the current thread's context loader to its original value + */ + public void resetThreadContextLoader() + { + if( isContextLoaderSaved && + getContextClassLoader != null && setContextClassLoader != null ) + { + try + { + Object[] args = new Object[]{savedContextLoader}; + setContextClassLoader.invoke( Thread.currentThread(), args ); + savedContextLoader = null; + isContextLoaderSaved = false; + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + throw new BuildException( t.toString() ); + } + catch( Exception e ) + { + throw new BuildException( e.toString() ); + } + } + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) { } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + + /** + * Returns an enumeration of URLs representing all the resources with the + * given name by searching the class loader's classpath. + * + * @param name the resource name. + * @return an enumeration of URLs for the resources. + * @throws IOException if I/O errors occurs (can't happen) + */ + protected Enumeration findResources( String name ) + throws IOException + { + return new ResourceEnumeration( name ); + } + + + /** + * Load a class with this class loader. This method will load a class. This + * class attempts to load the class firstly using the parent class loader. + * For JDK 1.1 compatability, this uses the findSystemClass method. + * + * @param classname the name of the class to be loaded. + * @param resolve true if all classes upon which this class depends are to + * be loaded. + * @return the required Class object + * @throws ClassNotFoundException if the requested class does not exist on + * the system classpath or this loader's classpath. + */ + protected Class loadClass( String classname, boolean resolve ) + throws ClassNotFoundException + { + + Class theClass = findLoadedClass( classname ); + if( theClass != null ) + { + return theClass; + } + + if( isParentFirst( classname ) ) + { + try + { + theClass = findBaseClass( classname ); + log( "Class " + classname + " loaded from parent loader", Project.MSG_DEBUG ); + } + catch( ClassNotFoundException cnfe ) + { + theClass = findClass( classname ); + log( "Class " + classname + " loaded from ant loader", Project.MSG_DEBUG ); + } + } + else + { + try + { + theClass = findClass( classname ); + log( "Class " + classname + " loaded from ant loader", Project.MSG_DEBUG ); + } + catch( ClassNotFoundException cnfe ) + { + if( ignoreBase ) + { + throw cnfe; + } + theClass = findBaseClass( classname ); + log( "Class " + classname + " loaded from parent loader", Project.MSG_DEBUG ); + } + } + + if( resolve ) + { + resolveClass( theClass ); + } + + return theClass; + } + + /** + * Log a message through the project object if one has been provided. + * + * @param message the message to log + * @param priority the logging priority of the message + */ + protected void log( String message, int priority ) + { + if( project != null ) + { + project.log( message, priority ); + } +// else { +// System.out.println(message); +// } + } + + /** + * Convert the class dot notation to a filesystem equivalent for searching + * purposes. + * + * @param classname the class name in dot format (ie java.lang.Integer) + * @return the classname in filesystem format (ie java/lang/Integer.class) + */ + private String getClassFilename( String classname ) + { + return classname.replace( '.', '/' ) + ".class"; + } + + /** + * Read a class definition from a stream. + * + * @param stream the stream from which the class is to be read. + * @param classname the class name of the class in the stream. + * @return the Class object read from the stream. + * @throws IOException if there is a problem reading the class from the + * stream. + */ + private Class getClassFromStream( InputStream stream, String classname ) + throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + int bytesRead = -1; + byte[] buffer = new byte[BUFFER_SIZE]; + + while( ( bytesRead = stream.read( buffer, 0, BUFFER_SIZE ) ) != -1 ) + { + baos.write( buffer, 0, bytesRead ); + } + + byte[] classData = baos.toByteArray(); + + // Simply put: + // defineClass(classname, classData, 0, classData.length, Project.class.getProtectionDomain()); + // Made more elaborate to be 1.1-safe. + if( defineClassProtectionDomain != null ) + { + try + { + Object domain = getProtectionDomain.invoke( Project.class, new Object[0] ); + Object[] args = new Object[]{classname, classData, new Integer( 0 ), new Integer( classData.length ), domain}; + return ( Class )defineClassProtectionDomain.invoke( this, args ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof ClassFormatError ) + { + throw ( ClassFormatError )t; + } + else if( t instanceof NoClassDefFoundError ) + { + throw ( NoClassDefFoundError )t; + } + else + { + throw new IOException( t.toString() ); + } + } + catch( Exception e ) + { + throw new IOException( e.toString() ); + } + } + else + { + return defineClass( classname, classData, 0, classData.length ); + } + } + + /** + * Get an inputstream to a given resource in the given file which may either + * be a directory or a zip file. + * + * @param file the file (directory or jar) in which to search for the + * resource. + * @param resourceName the name of the resource for which a stream is + * required. + * @return a stream to the required resource or null if the resource cannot + * be found in the given file object + */ + private InputStream getResourceStream( File file, String resourceName ) + { + try + { + if( !file.exists() ) + { + return null; + } + + if( file.isDirectory() ) + { + File resource = new File( file, resourceName ); + + if( resource.exists() ) + { + return new FileInputStream( resource ); + } + } + else + { + // is the zip file in the cache + ZipFile zipFile = ( ZipFile )zipFiles.get( file ); + if( zipFile == null ) + { + zipFile = new ZipFile( file ); + zipFiles.put( file, zipFile ); + } + ZipEntry entry = zipFile.getEntry( resourceName ); + if( entry != null ) + { + return zipFile.getInputStream( entry ); + } + } + } + catch( Exception e ) + { + log( "Ignoring Exception " + e.getClass().getName() + ": " + e.getMessage() + + " reading resource " + resourceName + " from " + file, Project.MSG_VERBOSE ); + } + + return null; + } + + /** + * Get an inputstream to a given resource in the given file which may either + * be a directory or a zip file. + * + * @param file the file (directory or jar) in which to search for the + * resource. + * @param resourceName the name of the resource for which a stream is + * required. + * @return a stream to the required resource or null if the resource cannot + * be found in the given file object + */ + private URL getResourceURL( File file, String resourceName ) + { + try + { + if( !file.exists() ) + { + return null; + } + + if( file.isDirectory() ) + { + File resource = new File( file, resourceName ); + + if( resource.exists() ) + { + try + { + return new URL( "file:" + resource.toString() ); + } + catch( MalformedURLException ex ) + { + return null; + } + } + } + else + { + ZipFile zipFile = ( ZipFile )zipFiles.get( file ); + if( zipFile == null ) + { + zipFile = new ZipFile( file ); + zipFiles.put( file, zipFile ); + } + + ZipEntry entry = zipFile.getEntry( resourceName ); + if( entry != null ) + { + try + { + return new URL( "jar:file:" + file.toString() + "!/" + entry ); + } + catch( MalformedURLException ex ) + { + return null; + } + } + } + } + catch( Exception e ) + { + e.printStackTrace(); + } + + return null; + } + + private boolean isParentFirst( String resourceName ) + { + // default to the global setting and then see + // if this class belongs to a package which has been + // designated to use a specific loader first (this one or the parent one) + boolean useParentFirst = parentFirst; + + for( Enumeration e = systemPackages.elements(); e.hasMoreElements(); ) + { + String packageName = ( String )e.nextElement(); + if( resourceName.startsWith( packageName ) ) + { + useParentFirst = true; + break; + } + } + + for( Enumeration e = loaderPackages.elements(); e.hasMoreElements(); ) + { + String packageName = ( String )e.nextElement(); + if( resourceName.startsWith( packageName ) ) + { + useParentFirst = false; + break; + } + } + + return useParentFirst; + } + + /** + * Find a system class (which should be loaded from the same classloader as + * the Ant core). + * + * @param name Description of Parameter + * @return Description of the Returned Value + * @exception ClassNotFoundException Description of Exception + */ + private Class findBaseClass( String name ) + throws ClassNotFoundException + { + if( parent == null ) + { + return findSystemClass( name ); + } + else + { + return parent.loadClass( name ); + } + } + + + /** + * Find a class on the given classpath. + * + * @param name Description of Parameter + * @return Description of the Returned Value + * @exception ClassNotFoundException Description of Exception + */ + private Class findClassInComponents( String name ) + throws ClassNotFoundException + { + // we need to search the components of the path to see if we can find the + // class we want. + InputStream stream = null; + String classFilename = getClassFilename( name ); + try + { + for( Enumeration e = pathComponents.elements(); e.hasMoreElements(); ) + { + File pathComponent = ( File )e.nextElement(); + try + { + stream = getResourceStream( pathComponent, classFilename ); + if( stream != null ) + { + return getClassFromStream( stream, name ); + } + } + catch( IOException ioe ) + { + // ioe.printStackTrace(); + log( "Exception reading component " + pathComponent, Project.MSG_VERBOSE ); + } + } + + throw new ClassNotFoundException( name ); + } + finally + { + try + { + if( stream != null ) + { + stream.close(); + } + } + catch( IOException e ) + {} + } + } + + /** + * Find a system resource (which should be loaded from the parent + * classloader). + * + * @param name Description of Parameter + * @return Description of the Returned Value + */ + private InputStream loadBaseResource( String name ) + { + if( parent == null ) + { + return getSystemResourceAsStream( name ); + } + else + { + return parent.getResourceAsStream( name ); + } + } + + + /** + * Get a stream to read the requested resource name from this loader. + * + * @param name the name of the resource for which a stream is required. + * @return a stream to the required resource or null if the resource cannot + * be found on the loader's classpath. + */ + private InputStream loadResource( String name ) + { + // we need to search the components of the path to see if we can find the + // class we want. + InputStream stream = null; + + for( Enumeration e = pathComponents.elements(); e.hasMoreElements() && stream == null; ) + { + File pathComponent = ( File )e.nextElement(); + stream = getResourceStream( pathComponent, name ); + } + return stream; + } + + /** + * An enumeration of all resources of a given name found within the + * classpath of this class loader. This enumeration is used by the {@link + * #findResources(String) findResources} method, which is in turn used by + * the {@link ClassLoader#getResources ClassLoader.getResources} method. + * + * @author David A. Herman + * @see AntClassLoader#findResources(String) + * @see java.lang.ClassLoader#getResources(String) + */ + private class ResourceEnumeration implements Enumeration + { + + /** + * The URL of the next resource to return in the enumeration. If this + * field is null then the enumeration has been completed, + * i.e., there are no more elements to return. + */ + private URL nextResource; + + /** + * The index of the next classpath element to search. + */ + private int pathElementsIndex; + + /** + * The name of the resource being searched for. + */ + private String resourceName; + + /** + * Construct a new enumeration of resources of the given name found + * within this class loader's classpath. + * + * @param name the name of the resource to search for. + */ + ResourceEnumeration( String name ) + { + this.resourceName = name; + this.pathElementsIndex = 0; + findNextResource(); + } + + /** + * Indicates whether there are more elements in the enumeration to + * return. + * + * @return true if there are more elements in the + * enumeration; false otherwise. + */ + public boolean hasMoreElements() + { + return ( this.nextResource != null ); + } + + /** + * Returns the next resource in the enumeration. + * + * @return the next resource in the enumeration. + */ + public Object nextElement() + { + URL ret = this.nextResource; + findNextResource(); + return ret; + } + + /** + * Locates the next resource of the correct name in the classpath and + * sets nextResource to the URL of that resource. If no + * more resources can be found, nextResource is set to + * null. + */ + private void findNextResource() + { + URL url = null; + while( ( pathElementsIndex < pathComponents.size() ) && + ( url == null ) ) + { + try + { + File pathComponent + = ( File )pathComponents.elementAt( pathElementsIndex ); + url = getResourceURL( pathComponent, this.resourceName ); + pathElementsIndex++; + } + catch( BuildException e ) + { + // ignore path elements which are not valid relative to the project + } + } + this.nextResource = url; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildEvent.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildEvent.java new file mode 100644 index 000000000..feef6ff96 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildEvent.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.EventObject; + +public class BuildEvent extends EventObject +{ + private int priority = Project.MSG_VERBOSE; + private Throwable exception; + private String message; + private Project project; + private Target target; + private Task task; + + /** + * Construct a BuildEvent for a project level event + * + * @param project the project that emitted the event. + */ + public BuildEvent( Project project ) + { + super( project ); + this.project = project; + this.target = null; + this.task = null; + } + + /** + * Construct a BuildEvent for a target level event + * + * @param target the target that emitted the event. + */ + public BuildEvent( Target target ) + { + super( target ); + this.project = target.getProject(); + this.target = target; + this.task = null; + } + + /** + * Construct a BuildEvent for a task level event + * + * @param task the task that emitted the event. + */ + public BuildEvent( Task task ) + { + super( task ); + this.project = task.getProject(); + this.target = task.getOwningTarget(); + this.task = task; + } + + public void setException( Throwable exception ) + { + this.exception = exception; + } + + public void setMessage( String message, int priority ) + { + this.message = message; + this.priority = priority; + } + + /** + * Returns the exception that was thrown, if any. This field will only be + * set for "taskFinished", "targetFinished", and "buildFinished" events. + * + * @return The Exception value + * @see BuildListener#taskFinished(BuildEvent) + * @see BuildListener#targetFinished(BuildEvent) + * @see BuildListener#buildFinished(BuildEvent) + */ + public Throwable getException() + { + return exception; + } + + /** + * Returns the logging message. This field will only be set for + * "messageLogged" events. + * + * @return The Message value + * @see BuildListener#messageLogged(BuildEvent) + */ + public String getMessage() + { + return message; + } + + /** + * Returns the priority of the logging message. This field will only be set + * for "messageLogged" events. + * + * @return The Priority value + * @see BuildListener#messageLogged(BuildEvent) + */ + public int getPriority() + { + return priority; + } + + /** + * Returns the project that fired this event. + * + * @return The Project value + */ + public Project getProject() + { + return project; + } + + /** + * Returns the target that fired this event. + * + * @return The Target value + */ + public Target getTarget() + { + + return target; + } + + /** + * Returns the task that fired this event. + * + * @return The Task value + */ + public Task getTask() + { + return task; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildException.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildException.java new file mode 100644 index 000000000..a22ef8933 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildException.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +import org.apache.myrmidon.api.TaskException; + +/** + * Signals an error condition during a build. + * + * @author James Duncan Davidson + */ +public class BuildException + extends TaskException +{ + /** + * Location in the build file where the exception occured + */ + private Location location = Location.UNKNOWN_LOCATION; + + /** + * Constructs an exception with the given descriptive message. + * + * @param msg Description of or information about the exception. + */ + public BuildException( String msg ) + { + super( msg ); + } + + /** + * Constructs an exception with the given message and exception as a root + * cause. + * + * @param msg Description of or information about the exception. + * @param cause Throwable that might have cause this one. + */ + public BuildException( String msg, Throwable cause ) + { + super( msg, cause ); + } + + /** + * Constructs an exception with the given message and exception as a root + * cause and a location in a file. + * + * @param msg Description of or information about the exception. + * @param cause Exception that might have cause this one. + * @param location Location in the project file where the error occured. + */ + public BuildException( String msg, Throwable cause, Location location ) + { + this( msg, cause ); + this.location = location; + } + + /** + * Constructs an exception with the given exception as a root cause. + * + * @param cause Exception that might have caused this one. + */ + public BuildException( Throwable cause ) + { + super( cause.toString(), cause ); + } + + /** + * Constructs an exception with the given descriptive message and a location + * in a file. + * + * @param msg Description of or information about the exception. + * @param location Location in the project file where the error occured. + */ + public BuildException( String msg, Location location ) + { + super( msg ); + this.location = location; + } + + /** + * Sets the file location where the error occured. + * + * @param location The new Location value + */ + public void setLocation( Location location ) + { + this.location = location; + } + + /** + * Returns the file location where the error occured. + * + * @return The Location value + */ + public Location getLocation() + { + return location; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildListener.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildListener.java new file mode 100644 index 000000000..f6055badb --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildListener.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.EventListener; + +/** + * Classes that implement this interface will be notified when things happend + * during a build. + * + * @author RT + * @see BuildEvent + * @see Project#addBuildListener(BuildListener) + */ +public interface BuildListener extends EventListener +{ + + /** + * Fired before any targets are started. + * + * @param event Description of Parameter + */ + void buildStarted( BuildEvent event ); + + /** + * Fired after the last target has finished. This event will still be thrown + * if an error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + void buildFinished( BuildEvent event ); + + /** + * Fired when a target is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTarget() + */ + void targetStarted( BuildEvent event ); + + /** + * Fired when a target has finished. This event will still be thrown if an + * error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + void targetFinished( BuildEvent event ); + + /** + * Fired when a task is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTask() + */ + void taskStarted( BuildEvent event ); + + /** + * Fired when a task has finished. This event will still be throw if an + * error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + void taskFinished( BuildEvent event ); + + /** + * Fired whenever a message is logged. + * + * @param event Description of Parameter + * @see BuildEvent#getMessage() + * @see BuildEvent#getPriority() + */ + void messageLogged( BuildEvent event ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildLogger.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildLogger.java new file mode 100644 index 000000000..9e3879d14 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/BuildLogger.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.PrintStream; + +/** + * Interface used by Ant to log the build output. A build logger is a build + * listener which has the 'right' to send output to the ant log, which is + * usually System.out unles redirected by the -logfile option. + * + * @author Conor MacNeill + */ +public interface BuildLogger extends BuildListener +{ + /** + * Set the msgOutputLevel this logger is to respond to. Only messages with a + * message level lower than or equal to the given level are output to the + * log.

          + * + * Constants for the message levels are in Project.java. The order of the + * levels, from least to most verbose, is MSG_ERR, MSG_WARN, MSG_INFO, + * MSG_VERBOSE, MSG_DEBUG. + * + * @param level the logging level for the logger. + */ + void setMessageOutputLevel( int level ); + + /** + * Set the output stream to which this logger is to send its output. + * + * @param output the output stream for the logger. + */ + void setOutputPrintStream( PrintStream output ); + + /** + * Set this logger to produce emacs (and other editor) friendly output. + * + * @param emacsMode true if output is to be unadorned so that emacs and + * other editors can parse files names, etc. + */ + void setEmacsMode( boolean emacsMode ); + + /** + * Set the output stream to which this logger is to send error messages. + * + * @param err the error stream for the logger. + */ + void setErrorPrintStream( PrintStream err ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Constants.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Constants.java new file mode 100644 index 000000000..7f26d5f4e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Constants.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Abstract interface to hold constants. + * + * @author Peter Donald + */ +interface Constants +{ +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/DefaultLogger.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/DefaultLogger.java new file mode 100644 index 000000000..eb578b54e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/DefaultLogger.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import org.apache.tools.ant.util.StringUtils; + +/** + * Writes build event to a PrintStream. Currently, it only writes which targets + * are being executed, and any messages that get logged. + * + * @author RT + */ +public class DefaultLogger implements BuildLogger +{ + private static int LEFT_COLUMN_SIZE = 12; + protected int msgOutputLevel = Project.MSG_ERR; + private long startTime = System.currentTimeMillis(); + + protected boolean emacsMode = false; + protected PrintStream err; + + protected PrintStream out; + + protected static String formatTime( long millis ) + { + long seconds = millis / 1000; + long minutes = seconds / 60; + + if( minutes > 0 ) + { + return Long.toString( minutes ) + " minute" + + ( minutes == 1 ? " " : "s " ) + + Long.toString( seconds % 60 ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + else + { + return Long.toString( seconds ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + + } + + /** + * Set this logger to produce emacs (and other editor) friendly output. + * + * @param emacsMode true if output is to be unadorned so that emacs and + * other editors can parse files names, etc. + */ + public void setEmacsMode( boolean emacsMode ) + { + this.emacsMode = emacsMode; + } + + /** + * Set the output stream to which this logger is to send error messages. + * + * @param err the error stream for the logger. + */ + public void setErrorPrintStream( PrintStream err ) + { + this.err = new PrintStream( err, true ); + } + + /** + * Set the msgOutputLevel this logger is to respond to. Only messages with a + * message level lower than or equal to the given level are output to the + * log.

          + * + * Constants for the message levels are in Project.java. The order of the + * levels, from least to most verbose, is MSG_ERR, MSG_WARN, MSG_INFO, + * MSG_VERBOSE, MSG_DEBUG. The default message level for DefaultLogger is + * Project.MSG_ERR. + * + * @param level the logging level for the logger. + */ + public void setMessageOutputLevel( int level ) + { + this.msgOutputLevel = level; + } + + /** + * Set the output stream to which this logger is to send its output. + * + * @param output the output stream for the logger. + */ + public void setOutputPrintStream( PrintStream output ) + { + this.out = new PrintStream( output, true ); + } + + /** + * Prints whether the build succeeded or failed, and any errors the occured + * during the build. + * + * @param event Description of Parameter + */ + public void buildFinished( BuildEvent event ) + { + Throwable error = event.getException(); + StringBuffer message = new StringBuffer(); + + if( error == null ) + { + message.append( StringUtils.LINE_SEP ); + message.append( "BUILD SUCCESSFUL" ); + } + else + { + message.append( StringUtils.LINE_SEP ); + message.append( "BUILD FAILED" ); + message.append( StringUtils.LINE_SEP ); + + if( Project.MSG_VERBOSE <= msgOutputLevel || + !( error instanceof BuildException ) ) + { + message.append( StringUtils.getStackTrace( error ) ); + } + else + { + if( error instanceof BuildException ) + { + message.append( error.toString() ).append( StringUtils.LINE_SEP ); + } + else + { + message.append( error.getMessage() ).append( StringUtils.LINE_SEP ); + } + } + } + message.append( StringUtils.LINE_SEP ); + message.append( "Total time: " + + formatTime( System.currentTimeMillis() - startTime ) ); + + String msg = message.toString(); + if( error == null ) + { + out.println( msg ); + } + else + { + err.println( msg ); + } + log( msg ); + } + + public void buildStarted( BuildEvent event ) + { + startTime = System.currentTimeMillis(); + } + + public void messageLogged( BuildEvent event ) + { + // Filter out messages based on priority + if( event.getPriority() <= msgOutputLevel ) + { + + StringBuffer message = new StringBuffer(); + // Print out the name of the task if we're in one + if( event.getTask() != null ) + { + String name = event.getTask().getTaskName(); + + if( !emacsMode ) + { + String label = "[" + name + "] "; + for( int i = 0; i < ( LEFT_COLUMN_SIZE - label.length() ); i++ ) + { + message.append( " " ); + } + message.append( label ); + } + } + + message.append( event.getMessage() ); + String msg = message.toString(); + if( event.getPriority() != Project.MSG_ERR ) + { + out.println( msg ); + } + else + { + err.println( msg ); + } + log( msg ); + } + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) + { + if( Project.MSG_INFO <= msgOutputLevel ) + { + String msg = StringUtils.LINE_SEP + event.getTarget().getName() + ":"; + out.println( msg ); + log( msg ); + } + } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + + /** + * Empty implementation which allows subclasses to receive the same output + * that is generated here. + * + * @param message Description of Parameter + */ + protected void log( String message ) { } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/DemuxOutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/DemuxOutputStream.java new file mode 100644 index 000000000..ef0e5472f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/DemuxOutputStream.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Hashtable; + + +/** + * Logs content written by a thread and forwards the buffers onto the project + * object which will forward the content to the appropriate task + * + * @author Conor MacNeill + */ +public class DemuxOutputStream extends OutputStream +{ + + private final static int MAX_SIZE = 1024; + + private Hashtable buffers = new Hashtable(); +// private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + private boolean skip = false; + private boolean isErrorStream; + private Project project; + + /** + * Creates a new instance of this class. + * + * @param project Description of Parameter + * @param isErrorStream Description of Parameter + */ + public DemuxOutputStream( Project project, boolean isErrorStream ) + { + this.project = project; + this.isErrorStream = isErrorStream; + } + + /** + * Writes all remaining + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + flush(); + } + + /** + * Writes all remaining + * + * @exception IOException Description of Exception + */ + public void flush() + throws IOException + { + if( getBuffer().size() > 0 ) + { + processBuffer(); + } + } + + /** + * Write the data to the buffer and flush the buffer, if a line separator is + * detected. + * + * @param cc data to log (byte). + * @exception IOException Description of Exception + */ + public void write( int cc ) + throws IOException + { + final byte c = ( byte )cc; + if( ( c == '\n' ) || ( c == '\r' ) ) + { + if( !skip ) + { + processBuffer(); + } + } + else + { + ByteArrayOutputStream buffer = getBuffer(); + buffer.write( cc ); + if( buffer.size() > MAX_SIZE ) + { + processBuffer(); + } + } + skip = ( c == '\r' ); + } + + + /** + * Converts the buffer to a string and sends it to processLine + */ + protected void processBuffer() + { + String output = getBuffer().toString(); + project.demuxOutput( output, isErrorStream ); + resetBuffer(); + } + + private ByteArrayOutputStream getBuffer() + { + Thread current = Thread.currentThread(); + ByteArrayOutputStream buffer = ( ByteArrayOutputStream )buffers.get( current ); + if( buffer == null ) + { + buffer = new ByteArrayOutputStream(); + buffers.put( current, buffer ); + } + return buffer; + } + + private void resetBuffer() + { + Thread current = Thread.currentThread(); + buffers.remove( current ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/DesirableFilter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/DesirableFilter.java new file mode 100644 index 000000000..a53315fa2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/DesirableFilter.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FilenameFilter; + + +/** + * Filters filenames to determine whether or not the file is desirable. + * + * @author Jason Hunter [jhunter@servlets.com] + * @author james@x180.com + */ +public class DesirableFilter implements FilenameFilter +{ + + /** + * Test the given filename to determine whether or not it's desirable. This + * helps tasks filter temp files and files used by CVS. + * + * @param dir Description of Parameter + * @param name Description of Parameter + * @return Description of the Returned Value + */ + + public boolean accept( File dir, String name ) + { + + // emacs save file + if( name.endsWith( "~" ) ) + { + return false; + } + + // emacs autosave file + if( name.startsWith( "#" ) && name.endsWith( "#" ) ) + { + return false; + } + + // openwindows text editor does this I think + if( name.startsWith( "%" ) && name.endsWith( "%" ) ) + { + return false; + } + + /* + * CVS stuff -- hopefully there won't be a case with + * an all cap file/dir named "CVS" that somebody wants + * to keep around... + */ + if( name.equals( "CVS" ) ) + { + return false; + } + + /* + * If we are going to ignore CVS might as well ignore + * this one as well... + */ + if( name.equals( ".cvsignore" ) ) + { + return false; + } + + // CVS merge autosaves. + if( name.startsWith( ".#" ) ) + { + return false; + } + + // SCCS/CSSC/TeamWare: + if( name.equals( "SCCS" ) ) + { + return false; + } + + // Visual Source Save + if( name.equals( "vssver.scc" ) ) + { + return false; + } + + // default + return true; + } +} + + + + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/DirectoryScanner.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/DirectoryScanner.java new file mode 100644 index 000000000..7d95dd304 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/DirectoryScanner.java @@ -0,0 +1,1177 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * Class for scanning a directory for files/directories that match a certain + * criteria.

          + * + * These criteria consist of a set of include and exclude patterns. With these + * patterns, you can select which files you want to have included, and which + * files you want to have excluded.

          + * + * The idea is simple. A given directory is recursively scanned for all files + * and directories. Each file/directory is matched against a set of include and + * exclude patterns. Only files/directories that match at least one pattern of + * the include pattern list, and don't match a pattern of the exclude pattern + * list will be placed in the list of files/directories found.

          + * + * When no list of include patterns is supplied, "**" will be used, which means + * that everything will be matched. When no list of exclude patterns is + * supplied, an empty list is used, such that nothing will be excluded.

          + * + * The pattern matching is done as follows: The name to be matched is split up + * in path segments. A path segment is the name of a directory or file, which is + * bounded by File.separator ('/' under UNIX, '\' under Windows). + * E.g. "abc/def/ghi/xyz.java" is split up in the segments "abc", "def", "ghi" + * and "xyz.java". The same is done for the pattern against which should be + * matched.

          + * + * Then the segments of the name and the pattern will be matched against each + * other. When '**' is used for a path segment in the pattern, then it matches + * zero or more path segments of the name.

          + * + * There are special case regarding the use of File.separators at + * the beginningof the pattern and the string to match:
          + * When a pattern starts with a File.separator, the string to match + * must also start with a File.separator. When a pattern does not + * start with a File.separator, the string to match may not start + * with a File.separator. When one of these rules is not obeyed, + * the string will not match.

          + * + * When a name path segment is matched against a pattern path segment, the + * following special characters can be used: '*' matches zero or more + * characters, '?' matches one character.

          + * + * Examples:

          + * + * "**\*.class" matches all .class files/dirs in a directory tree.

          + * + * "test\a??.java" matches all files/dirs which start with an 'a', then two more + * characters and then ".java", in a directory called test.

          + * + * "**" matches everything in a directory tree.

          + * + * "**\test\**\XYZ*" matches all files/dirs that start with "XYZ" and where + * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").

          + * + * Case sensitivity may be turned off if necessary. By default, it is turned on. + *

          + * + * Example of usage:

          + *   String[] includes = {"**\\*.class"};
          + *   String[] excludes = {"modules\\*\\**"};
          + *   ds.setIncludes(includes);
          + *   ds.setExcludes(excludes);
          + *   ds.setBasedir(new File("test"));
          + *   ds.setCaseSensitive(true);
          + *   ds.scan();
          + *
          + *   System.out.println("FILES:");
          + *   String[] files = ds.getIncludedFiles();
          + *   for (int i = 0; i < files.length;i++) {
          + *     System.out.println(files[i]);
          + *   }
          + * 
          This will scan a directory called test for .class files, but excludes + * all .class files in all directories under a directory called "modules" + * + * @author Arnout J. Kuiper ajkuiper@wxs.nl + * @author Magesh Umasankar + */ +public class DirectoryScanner implements FileScanner +{ + + /** + * Patterns that should be excluded by default. + * + * @see #addDefaultExcludes() + */ + protected final static String[] DEFAULTEXCLUDES = { + "**/*~", + "**/#*#", + "**/.#*", + "**/%*%", + "**/CVS", + "**/CVS/**", + "**/.cvsignore", + "**/SCCS", + "**/SCCS/**", + "**/vssver.scc" + }; + + /** + * Have the Vectors holding our results been built by a slow scan? + */ + protected boolean haveSlowResults = false; + + /** + * Should the file system be treated as a case sensitive one? + */ + protected boolean isCaseSensitive = true; + + /** + * Is everything we've seen so far included? + */ + protected boolean everythingIncluded = true; + + /** + * The base directory which should be scanned. + */ + protected File basedir; + + /** + * The files that where found and matched at least one includes, and also + * matched at least one excludes. + */ + protected Vector dirsExcluded; + + /** + * The directories that where found and matched at least one includes, and + * matched no excludes. + */ + protected Vector dirsIncluded; + + /** + * The directories that where found and did not match any includes. + */ + protected Vector dirsNotIncluded; + + /** + * The patterns for the files that should be excluded. + */ + protected String[] excludes; + + /** + * The files that where found and matched at least one includes, and also + * matched at least one excludes. + */ + protected Vector filesExcluded; + + /** + * The files that where found and matched at least one includes, and matched + * no excludes. + */ + protected Vector filesIncluded; + + /** + * The files that where found and did not match any includes. + */ + protected Vector filesNotIncluded; + + /** + * The patterns for the files that should be included. + */ + protected String[] includes; + + /** + * Constructor. + */ + public DirectoryScanner() { } + + + /** + * Matches a string against a pattern. The pattern contains two special + * characters: '*' which means zero or more characters, '?' which means one + * and only one character. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string that must be matched against the pattern + * @return true when the string matches against the pattern, + * false otherwise. + */ + public static boolean match( String pattern, String str ) + { + return match( pattern, str, true ); + } + + + /** + * Matches a string against a pattern. The pattern contains two special + * characters: '*' which means zero or more characters, '?' which means one + * and only one character. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string that must be matched against the pattern + * @param isCaseSensitive Description of Parameter + * @return true when the string matches against the pattern, + * false otherwise. + */ + protected static boolean match( String pattern, String str, boolean isCaseSensitive ) + { + char[] patArr = pattern.toCharArray(); + char[] strArr = str.toCharArray(); + int patIdxStart = 0; + int patIdxEnd = patArr.length - 1; + int strIdxStart = 0; + int strIdxEnd = strArr.length - 1; + char ch; + + boolean containsStar = false; + for( int i = 0; i < patArr.length; i++ ) + { + if( patArr[i] == '*' ) + { + containsStar = true; + break; + } + } + + if( !containsStar ) + { + // No '*'s, so we make a shortcut + if( patIdxEnd != strIdxEnd ) + { + return false;// Pattern and string do not have the same size + } + for( int i = 0; i <= patIdxEnd; i++ ) + { + ch = patArr[i]; + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[i] ) + { + return false;// Character mismatch + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[i] ) ) + { + return false;// Character mismatch + } + } + } + return true;// String matches against pattern + } + + if( patIdxEnd == 0 ) + { + return true;// Pattern contains only '*', which matches anything + } + + // Process characters before first star + while( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd ) + { + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[strIdxStart] ) + { + return false;// Character mismatch + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[strIdxStart] ) ) + { + return false;// Character mismatch + } + } + patIdxStart++; + strIdxStart++; + } + if( strIdxStart > strIdxEnd ) + { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( patArr[i] != '*' ) + { + return false; + } + } + return true; + } + + // Process characters after last star + while( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd ) + { + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[strIdxEnd] ) + { + return false;// Character mismatch + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[strIdxEnd] ) ) + { + return false;// Character mismatch + } + } + patIdxEnd--; + strIdxEnd--; + } + if( strIdxStart > strIdxEnd ) + { + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( patArr[i] != '*' ) + { + return false; + } + } + return true; + } + + // process pattern between stars. padIdxStart and patIdxEnd point + // always to a '*'. + while( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd ) + { + int patIdxTmp = -1; + for( int i = patIdxStart + 1; i <= patIdxEnd; i++ ) + { + if( patArr[i] == '*' ) + { + patIdxTmp = i; + break; + } + } + if( patIdxTmp == patIdxStart + 1 ) + { + // Two stars next to each other, skip the first one. + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = ( patIdxTmp - patIdxStart - 1 ); + int strLength = ( strIdxEnd - strIdxStart + 1 ); + int foundIdx = -1; + strLoop : + for( int i = 0; i <= strLength - patLength; i++ ) + { + for( int j = 0; j < patLength; j++ ) + { + ch = patArr[patIdxStart + j + 1]; + if( ch != '?' ) + { + if( isCaseSensitive && ch != strArr[strIdxStart + i + j] ) + { + continue strLoop; + } + if( !isCaseSensitive && Character.toUpperCase( ch ) != + Character.toUpperCase( strArr[strIdxStart + i + j] ) ) + { + continue strLoop; + } + } + } + + foundIdx = strIdxStart + i; + break; + } + + if( foundIdx == -1 ) + { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + // All characters in the string are used. Check if only '*'s are left + // in the pattern. If so, we succeeded. Otherwise failure. + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( patArr[i] != '*' ) + { + return false; + } + } + return true; + } + + /** + * Matches a path against a pattern. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @return true when the pattern matches against the string. + * false otherwise. + */ + protected static boolean matchPath( String pattern, String str ) + { + return matchPath( pattern, str, true ); + } + + /** + * Matches a path against a pattern. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @param isCaseSensitive must a case sensitive match be done? + * @return true when the pattern matches against the string. + * false otherwise. + */ + protected static boolean matchPath( String pattern, String str, boolean isCaseSensitive ) + { + // When str starts with a File.separator, pattern has to start with a + // File.separator. + // When pattern starts with a File.separator, str has to start with a + // File.separator. + if( str.startsWith( File.separator ) != + pattern.startsWith( File.separator ) ) + { + return false; + } + + Vector patDirs = new Vector(); + StringTokenizer st = new StringTokenizer( pattern, File.separator ); + while( st.hasMoreTokens() ) + { + patDirs.addElement( st.nextToken() ); + } + + Vector strDirs = new Vector(); + st = new StringTokenizer( str, File.separator ); + while( st.hasMoreTokens() ) + { + strDirs.addElement( st.nextToken() ); + } + + int patIdxStart = 0; + int patIdxEnd = patDirs.size() - 1; + int strIdxStart = 0; + int strIdxEnd = strDirs.size() - 1; + + // up to first '**' + while( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd ) + { + String patDir = ( String )patDirs.elementAt( patIdxStart ); + if( patDir.equals( "**" ) ) + { + break; + } + if( !match( patDir, ( String )strDirs.elementAt( strIdxStart ), isCaseSensitive ) ) + { + return false; + } + patIdxStart++; + strIdxStart++; + } + if( strIdxStart > strIdxEnd ) + { + // String is exhausted + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( !patDirs.elementAt( i ).equals( "**" ) ) + { + return false; + } + } + return true; + } + else + { + if( patIdxStart > patIdxEnd ) + { + // String not exhausted, but pattern is. Failure. + return false; + } + } + + // up to last '**' + while( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd ) + { + String patDir = ( String )patDirs.elementAt( patIdxEnd ); + if( patDir.equals( "**" ) ) + { + break; + } + if( !match( patDir, ( String )strDirs.elementAt( strIdxEnd ), isCaseSensitive ) ) + { + return false; + } + patIdxEnd--; + strIdxEnd--; + } + if( strIdxStart > strIdxEnd ) + { + // String is exhausted + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( !patDirs.elementAt( i ).equals( "**" ) ) + { + return false; + } + } + return true; + } + + while( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd ) + { + int patIdxTmp = -1; + for( int i = patIdxStart + 1; i <= patIdxEnd; i++ ) + { + if( patDirs.elementAt( i ).equals( "**" ) ) + { + patIdxTmp = i; + break; + } + } + if( patIdxTmp == patIdxStart + 1 ) + { + // '**/**' situation, so skip one + patIdxStart++; + continue; + } + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = ( patIdxTmp - patIdxStart - 1 ); + int strLength = ( strIdxEnd - strIdxStart + 1 ); + int foundIdx = -1; + strLoop : + for( int i = 0; i <= strLength - patLength; i++ ) + { + for( int j = 0; j < patLength; j++ ) + { + String subPat = ( String )patDirs.elementAt( patIdxStart + j + 1 ); + String subStr = ( String )strDirs.elementAt( strIdxStart + i + j ); + if( !match( subPat, subStr, isCaseSensitive ) ) + { + continue strLoop; + } + } + + foundIdx = strIdxStart + i; + break; + } + + if( foundIdx == -1 ) + { + return false; + } + + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + for( int i = patIdxStart; i <= patIdxEnd; i++ ) + { + if( !patDirs.elementAt( i ).equals( "**" ) ) + { + return false; + } + } + + return true; + } + + + /** + * Does the path match the start of this pattern up to the first "**".

          + * + * This is not a general purpose test and should only be used if you can + * live with false positives.

          + * + * pattern=**\\a and str=b will yield true. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @return Description of the Returned Value + */ + protected static boolean matchPatternStart( String pattern, String str ) + { + return matchPatternStart( pattern, str, true ); + } + + /** + * Does the path match the start of this pattern up to the first "**".

          + * + * This is not a general purpose test and should only be used if you can + * live with false positives.

          + * + * pattern=**\\a and str=b will yield true. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string (path) to match + * @param isCaseSensitive must matches be case sensitive? + * @return Description of the Returned Value + */ + protected static boolean matchPatternStart( String pattern, String str, + boolean isCaseSensitive ) + { + // When str starts with a File.separator, pattern has to start with a + // File.separator. + // When pattern starts with a File.separator, str has to start with a + // File.separator. + if( str.startsWith( File.separator ) != + pattern.startsWith( File.separator ) ) + { + return false; + } + + Vector patDirs = new Vector(); + StringTokenizer st = new StringTokenizer( pattern, File.separator ); + while( st.hasMoreTokens() ) + { + patDirs.addElement( st.nextToken() ); + } + + Vector strDirs = new Vector(); + st = new StringTokenizer( str, File.separator ); + while( st.hasMoreTokens() ) + { + strDirs.addElement( st.nextToken() ); + } + + int patIdxStart = 0; + int patIdxEnd = patDirs.size() - 1; + int strIdxStart = 0; + int strIdxEnd = strDirs.size() - 1; + + // up to first '**' + while( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd ) + { + String patDir = ( String )patDirs.elementAt( patIdxStart ); + if( patDir.equals( "**" ) ) + { + break; + } + if( !match( patDir, ( String )strDirs.elementAt( strIdxStart ), isCaseSensitive ) ) + { + return false; + } + patIdxStart++; + strIdxStart++; + } + + if( strIdxStart > strIdxEnd ) + { + // String is exhausted + return true; + } + else if( patIdxStart > patIdxEnd ) + { + // String not exhausted, but pattern is. Failure. + return false; + } + else + { + // pattern now holds ** while string is not exhausted + // this will generate false positives but we can live with that. + return true; + } + } + + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. All '/' and '\' characters are replaced by File.separatorChar + * . So the separator used need not match File.separatorChar. + * + * @param basedir the (non-null) basedir for scanning + */ + public void setBasedir( String basedir ) + { + setBasedir( new File( basedir.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ) ) ); + } + + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. + * + * @param basedir the basedir for scanning + */ + public void setBasedir( File basedir ) + { + this.basedir = basedir; + } + + + /** + * Sets the case sensitivity of the file system + * + * @param isCaseSensitive The new CaseSensitive value + */ + public void setCaseSensitive( boolean isCaseSensitive ) + { + this.isCaseSensitive = isCaseSensitive; + } + + + /** + * Sets the set of exclude patterns to use. All '/' and '\' characters are + * replaced by File.separatorChar. So the separator used need + * not match File.separatorChar.

          + * + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param excludes list of exclude patterns + */ + public void setExcludes( String[] excludes ) + { + if( excludes == null ) + { + this.excludes = null; + } + else + { + this.excludes = new String[excludes.length]; + for( int i = 0; i < excludes.length; i++ ) + { + String pattern; + pattern = excludes[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ); + if( pattern.endsWith( File.separator ) ) + { + pattern += "**"; + } + this.excludes[i] = pattern; + } + } + } + + /** + * Sets the set of include patterns to use. All '/' and '\' characters are + * replaced by File.separatorChar. So the separator used need + * not match File.separatorChar.

          + * + * When a pattern ends with a '/' or '\', "**" is appended. + * + * @param includes list of include patterns + */ + public void setIncludes( String[] includes ) + { + if( includes == null ) + { + this.includes = null; + } + else + { + this.includes = new String[includes.length]; + for( int i = 0; i < includes.length; i++ ) + { + String pattern; + pattern = includes[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ); + if( pattern.endsWith( File.separator ) ) + { + pattern += "**"; + } + this.includes[i] = pattern; + } + } + } + + + /** + * Gets the basedir that is used for scanning. This is the directory that is + * scanned recursively. + * + * @return the basedir that is used for scanning + */ + public File getBasedir() + { + return basedir; + } + + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the directories + */ + public String[] getExcludedDirectories() + { + slowScan(); + int count = dirsExcluded.size(); + String[] directories = new String[count]; + for( int i = 0; i < count; i++ ) + { + directories[i] = ( String )dirsExcluded.elementAt( i ); + } + return directories; + } + + + /** + * Get the names of the files that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the files + */ + public String[] getExcludedFiles() + { + slowScan(); + int count = filesExcluded.size(); + String[] files = new String[count]; + for( int i = 0; i < count; i++ ) + { + files[i] = ( String )filesExcluded.elementAt( i ); + } + return files; + } + + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched none of the exclude patterns. The names are relative + * to the basedir. + * + * @return the names of the directories + */ + public String[] getIncludedDirectories() + { + int count = dirsIncluded.size(); + String[] directories = new String[count]; + for( int i = 0; i < count; i++ ) + { + directories[i] = ( String )dirsIncluded.elementAt( i ); + } + return directories; + } + + + /** + * Get the names of the files that matched at least one of the include + * patterns, and matched none of the exclude patterns. The names are + * relative to the basedir. + * + * @return the names of the files + */ + public String[] getIncludedFiles() + { + int count = filesIncluded.size(); + String[] files = new String[count]; + for( int i = 0; i < count; i++ ) + { + files[i] = ( String )filesIncluded.elementAt( i ); + } + return files; + } + + + /** + * Get the names of the directories that matched at none of the include + * patterns. The names are relative to the basedir. + * + * @return the names of the directories + */ + public String[] getNotIncludedDirectories() + { + slowScan(); + int count = dirsNotIncluded.size(); + String[] directories = new String[count]; + for( int i = 0; i < count; i++ ) + { + directories[i] = ( String )dirsNotIncluded.elementAt( i ); + } + return directories; + } + + + /** + * Get the names of the files that matched at none of the include patterns. + * The names are relative to the basedir. + * + * @return the names of the files + */ + public String[] getNotIncludedFiles() + { + slowScan(); + int count = filesNotIncluded.size(); + String[] files = new String[count]; + for( int i = 0; i < count; i++ ) + { + files[i] = ( String )filesNotIncluded.elementAt( i ); + } + return files; + } + + /** + * Has the scanner excluded or omitted any files or directories it came + * accross? + * + * @return true if all files and directories that have been found, are + * included. + */ + public boolean isEverythingIncluded() + { + return everythingIncluded; + } + + + /** + * Adds the array with default exclusions to the current exclusions set. + */ + public void addDefaultExcludes() + { + int excludesLength = excludes == null ? 0 : excludes.length; + String[] newExcludes; + newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length]; + if( excludesLength > 0 ) + { + System.arraycopy( excludes, 0, newExcludes, 0, excludesLength ); + } + for( int i = 0; i < DEFAULTEXCLUDES.length; i++ ) + { + newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ); + } + excludes = newExcludes; + } + + + /** + * Scans the base directory for files that match at least one include + * pattern, and don't match any exclude patterns. + * + */ + public void scan() + { + if( basedir == null ) + { + throw new IllegalStateException( "No basedir set" ); + } + if( !basedir.exists() ) + { + throw new IllegalStateException( "basedir " + basedir + + " does not exist" ); + } + if( !basedir.isDirectory() ) + { + throw new IllegalStateException( "basedir " + basedir + + " is not a directory" ); + } + + if( includes == null ) + { + // No includes supplied, so set it to 'matches all' + includes = new String[1]; + includes[0] = "**"; + } + if( excludes == null ) + { + excludes = new String[0]; + } + + filesIncluded = new Vector(); + filesNotIncluded = new Vector(); + filesExcluded = new Vector(); + dirsIncluded = new Vector(); + dirsNotIncluded = new Vector(); + dirsExcluded = new Vector(); + + if( isIncluded( "" ) ) + { + if( !isExcluded( "" ) ) + { + dirsIncluded.addElement( "" ); + } + else + { + dirsExcluded.addElement( "" ); + } + } + else + { + dirsNotIncluded.addElement( "" ); + } + scandir( basedir, "", true ); + } + + /** + * Tests whether a name matches against at least one exclude pattern. + * + * @param name the name to match + * @return true when the name matches against at least one + * exclude pattern, false otherwise. + */ + protected boolean isExcluded( String name ) + { + for( int i = 0; i < excludes.length; i++ ) + { + if( matchPath( excludes[i], name, isCaseSensitive ) ) + { + return true; + } + } + return false; + } + + + /** + * Tests whether a name matches against at least one include pattern. + * + * @param name the name to match + * @return true when the name matches against at least one + * include pattern, false otherwise. + */ + protected boolean isIncluded( String name ) + { + for( int i = 0; i < includes.length; i++ ) + { + if( matchPath( includes[i], name, isCaseSensitive ) ) + { + return true; + } + } + return false; + } + + /** + * Tests whether a name matches the start of at least one include pattern. + * + * @param name the name to match + * @return true when the name matches against at least one + * include pattern, false otherwise. + */ + protected boolean couldHoldIncluded( String name ) + { + for( int i = 0; i < includes.length; i++ ) + { + if( matchPatternStart( includes[i], name, isCaseSensitive ) ) + { + return true; + } + } + return false; + } + + + /** + * Scans the passed dir for files and directories. Found files and + * directories are placed in their respective collections, based on the + * matching of includes and excludes. When a directory is found, it is + * scanned recursively. + * + * @param dir the directory to scan + * @param vpath the path relative to the basedir (needed to prevent problems + * with an absolute path when using dir) + * @param fast Description of Parameter + * @see #filesIncluded + * @see #filesNotIncluded + * @see #filesExcluded + * @see #dirsIncluded + * @see #dirsNotIncluded + * @see #dirsExcluded + */ + protected void scandir( File dir, String vpath, boolean fast ) + { + String[] newfiles = dir.list(); + + if( newfiles == null ) + { + /* + * two reasons are mentioned in the API docs for File.list + * (1) dir is not a directory. This is impossible as + * we wouldn't get here in this case. + * (2) an IO error occurred (why doesn't it throw an exception + * then???) + */ + throw new BuildException( "IO error scanning directory " + + dir.getAbsolutePath() ); + } + + for( int i = 0; i < newfiles.length; i++ ) + { + String name = vpath + newfiles[i]; + File file = new File( dir, newfiles[i] ); + if( file.isDirectory() ) + { + if( isIncluded( name ) ) + { + if( !isExcluded( name ) ) + { + dirsIncluded.addElement( name ); + if( fast ) + { + scandir( file, name + File.separator, fast ); + } + } + else + { + everythingIncluded = false; + dirsExcluded.addElement( name ); + if( fast && couldHoldIncluded( name ) ) + { + scandir( file, name + File.separator, fast ); + } + } + } + else + { + everythingIncluded = false; + dirsNotIncluded.addElement( name ); + if( fast && couldHoldIncluded( name ) ) + { + scandir( file, name + File.separator, fast ); + } + } + if( !fast ) + { + scandir( file, name + File.separator, fast ); + } + } + else if( file.isFile() ) + { + if( isIncluded( name ) ) + { + if( !isExcluded( name ) ) + { + filesIncluded.addElement( name ); + } + else + { + everythingIncluded = false; + filesExcluded.addElement( name ); + } + } + else + { + everythingIncluded = false; + filesNotIncluded.addElement( name ); + } + } + } + } + + /** + * Toplevel invocation for the scan.

          + * + * Returns immediately if a slow scan has already been requested. + */ + protected void slowScan() + { + if( haveSlowResults ) + { + return; + } + + String[] excl = new String[dirsExcluded.size()]; + dirsExcluded.copyInto( excl ); + + String[] notIncl = new String[dirsNotIncluded.size()]; + dirsNotIncluded.copyInto( notIncl ); + + for( int i = 0; i < excl.length; i++ ) + { + if( !couldHoldIncluded( excl[i] ) ) + { + scandir( new File( basedir, excl[i] ), + excl[i] + File.separator, false ); + } + } + + for( int i = 0; i < notIncl.length; i++ ) + { + if( !couldHoldIncluded( notIncl[i] ) ) + { + scandir( new File( basedir, notIncl[i] ), + notIncl[i] + File.separator, false ); + } + } + + haveSlowResults = true; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/ExitException.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/ExitException.java new file mode 100644 index 000000000..8e812b1f2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/ExitException.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Used to report exit status of classes which call System.exit() + * + * @author Conor MacNeill + * @see NoExitSecurityManager + */ +public class ExitException extends SecurityException +{ + + private int status; + + /** + * Constructs an exit exception. + * + * @param status the status code returned via System.exit() + */ + public ExitException( int status ) + { + super( "ExitException: status " + status ); + this.status = status; + } + + /** + * @return the status code return via System.exit() + */ + public int getStatus() + { + return status; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/FileScanner.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/FileScanner.java new file mode 100644 index 000000000..a23d95e26 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/FileScanner.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; + +/** + * An interface used to describe the actions required by any type of directory + * scanner. + * + * @author RT + */ +public interface FileScanner +{ + /** + * Adds an array with default exclusions to the current exclusions set. + */ + void addDefaultExcludes(); + + /** + * Gets the basedir that is used for scanning. This is the directory that is + * scanned recursively. + * + * @return the basedir that is used for scanning + */ + File getBasedir(); + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the directories + */ + String[] getExcludedDirectories(); + + /** + * Get the names of the files that matched at least one of the include + * patterns, an matched also at least one of the exclude patterns. The names + * are relative to the basedir. + * + * @return the names of the files + */ + String[] getExcludedFiles(); + + /** + * Get the names of the directories that matched at least one of the include + * patterns, an matched none of the exclude patterns. The names are relative + * to the basedir. + * + * @return the names of the directories + */ + String[] getIncludedDirectories(); + + /** + * Get the names of the files that matched at least one of the include + * patterns, an matched none of the exclude patterns. The names are relative + * to the basedir. + * + * @return the names of the files + */ + String[] getIncludedFiles(); + + /** + * Get the names of the directories that matched at none of the include + * patterns. The names are relative to the basedir. + * + * @return the names of the directories + */ + String[] getNotIncludedDirectories(); + + /** + * Get the names of the files that matched at none of the include patterns. + * The names are relative to the basedir. + * + * @return the names of the files + */ + String[] getNotIncludedFiles(); + + /** + * Scans the base directory for files that match at least one include + * pattern, and don't match any exclude patterns. + * + */ + void scan(); + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. + * + * @param basedir the (non-null) basedir for scanning + */ + void setBasedir( String basedir ); + + /** + * Sets the basedir for scanning. This is the directory that is scanned + * recursively. + * + * @param basedir the basedir for scanning + */ + void setBasedir( File basedir ); + + /** + * Sets the set of exclude patterns to use. + * + * @param excludes list of exclude patterns + */ + void setExcludes( String[] excludes ); + + /** + * Sets the set of include patterns to use. + * + * @param includes list of include patterns + */ + void setIncludes( String[] includes ); + + /** + * Sets the case sensitivity of the file system + * + * @param isCaseSensitive The new CaseSensitive value + */ + void setCaseSensitive( boolean isCaseSensitive ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/IntrospectionHelper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/IntrospectionHelper.java new file mode 100644 index 000000000..8b7160c9f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/IntrospectionHelper.java @@ -0,0 +1,848 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; + +/** + * Helper class that collects the methods a task or nested element holds to set + * attributes, create nested elements or hold PCDATA elements. + * + * @author Stefan Bodewig + */ +public class IntrospectionHelper implements BuildListener +{ + + /** + * instances we've already created + */ + private static Hashtable helpers = new Hashtable(); + + /** + * The method to add PCDATA stuff. + */ + private Method addText = null; + + /** + * holds the attribute setter methods. + */ + private Hashtable attributeSetters; + + /** + * holds the types of the attributes that could be set. + */ + private Hashtable attributeTypes; + + /** + * The Class that's been introspected. + */ + private Class bean; + + /** + * Holds methods to create nested elements. + */ + private Hashtable nestedCreators; + + /** + * Holds methods to store configured nested elements. + */ + private Hashtable nestedStorers; + + /** + * Holds the types of nested elements that could be created. + */ + private Hashtable nestedTypes; + + private IntrospectionHelper( final Class bean ) + { + attributeTypes = new Hashtable(); + attributeSetters = new Hashtable(); + nestedTypes = new Hashtable(); + nestedCreators = new Hashtable(); + nestedStorers = new Hashtable(); + + this.bean = bean; + + Method[] methods = bean.getMethods(); + for( int i = 0; i < methods.length; i++ ) + { + final Method m = methods[i]; + final String name = m.getName(); + Class returnType = m.getReturnType(); + Class[] args = m.getParameterTypes(); + + // not really user settable properties on tasks + if( org.apache.tools.ant.Task.class.isAssignableFrom( bean ) + && args.length == 1 && + ( + ( + "setLocation".equals( name ) && org.apache.tools.ant.Location.class.equals( args[0] ) + ) || ( + "setTaskType".equals( name ) && java.lang.String.class.equals( args[0] ) + ) + ) ) + { + continue; + } + + // hide addTask for TaskContainers + if( org.apache.tools.ant.TaskContainer.class.isAssignableFrom( bean ) + && args.length == 1 && "addTask".equals( name ) + && org.apache.tools.ant.Task.class.equals( args[0] ) ) + { + continue; + } + + if( "addText".equals( name ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && java.lang.String.class.equals( args[0] ) ) + { + + addText = methods[i]; + + } + else if( name.startsWith( "set" ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && !args[0].isArray() ) + { + + String propName = getPropertyName( name, "set" ); + if( attributeSetters.get( propName ) != null ) + { + if( java.lang.String.class.equals( args[0] ) ) + { + /* + * Ignore method m, as there is an overloaded + * form of this method that takes in a + * non-string argument, which gains higher + * priority. + */ + continue; + } + /* + * If the argument is not a String, and if there + * is an overloaded form of this method already defined, + * we just override that with the new one. + * This mechanism does not guarantee any specific order + * in which the methods will be selected: so any code + * that depends on the order in which "set" methods have + * been defined, is not guaranteed to be selected in any + * particular order. + */ + } + AttributeSetter as = createAttributeSetter( m, args[0] ); + if( as != null ) + { + attributeTypes.put( propName, args[0] ); + attributeSetters.put( propName, as ); + } + + } + else if( name.startsWith( "create" ) + && !returnType.isArray() + && !returnType.isPrimitive() + && args.length == 0 ) + { + + String propName = getPropertyName( name, "create" ); + nestedTypes.put( propName, returnType ); + nestedCreators.put( propName, + new NestedCreator() + { + + public Object create( Object parent ) + throws InvocationTargetException, + IllegalAccessException + { + + return m.invoke( parent, new Object[]{} ); + } + + } ); + + } + else if( name.startsWith( "addConfigured" ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && !java.lang.String.class.equals( args[0] ) + && !args[0].isArray() + && !args[0].isPrimitive() ) + { + + try + { + final Constructor c = + args[0].getConstructor( new Class[]{} ); + String propName = getPropertyName( name, "addConfigured" ); + nestedTypes.put( propName, args[0] ); + nestedCreators.put( propName, + new NestedCreator() + { + + public Object create( Object parent ) + throws InvocationTargetException, IllegalAccessException, InstantiationException + { + + Object o = c.newInstance( new Object[]{} ); + return o; + } + + } ); + nestedStorers.put( propName, + new NestedStorer() + { + + public void store( Object parent, Object child ) + throws InvocationTargetException, IllegalAccessException, InstantiationException + { + + m.invoke( parent, new Object[]{child} ); + } + + } ); + } + catch( NoSuchMethodException nse ) + { + } + } + else if( name.startsWith( "add" ) + && java.lang.Void.TYPE.equals( returnType ) + && args.length == 1 + && !java.lang.String.class.equals( args[0] ) + && !args[0].isArray() + && !args[0].isPrimitive() ) + { + + try + { + final Constructor c = + args[0].getConstructor( new Class[]{} ); + String propName = getPropertyName( name, "add" ); + nestedTypes.put( propName, args[0] ); + nestedCreators.put( propName, + new NestedCreator() + { + + public Object create( Object parent ) + throws InvocationTargetException, IllegalAccessException, InstantiationException + { + + Object o = c.newInstance( new Object[]{} ); + m.invoke( parent, new Object[]{o} ); + return o; + } + + } ); + } + catch( NoSuchMethodException nse ) + { + } + } + } + } + + /** + * Factory method for helper objects. + * + * @param c Description of Parameter + * @return The Helper value + */ + public static synchronized IntrospectionHelper getHelper( Class c ) + { + IntrospectionHelper ih = ( IntrospectionHelper )helpers.get( c ); + if( ih == null ) + { + ih = new IntrospectionHelper( c ); + helpers.put( c, ih ); + } + return ih; + } + + /** + * Sets the named attribute. + * + * @param p The new Attribute value + * @param element The new Attribute value + * @param attributeName The new Attribute value + * @param value The new Attribute value + * @exception BuildException Description of Exception + */ + public void setAttribute( Project p, Object element, String attributeName, + String value ) + throws BuildException + { + AttributeSetter as = ( AttributeSetter )attributeSetters.get( attributeName ); + if( as == null ) + { + String msg = getElementName( p, element ) + + //String msg = "Class " + element.getClass().getName() + + " doesn't support the \"" + attributeName + "\" attribute."; + throw new BuildException( msg ); + } + try + { + as.set( p, element, value ); + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + /** + * returns the type of a named attribute. + * + * @param attributeName Description of Parameter + * @return The AttributeType value + * @exception BuildException Description of Exception + */ + public Class getAttributeType( String attributeName ) + throws BuildException + { + Class at = ( Class )attributeTypes.get( attributeName ); + if( at == null ) + { + String msg = "Class " + bean.getName() + + " doesn't support the \"" + attributeName + "\" attribute."; + throw new BuildException( msg ); + } + return at; + } + + /** + * Return all attribues supported by the introspected class. + * + * @return The Attributes value + */ + public Enumeration getAttributes() + { + return attributeSetters.keys(); + } + + /** + * returns the type of a named nested element. + * + * @param elementName Description of Parameter + * @return The ElementType value + * @exception BuildException Description of Exception + */ + public Class getElementType( String elementName ) + throws BuildException + { + Class nt = ( Class )nestedTypes.get( elementName ); + if( nt == null ) + { + String msg = "Class " + bean.getName() + + " doesn't support the nested \"" + elementName + "\" element."; + throw new BuildException( msg ); + } + return nt; + } + + /** + * Return all nested elements supported by the introspected class. + * + * @return The NestedElements value + */ + public Enumeration getNestedElements() + { + return nestedTypes.keys(); + } + + /** + * Adds PCDATA areas. + * + * @param project The feature to be added to the Text attribute + * @param element The feature to be added to the Text attribute + * @param text The feature to be added to the Text attribute + */ + public void addText( Project project, Object element, String text ) + { + if( addText == null ) + { + String msg = getElementName( project, element ) + + //String msg = "Class " + element.getClass().getName() + + " doesn't support nested text data."; + throw new BuildException( msg ); + } + try + { + addText.invoke( element, new String[]{text} ); + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + public void buildFinished( BuildEvent event ) + { + attributeTypes.clear(); + attributeSetters.clear(); + nestedTypes.clear(); + nestedCreators.clear(); + addText = null; + helpers.clear(); + } + + public void buildStarted( BuildEvent event ) { } + + /** + * Creates a named nested element. + * + * @param project Description of Parameter + * @param element Description of Parameter + * @param elementName Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Object createElement( Project project, Object element, String elementName ) + throws BuildException + { + NestedCreator nc = ( NestedCreator )nestedCreators.get( elementName ); + if( nc == null ) + { + String msg = getElementName( project, element ) + + " doesn't support the nested \"" + elementName + "\" element."; + throw new BuildException( msg ); + } + try + { + Object nestedElement = nc.create( element ); + if( nestedElement instanceof ProjectComponent ) + { + ( ( ProjectComponent )nestedElement ).setProject( project ); + } + return nestedElement; + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InstantiationException ine ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ine ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + public void messageLogged( BuildEvent event ) { } + + /** + * Creates a named nested element. + * + * @param project Description of Parameter + * @param element Description of Parameter + * @param child Description of Parameter + * @param elementName Description of Parameter + * @exception BuildException Description of Exception + */ + public void storeElement( Project project, Object element, Object child, String elementName ) + throws BuildException + { + if( elementName == null ) + { + return; + } + NestedStorer ns = ( NestedStorer )nestedStorers.get( elementName ); + if( ns == null ) + { + return; + } + try + { + ns.store( element, child ); + } + catch( IllegalAccessException ie ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ie ); + } + catch( InstantiationException ine ) + { + // impossible as getMethods should only return public methods + throw new BuildException( ine ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( t ); + } + } + + /** + * Does the introspected class support PCDATA? + * + * @return Description of the Returned Value + */ + public boolean supportsCharacters() + { + return addText != null; + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) { } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + + protected String getElementName( Project project, Object element ) + { + Hashtable elements = project.getTaskDefinitions(); + String typeName = "task"; + if( !elements.contains( element.getClass() ) ) + { + elements = project.getDataTypeDefinitions(); + typeName = "data type"; + if( !elements.contains( element.getClass() ) ) + { + elements = null; + } + } + + if( elements != null ) + { + Enumeration e = elements.keys(); + while( e.hasMoreElements() ) + { + String elementName = ( String )e.nextElement(); + Class elementClass = ( Class )elements.get( elementName ); + if( element.getClass().equals( elementClass ) ) + { + return "The <" + elementName + "> " + typeName; + } + } + } + + return "Class " + element.getClass().getName(); + } + + /** + * extract the name of a property from a method name - subtracting a given + * prefix. + * + * @param methodName Description of Parameter + * @param prefix Description of Parameter + * @return The PropertyName value + */ + private String getPropertyName( String methodName, String prefix ) + { + int start = prefix.length(); + return methodName.substring( start ).toLowerCase( Locale.US ); + } + + /** + * Create a proper implementation of AttributeSetter for the given attribute + * type. + * + * @param m Description of Parameter + * @param arg Description of Parameter + * @return Description of the Returned Value + */ + private AttributeSetter createAttributeSetter( final Method m, + final Class arg ) + { + + // simplest case - setAttribute expects String + if( java.lang.String.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new String[]{value} ); + } + }; + // now for the primitive types, use their wrappers + } + else if( java.lang.Character.class.equals( arg ) + || java.lang.Character.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Character[]{new Character( value.charAt( 0 ) )} ); + } + + }; + } + else if( java.lang.Byte.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Byte[]{new Byte( value )} ); + } + + }; + } + else if( java.lang.Short.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Short[]{new Short( value )} ); + } + + }; + } + else if( java.lang.Integer.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Integer[]{new Integer( value )} ); + } + + }; + } + else if( java.lang.Long.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Long[]{new Long( value )} ); + } + + }; + } + else if( java.lang.Float.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Float[]{new Float( value )} ); + } + + }; + } + else if( java.lang.Double.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Double[]{new Double( value )} ); + } + + }; + // boolean gets an extra treatment, because we have a nice method + // in Project + } + else if( java.lang.Boolean.class.equals( arg ) + || java.lang.Boolean.TYPE.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, + new Boolean[]{new Boolean( Project.toBoolean( value ) )} ); + } + + }; + // Class doesn't have a String constructor but a decent factory method + } + else if( java.lang.Class.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException, BuildException + { + try + { + m.invoke( parent, new Class[]{Class.forName( value )} ); + } + catch( ClassNotFoundException ce ) + { + throw new BuildException( ce ); + } + } + }; + // resolve relative paths through Project + } + else if( java.io.File.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new File[]{p.resolveFile( value )} ); + } + + }; + // resolve relative paths through Project + } + else if( org.apache.tools.ant.types.Path.class.equals( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException + { + m.invoke( parent, new Path[]{new Path( p, value )} ); + } + + }; + // EnumeratedAttributes have their own helper class + } + else if( org.apache.tools.ant.types.EnumeratedAttribute.class.isAssignableFrom( arg ) ) + { + return + new AttributeSetter() + { + public void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException, BuildException + { + try + { + org.apache.tools.ant.types.EnumeratedAttribute ea = ( org.apache.tools.ant.types.EnumeratedAttribute )arg.newInstance(); + ea.setValue( value ); + m.invoke( parent, new EnumeratedAttribute[]{ea} ); + } + catch( InstantiationException ie ) + { + throw new BuildException( ie ); + } + } + }; + + // worst case. look for a public String constructor and use it + } + else + { + + try + { + final Constructor c = + arg.getConstructor( new Class[]{java.lang.String.class} ); + + return + new AttributeSetter() + { + public void set( Project p, Object parent, + String value ) + throws InvocationTargetException, IllegalAccessException, BuildException + { + try + { + Object attribute = c.newInstance( new String[]{value} ); + if( attribute instanceof ProjectComponent ) + { + ( ( ProjectComponent )attribute ).setProject( p ); + } + m.invoke( parent, new Object[]{attribute} ); + } + catch( InstantiationException ie ) + { + throw new BuildException( ie ); + } + } + }; + } + catch( NoSuchMethodException nme ) + { + } + } + + return null; + } + + private interface AttributeSetter + { + void set( Project p, Object parent, String value ) + throws InvocationTargetException, IllegalAccessException, + BuildException; + } + + private interface NestedCreator + { + Object create( Object parent ) + throws InvocationTargetException, IllegalAccessException, InstantiationException; + } + + private interface NestedStorer + { + void store( Object parent, Object child ) + throws InvocationTargetException, IllegalAccessException, InstantiationException; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Launcher.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Launcher.java new file mode 100644 index 000000000..0a220ec89 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Launcher.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FilenameFilter; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Properties; +import java.util.StringTokenizer; + +/** + * This is the Ant command line front end to end. This front end works out where + * ant is installed and loads the ant libraries before starting Ant proper. + * + * @author Conor MacNeill + */ +public class Launcher +{ + + public static void main( String[] args ) + { + File antHome = null; + ClassLoader systemClassLoader = Launcher.class.getClassLoader(); + if( systemClassLoader == null ) + { + antHome = determineAntHome11(); + } + else + { + antHome = determineAntHome( systemClassLoader ); + } + if( antHome == null ) + { + System.err.println( "Unable to determine ANT_HOME" ); + System.exit( 1 ); + } + + System.out.println( "ANT_HOME is " + antHome ); + + // We now create the class loader with which we are going to launch ant + AntClassLoader antLoader = new AntClassLoader( systemClassLoader, false ); + + // need to find tools.jar + addToolsJar( antLoader ); + + // add everything in the lib directory to this classloader + File libDir = new File( antHome, "lib" ); + addDirJars( antLoader, libDir ); + + File optionalDir = new File( antHome, "lib/optional" ); + addDirJars( antLoader, optionalDir ); + + Properties launchProperties = new Properties(); + launchProperties.put( "ant.home", antHome.getAbsolutePath() ); + + try + { + Class mainClass = antLoader.loadClass( "org.apache.tools.ant.Main" ); + antLoader.initializeClass( mainClass ); + + final Class[] param = {Class.forName( "[Ljava.lang.String;" ), + Properties.class, ClassLoader.class}; + final Method startMethod = mainClass.getMethod( "start", param ); + final Object[] argument = {args, launchProperties, systemClassLoader}; + startMethod.invoke( null, argument ); + } + catch( Exception e ) + { + System.out.println( "Exception running Ant: " + e.getClass().getName() + ": " + e.getMessage() ); + e.printStackTrace(); + } + } + + private static void addDirJars( AntClassLoader classLoader, File jarDir ) + { + String[] fileList = jarDir.list( + new FilenameFilter() + { + public boolean accept( File dir, String name ) + { + return name.endsWith( ".jar" ); + } + } ); + + if( fileList != null ) + { + for( int i = 0; i < fileList.length; ++i ) + { + File jarFile = new File( jarDir, fileList[i] ); + classLoader.addPathElement( jarFile.getAbsolutePath() ); + } + } + } + + private static void addToolsJar( AntClassLoader antLoader ) + { + String javaHome = System.getProperty( "java.home" ); + if( javaHome.endsWith( "jre" ) ) + { + javaHome = javaHome.substring( 0, javaHome.length() - 4 ); + } + System.out.println( "Java home is " + javaHome ); + File toolsJar = new File( javaHome, "lib/tools.jar" ); + if( !toolsJar.exists() ) + { + System.out.println( "Unable to find tools.jar at " + toolsJar.getPath() ); + } + else + { + antLoader.addPathElement( toolsJar.getAbsolutePath() ); + } + } + + private static File determineAntHome( ClassLoader systemClassLoader ) + { + try + { + String className = Launcher.class.getName().replace( '.', '/' ) + ".class"; + URL classResource = systemClassLoader.getResource( className ); + String fileComponent = classResource.getFile(); + if( classResource.getProtocol().equals( "file" ) ) + { + // Class comes from a directory of class files rather than + // from a jar. + int classFileIndex = fileComponent.lastIndexOf( className ); + if( classFileIndex != -1 ) + { + fileComponent = fileComponent.substring( 0, classFileIndex ); + } + File classFilesDir = new File( fileComponent ); + File buildDir = new File( classFilesDir.getParent() ); + File devAntHome = new File( buildDir.getParent() ); + return devAntHome; + } + else if( classResource.getProtocol().equals( "jar" ) ) + { + // Class is coming from a jar. The file component of the URL + // is actually the URL of the jar file + int classSeparatorIndex = fileComponent.lastIndexOf( "!" ); + if( classSeparatorIndex != -1 ) + { + fileComponent = fileComponent.substring( 0, classSeparatorIndex ); + } + URL antJarURL = new URL( fileComponent ); + File antJarFile = new File( antJarURL.getFile() ); + File libDirectory = new File( antJarFile.getParent() ); + File antHome = new File( libDirectory.getParent() ); + return antHome; + } + } + catch( MalformedURLException e ) + { + e.printStackTrace(); + } + return null; + } + + private static File determineAntHome11() + { + String classpath = System.getProperty( "java.class.path" ); + StringTokenizer tokenizer = new StringTokenizer( classpath, System.getProperty( "path.separator" ) ); + while( tokenizer.hasMoreTokens() ) + { + String path = tokenizer.nextToken(); + if( path.endsWith( "ant.jar" ) ) + { + File antJarFile = new File( path ); + File libDirectory = new File( antJarFile.getParent() ); + File antHome = new File( libDirectory.getParent() ); + return antHome; + } + } + return null; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Location.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Location.java new file mode 100644 index 000000000..e48a844dc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Location.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Stores the file name and line number in a file. + * + * @author RT + */ +public class Location +{ + + public final static Location UNKNOWN_LOCATION = new Location(); + private int columnNumber; + private String fileName; + private int lineNumber; + + /** + * Creates a location consisting of a file name but no line number. + * + * @param fileName Description of Parameter + */ + public Location( String fileName ) + { + this( fileName, 0, 0 ); + } + + /** + * Creates a location consisting of a file name and line number. + * + * @param fileName Description of Parameter + * @param lineNumber Description of Parameter + * @param columnNumber Description of Parameter + */ + public Location( String fileName, int lineNumber, int columnNumber ) + { + this.fileName = fileName; + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; + } + + /** + * Creates an "unknown" location. + */ + private Location() + { + this( null, 0, 0 ); + } + + /** + * Returns the file name, line number and a trailing space. An error message + * can be appended easily. For unknown locations, returns an empty string. + * + * @return Description of the Returned Value + */ + public String toString() + { + StringBuffer buf = new StringBuffer(); + + if( fileName != null ) + { + buf.append( fileName ); + + if( lineNumber != 0 ) + { + buf.append( ":" ); + buf.append( lineNumber ); + } + + buf.append( ": " ); + } + + return buf.toString(); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Main.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Main.java new file mode 100644 index 000000000..0c605f732 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Main.java @@ -0,0 +1,842 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Enumeration; +import java.util.Properties; +import java.util.Vector; + +/** + * Command line entry point into Ant. This class is entered via the cannonical + * `public static void main` entry point and reads the command line arguments. + * It then assembles and executes an Ant project.

          + * + * If you integrating Ant into some other tool, this is not the class to use as + * an entry point. Please see the source code of this class to see how it + * manipulates the Ant project classes. + * + * @author duncan@x180.com + */ +public class Main +{ + + /** + * The default build file name + */ + public final static String DEFAULT_BUILD_FILENAME = "build.xml"; + + private static String antVersion = null; + + /** + * Our current message output status. Follows Project.MSG_XXX + */ + private int msgOutputLevel = Project.MSG_INFO; + /** + * null + */ + + /** + * Stream that we are using for logging + */ + private PrintStream out = System.out; + + /** + * Stream that we are using for logging error messages + */ + private PrintStream err = System.err; + + /** + * The build targets + */ + private Vector targets = new Vector( 5 ); + + /** + * Set of properties that can be used by tasks + */ + private Properties definedProps = new Properties(); + + /** + * Names of classes to add as listeners to project + */ + private Vector listeners = new Vector( 5 ); + + /** + * The Ant logger class. There may be only one logger. It will have the + * right to use the 'out' PrintStream. The class must implements the + * BuildLogger interface + */ + private String loggerClassname = null; + + /** + * Indicates whether output to the log is to be unadorned. + */ + private boolean emacsMode = false; + + /** + * Indicates if this ant should be run. + */ + private boolean readyToRun = false; + + /** + * Indicates we should only parse and display the project help information + */ + private boolean projectHelp = false; + + /** + * File that we are using for configuration + */ + private File buildFile; + + protected Main( String[] args ) + throws BuildException + { + + String searchForThis = null; + + // cycle through given args + + for( int i = 0; i < args.length; i++ ) + { + String arg = args[i]; + + if( arg.equals( "-help" ) ) + { + printUsage(); + return; + } + else if( arg.equals( "-version" ) ) + { + printVersion(); + return; + } + else if( arg.equals( "-quiet" ) || arg.equals( "-q" ) ) + { + msgOutputLevel = Project.MSG_WARN; + } + else if( arg.equals( "-verbose" ) || arg.equals( "-v" ) ) + { + printVersion(); + msgOutputLevel = Project.MSG_VERBOSE; + } + else if( arg.equals( "-debug" ) ) + { + printVersion(); + msgOutputLevel = Project.MSG_DEBUG; + } + else if( arg.equals( "-logfile" ) || arg.equals( "-l" ) ) + { + try + { + File logFile = new File( args[i + 1] ); + i++; + out = new PrintStream( new FileOutputStream( logFile ) ); + err = out; + System.setOut( out ); + System.setErr( out ); + } + catch( IOException ioe ) + { + String msg = "Cannot write on the specified log file. " + + "Make sure the path exists and you have write permissions."; + System.out.println( msg ); + return; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + String msg = "You must specify a log file when " + + "using the -log argument"; + System.out.println( msg ); + return; + } + } + else if( arg.equals( "-buildfile" ) || arg.equals( "-file" ) || arg.equals( "-f" ) ) + { + try + { + buildFile = new File( args[i + 1] ); + i++; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + String msg = "You must specify a buildfile when " + + "using the -buildfile argument"; + System.out.println( msg ); + return; + } + } + else if( arg.equals( "-listener" ) ) + { + try + { + listeners.addElement( args[i + 1] ); + i++; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + String msg = "You must specify a classname when " + + "using the -listener argument"; + System.out.println( msg ); + return; + } + } + else if( arg.startsWith( "-D" ) ) + { + + /* + * Interestingly enough, we get to here when a user + * uses -Dname=value. However, in some cases, the JDK + * goes ahead * and parses this out to args + * {"-Dname", "value"} + * so instead of parsing on "=", we just make the "-D" + * characters go away and skip one argument forward. + * + * I don't know how to predict when the JDK is going + * to help or not, so we simply look for the equals sign. + */ + String name = arg.substring( 2, arg.length() ); + String value = null; + int posEq = name.indexOf( "=" ); + if( posEq > 0 ) + { + value = name.substring( posEq + 1 ); + name = name.substring( 0, posEq ); + } + else if( i < args.length - 1 ) + value = args[++i]; + + definedProps.put( name, value ); + } + else if( arg.equals( "-logger" ) ) + { + if( loggerClassname != null ) + { + System.out.println( "Only one logger class may be specified." ); + return; + } + try + { + loggerClassname = args[++i]; + } + catch( ArrayIndexOutOfBoundsException aioobe ) + { + System.out.println( "You must specify a classname when " + + "using the -logger argument" ); + return; + } + } + else if( arg.equals( "-emacs" ) ) + { + emacsMode = true; + } + else if( arg.equals( "-projecthelp" ) ) + { + // set the flag to display the targets and quit + projectHelp = true; + } + else if( arg.equals( "-find" ) ) + { + // eat up next arg if present, default to build.xml + if( i < args.length - 1 ) + { + searchForThis = args[++i]; + } + else + { + searchForThis = DEFAULT_BUILD_FILENAME; + } + } + else if( arg.startsWith( "-" ) ) + { + // we don't have any more args to recognize! + String msg = "Unknown argument: " + arg; + System.out.println( msg ); + printUsage(); + return; + } + else + { + // if it's no other arg, it may be the target + targets.addElement( arg ); + } + + } + + // if buildFile was not specified on the command line, + if( buildFile == null ) + { + // but -find then search for it + if( searchForThis != null ) + { + buildFile = findBuildFile( System.getProperty( "user.dir" ), + searchForThis ); + } + else + { + buildFile = new File( DEFAULT_BUILD_FILENAME ); + } + } + + // make sure buildfile exists + if( !buildFile.exists() ) + { + System.out.println( "Buildfile: " + buildFile + " does not exist!" ); + throw new BuildException( "Build failed" ); + } + + // make sure it's not a directory (this falls into the ultra + // paranoid lets check everything catagory + + if( buildFile.isDirectory() ) + { + System.out.println( "What? Buildfile: " + buildFile + " is a dir!" ); + throw new BuildException( "Build failed" ); + } + + readyToRun = true; + } + + public static synchronized String getAntVersion() + throws BuildException + { + if( antVersion == null ) + { + try + { + Properties props = new Properties(); + InputStream in = + Main.class.getResourceAsStream( "/org/apache/tools/ant/version.txt" ); + props.load( in ); + in.close(); + + String lSep = System.getProperty( "line.separator" ); + StringBuffer msg = new StringBuffer(); + msg.append( "Apache Ant version " ); + msg.append( props.getProperty( "VERSION" ) ); + msg.append( " compiled on " ); + msg.append( props.getProperty( "DATE" ) ); + antVersion = msg.toString(); + } + catch( IOException ioe ) + { + throw new BuildException( "Could not load the version information:" + + ioe.getMessage() ); + } + catch( NullPointerException npe ) + { + throw new BuildException( "Could not load the version information." ); + } + } + return antVersion; + } + + + /** + * Command line entry point. This method kicks off the building of a project + * object and executes a build using either a given target or the default + * target. + * + * @param args Command line args. + */ + public static void main( String[] args ) + { + start( args, null, null ); + } + + /** + * Entry point method. + * + * @param args Description of Parameter + * @param additionalUserProperties Description of Parameter + * @param coreLoader Description of Parameter + */ + public static void start( String[] args, Properties additionalUserProperties, + ClassLoader coreLoader ) + { + Main m = null; + + try + { + m = new Main( args ); + } + catch( Throwable exc ) + { + printMessage( exc ); + System.exit( 1 ); + } + + if( additionalUserProperties != null ) + { + for( Enumeration e = additionalUserProperties.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + String property = additionalUserProperties.getProperty( key ); + m.definedProps.put( key, property ); + } + } + + try + { + m.runBuild( coreLoader ); + System.exit( 0 ); + } + catch( BuildException be ) + { + if( m.err != System.err ) + { + printMessage( be ); + } + System.exit( 1 ); + } + catch( Throwable exc ) + { + exc.printStackTrace(); + printMessage( exc ); + System.exit( 1 ); + } + } + + /** + * Search for the insert position to keep names a sorted list of Strings + * + * @param names Description of Parameter + * @param name Description of Parameter + * @return Description of the Returned Value + */ + private static int findTargetPosition( Vector names, String name ) + { + int res = names.size(); + for( int i = 0; i < names.size() && res == names.size(); i++ ) + { + if( name.compareTo( ( String )names.elementAt( i ) ) < 0 ) + { + res = i; + } + } + return res; + } + + /** + * Print the project description, if any + * + * @param project Description of Parameter + */ + private static void printDescription( Project project ) + { + if( project.getDescription() != null ) + { + System.out.println( project.getDescription() ); + } + } + + /** + * Prints the message of the Throwable if it's not null. + * + * @param t Description of Parameter + */ + private static void printMessage( Throwable t ) + { + String message = t.getMessage(); + if( message != null ) + { + System.err.println( message ); + } + } + + /** + * Print out a list of all targets in the current buildfile + * + * @param project Description of Parameter + * @param printSubTargets Description of Parameter + */ + private static void printTargets( Project project, boolean printSubTargets ) + { + // find the target with the longest name + int maxLength = 0; + Enumeration ptargets = project.getTargets().elements(); + String targetName; + String targetDescription; + Target currentTarget; + // split the targets in top-level and sub-targets depending + // on the presence of a description + Vector topNames = new Vector(); + Vector topDescriptions = new Vector(); + Vector subNames = new Vector(); + + while( ptargets.hasMoreElements() ) + { + currentTarget = ( Target )ptargets.nextElement(); + targetName = currentTarget.getName(); + targetDescription = currentTarget.getDescription(); + // maintain a sorted list of targets + if( targetDescription == null ) + { + int pos = findTargetPosition( subNames, targetName ); + subNames.insertElementAt( targetName, pos ); + } + else + { + int pos = findTargetPosition( topNames, targetName ); + topNames.insertElementAt( targetName, pos ); + topDescriptions.insertElementAt( targetDescription, pos ); + if( targetName.length() > maxLength ) + { + maxLength = targetName.length(); + } + } + } + + printTargets( topNames, topDescriptions, "Main targets:", maxLength ); + + if( printSubTargets ) + { + printTargets( subNames, null, "Subtargets:", 0 ); + } + + String defaultTarget = project.getDefaultTarget(); + if( defaultTarget != null && !"".equals( defaultTarget ) ) + {// shouldn't need to check but... + System.out.println( "Default target: " + defaultTarget ); + } + } + + /** + * Output a formatted list of target names with an optional description + * + * @param names Description of Parameter + * @param descriptions Description of Parameter + * @param heading Description of Parameter + * @param maxlen Description of Parameter + */ + private static void printTargets( Vector names, Vector descriptions, String heading, int maxlen ) + { + // now, start printing the targets and their descriptions + String lSep = System.getProperty( "line.separator" ); + // got a bit annoyed that I couldn't find a pad function + String spaces = " "; + while( spaces.length() < maxlen ) + { + spaces += spaces; + } + StringBuffer msg = new StringBuffer(); + msg.append( heading + lSep + lSep ); + for( int i = 0; i < names.size(); i++ ) + { + msg.append( " " ); + msg.append( names.elementAt( i ) ); + if( descriptions != null ) + { + msg.append( spaces.substring( 0, maxlen - ( ( String )names.elementAt( i ) ).length() + 2 ) ); + msg.append( descriptions.elementAt( i ) ); + } + msg.append( lSep ); + } + System.out.println( msg.toString() ); + } + + /** + * Prints the usage of how to use this class to System.out + */ + private static void printUsage() + { + String lSep = System.getProperty( "line.separator" ); + StringBuffer msg = new StringBuffer(); + msg.append( "ant [options] [target [target2 [target3] ...]]" + lSep ); + msg.append( "Options: " + lSep ); + msg.append( " -help print this message" + lSep ); + msg.append( " -projecthelp print project help information" + lSep ); + msg.append( " -version print the version information and exit" + lSep ); + msg.append( " -quiet be extra quiet" + lSep ); + msg.append( " -verbose be extra verbose" + lSep ); + msg.append( " -debug print debugging information" + lSep ); + msg.append( " -emacs produce logging information without adornments" + lSep ); + msg.append( " -logfile use given file for log" + lSep ); + msg.append( " -logger the class which is to perform logging" + lSep ); + msg.append( " -listener add an instance of class as a project listener" + lSep ); + msg.append( " -buildfile use given buildfile" + lSep ); + msg.append( " -D= use value for given property" + lSep ); + msg.append( " -find search for buildfile towards the root of the" + lSep ); + msg.append( " filesystem and use it" + lSep ); + System.out.println( msg.toString() ); + } + + private static void printVersion() + throws BuildException + { + System.out.println( getAntVersion() ); + } + + protected void addBuildListeners( Project project ) + { + + // Add the default listener + project.addBuildListener( createLogger() ); + + for( int i = 0; i < listeners.size(); i++ ) + { + String className = ( String )listeners.elementAt( i ); + try + { + BuildListener listener = + ( BuildListener )Class.forName( className ).newInstance(); + project.addBuildListener( listener ); + } + catch( Throwable exc ) + { + throw new BuildException( "Unable to instantiate listener " + className, exc ); + } + } + } + + /** + * Helper to get the parent file for a given file.

          + * + * Added to simulate File.getParentFile() from JDK 1.2. + * + * @param file File + * @return Parent file or null if none + */ + private File getParentFile( File file ) + { + String filename = file.getAbsolutePath(); + file = new File( filename ); + filename = file.getParent(); + + if( filename != null && msgOutputLevel >= Project.MSG_VERBOSE ) + { + System.out.println( "Searching in " + filename ); + } + + return ( filename == null ) ? null : new File( filename ); + } + + /** + * Creates the default build logger for sending build events to the ant log. + * + * @return Description of the Returned Value + */ + private BuildLogger createLogger() + { + BuildLogger logger = null; + if( loggerClassname != null ) + { + try + { + logger = ( BuildLogger )( Class.forName( loggerClassname ).newInstance() ); + } + catch( ClassCastException e ) + { + System.err.println( "The specified logger class " + loggerClassname + + " does not implement the BuildLogger interface" ); + throw new RuntimeException(); + } + catch( Exception e ) + { + System.err.println( "Unable to instantiate specified logger class " + + loggerClassname + " : " + e.getClass().getName() ); + throw new RuntimeException(); + } + } + else + { + logger = new DefaultLogger(); + } + + logger.setMessageOutputLevel( msgOutputLevel ); + logger.setOutputPrintStream( out ); + logger.setErrorPrintStream( err ); + logger.setEmacsMode( emacsMode ); + + return logger; + } + + /** + * Search parent directories for the build file.

          + * + * Takes the given target as a suffix to append to each parent directory in + * seach of a build file. Once the root of the file-system has been reached + * an exception is thrown. + * + * @param suffix Suffix filename to look for in parents. + * @param start Description of Parameter + * @return A handle to the build file + * @exception BuildException Failed to locate a build file + */ + private File findBuildFile( String start, String suffix ) + throws BuildException + { + if( msgOutputLevel >= Project.MSG_INFO ) + { + System.out.println( "Searching for " + suffix + " ..." ); + } + + File parent = new File( new File( start ).getAbsolutePath() ); + File file = new File( parent, suffix ); + + // check if the target file exists in the current directory + while( !file.exists() ) + { + // change to parent directory + parent = getParentFile( parent ); + + // if parent is null, then we are at the root of the fs, + // complain that we can't find the build file. + if( parent == null ) + { + throw new BuildException( "Could not locate a build file!" ); + } + + // refresh our file handle + file = new File( parent, suffix ); + } + + return file; + } + + /** + * Executes the build. + * + * @param coreLoader Description of Parameter + * @exception BuildException Description of Exception + */ + private void runBuild( ClassLoader coreLoader ) + throws BuildException + { + + if( !readyToRun ) + { + return; + } + + // track when we started + + if( msgOutputLevel >= Project.MSG_INFO ) + { + System.out.println( "Buildfile: " + buildFile ); + } + + final Project project = new Project(); + project.setCoreLoader( coreLoader ); + + Throwable error = null; + + try + { + addBuildListeners( project ); + + PrintStream err = System.err; + PrintStream out = System.out; + + // use a system manager that prevents from System.exit() + // only in JDK > 1.1 + SecurityManager oldsm = null; + if( !Project.JAVA_1_0.equals( Project.getJavaVersion() ) && + !Project.JAVA_1_1.equals( Project.getJavaVersion() ) ) + { + oldsm = System.getSecurityManager(); + + //SecurityManager can not be installed here for backwards + //compatability reasons (PD). Needs to be loaded prior to + //ant class if we are going to implement it. + //System.setSecurityManager(new NoExitSecurityManager()); + } + try + { + System.setOut( new PrintStream( new DemuxOutputStream( project, false ) ) ); + System.setErr( new PrintStream( new DemuxOutputStream( project, true ) ) ); + + if( !projectHelp ) + { + project.fireBuildStarted(); + } + project.init(); + project.setUserProperty( "ant.version", getAntVersion() ); + + // set user-define properties + Enumeration e = definedProps.keys(); + while( e.hasMoreElements() ) + { + String arg = ( String )e.nextElement(); + String value = ( String )definedProps.get( arg ); + project.setUserProperty( arg, value ); + } + + project.setUserProperty( "ant.file", buildFile.getAbsolutePath() ); + + // first use the ProjectHelper to create the project object + // from the given build file. + String noParserMessage = + "No JAXP compliant XML parser found. Please visit http://xml.apache.org for a suitable parser"; + try + { + Class.forName( "javax.xml.parsers.SAXParserFactory" ); + ProjectHelper.configureProject( project, buildFile ); + } + catch( NoClassDefFoundError ncdfe ) + { + throw new BuildException( noParserMessage, ncdfe ); + } + catch( ClassNotFoundException cnfe ) + { + throw new BuildException( noParserMessage, cnfe ); + } + catch( NullPointerException npe ) + { + throw new BuildException( noParserMessage, npe ); + } + + if( projectHelp ) + { + printDescription( project ); + printTargets( project, msgOutputLevel > Project.MSG_INFO ); + return; + } + + // make sure that we have a target to execute + if( targets.size() == 0 ) + { + targets.addElement( project.getDefaultTarget() ); + } + + project.executeTargets( targets ); + } + finally + { + // put back the original security manager + //The following will never eval to true. (PD) + if( oldsm != null ) + { + System.setSecurityManager( oldsm ); + } + + System.setOut( out ); + System.setErr( err ); + } + } + catch( RuntimeException exc ) + { + error = exc; + throw exc; + } + catch( Error err ) + { + error = err; + throw err; + } + finally + { + if( !projectHelp ) + { + project.fireBuildFinished( error ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/NoBannerLogger.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/NoBannerLogger.java new file mode 100644 index 000000000..9311265f5 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/NoBannerLogger.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Extends DefaultLogger to strip out empty targets. + * + * @author Peter Donald + */ +public class NoBannerLogger extends DefaultLogger +{ + + private final static String lSep = System.getProperty( "line.separator" ); + + protected String targetName; + + public void messageLogged( BuildEvent event ) + { + + if( event.getPriority() > msgOutputLevel || + null == event.getMessage() || + "".equals( event.getMessage().trim() ) ) + { + return; + } + + if( null != targetName ) + { + out.println( lSep + targetName + ":" ); + targetName = null; + } + + super.messageLogged( event ); + } + + public void targetFinished( BuildEvent event ) + { + targetName = null; + } + + public void targetStarted( BuildEvent event ) + { + targetName = event.getTarget().getName(); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/PathTokenizer.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/PathTokenizer.java new file mode 100644 index 000000000..c66d94a07 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/PathTokenizer.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; + +/** + * A Path tokenizer takes a path and returns the components that make up that + * path. The path can use path separators of either ':' or ';' and file + * separators of either '/' or '\' + * + * @author Conor MacNeill (conor@ieee.org) + */ +public class PathTokenizer +{ + + /** + * A String which stores any path components which have been read ahead. + */ + private String lookahead = null; + + /** + * Flag to indicate whether we are running on a platform with a DOS style + * filesystem + */ + private boolean dosStyleFilesystem; + /** + * A tokenizer to break the string up based on the ':' or ';' separators. + */ + private StringTokenizer tokenizer; + + public PathTokenizer( String path ) + { + tokenizer = new StringTokenizer( path, ":;", false ); + dosStyleFilesystem = File.pathSeparatorChar == ';'; + } + + public boolean hasMoreTokens() + { + if( lookahead != null ) + { + return true; + } + + return tokenizer.hasMoreTokens(); + } + + public String nextToken() + throws NoSuchElementException + { + String token = null; + if( lookahead != null ) + { + token = lookahead; + lookahead = null; + } + else + { + token = tokenizer.nextToken().trim(); + } + + if( token.length() == 1 && Character.isLetter( token.charAt( 0 ) ) + && dosStyleFilesystem + && tokenizer.hasMoreTokens() ) + { + // we are on a dos style system so this path could be a drive + // spec. We look at the next token + String nextToken = tokenizer.nextToken().trim(); + if( nextToken.startsWith( "\\" ) || nextToken.startsWith( "/" ) ) + { + // we know we are on a DOS style platform and the next path starts with a + // slash or backslash, so we know this is a drive spec + token += ":" + nextToken; + } + else + { + // store the token just read for next time + lookahead = nextToken; + } + } + + return token; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Project.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Project.java new file mode 100644 index 000000000..c509b8795 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Project.java @@ -0,0 +1,1575 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Modifier; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Stack; +import java.util.Vector; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; +import org.apache.tools.ant.util.FileUtils; + +/** + * Central representation of an Ant project. This class defines a Ant project + * with all of it's targets and tasks. It also provides the mechanism to kick + * off a build using a particular target name.

          + * + * This class also encapsulates methods which allow Files to be refered to using + * abstract path names which are translated to native system file paths at + * runtime as well as defining various project properties. + * + * @author duncan@x180.com + */ + +public class Project +{ + + public final static int MSG_ERR = 0; + public final static int MSG_WARN = 1; + public final static int MSG_INFO = 2; + public final static int MSG_VERBOSE = 3; + public final static int MSG_DEBUG = 4; + + // private set of constants to represent the state + // of a DFS of the Target dependencies + private final static String VISITING = "VISITING"; + private final static String VISITED = "VISITED"; + + public final static String JAVA_1_0 = "1.0"; + public final static String JAVA_1_1 = "1.1"; + public final static String JAVA_1_2 = "1.2"; + public final static String JAVA_1_3 = "1.3"; + public final static String JAVA_1_4 = "1.4"; + + public final static String TOKEN_START = FilterSet.DEFAULT_TOKEN_START; + public final static String TOKEN_END = FilterSet.DEFAULT_TOKEN_END; + + private static String javaVersion; + + private Hashtable properties = new Hashtable(); + private Hashtable userProperties = new Hashtable(); + private Hashtable references = new Hashtable(); + private Hashtable dataClassDefinitions = new Hashtable(); + private Hashtable taskClassDefinitions = new Hashtable(); + private Hashtable createdTasks = new Hashtable(); + private Hashtable targets = new Hashtable(); + private FilterSet globalFilterSet = new FilterSet(); + private FilterSetCollection globalFilters = new FilterSetCollection( globalFilterSet ); + + private Vector listeners = new Vector(); + + /** + * The Ant core classloader - may be null if using system loader + */ + private ClassLoader coreLoader = null; + + /** + * Records the latest task on a thread + */ + private Hashtable threadTasks = new Hashtable(); + private File baseDir; + private String defaultTarget; + private String description; + + private FileUtils fileUtils; + + private String name; + + static + { + + // Determine the Java version by looking at available classes + // java.lang.StrictMath was introduced in JDK 1.3 + // java.lang.ThreadLocal was introduced in JDK 1.2 + // java.lang.Void was introduced in JDK 1.1 + // Count up version until a NoClassDefFoundError ends the try + + try + { + javaVersion = JAVA_1_0; + Class.forName( "java.lang.Void" ); + javaVersion = JAVA_1_1; + Class.forName( "java.lang.ThreadLocal" ); + javaVersion = JAVA_1_2; + Class.forName( "java.lang.StrictMath" ); + javaVersion = JAVA_1_3; + Class.forName( "java.lang.CharSequence" ); + javaVersion = JAVA_1_4; + } + catch( ClassNotFoundException cnfe ) + { + // swallow as we've hit the max class version that + // we have + } + } + + /** + * create a new ant project + */ + public Project() + { + fileUtils = FileUtils.newFileUtils(); + } + + /** + * static query of the java version + * + * @return something like "1.1" or "1.3" + */ + public static String getJavaVersion() + { + return javaVersion; + } + + /** + * returns the boolean equivalent of a string, which is considered true if + * either "on", "true", or "yes" is found, ignoring case. + * + * @param s Description of Parameter + * @return Description of the Returned Value + */ + public static boolean toBoolean( String s ) + { + return ( s.equalsIgnoreCase( "on" ) || + s.equalsIgnoreCase( "true" ) || + s.equalsIgnoreCase( "yes" ) ); + } + + /** + * Translate a path into its native (platform specific) format.

          + * + * This method uses the PathTokenizer class to separate the input path into + * its components. This handles DOS style paths in a relatively sensible + * way. The file separators are then converted to their platform specific + * versions. + * + * @param to_process the path to be converted + * @return the native version of to_process or an empty string if to_process + * is null or empty + */ + public static String translatePath( String to_process ) + { + if( to_process == null || to_process.length() == 0 ) + { + return ""; + } + + StringBuffer path = new StringBuffer( to_process.length() + 50 ); + PathTokenizer tokenizer = new PathTokenizer( to_process ); + while( tokenizer.hasMoreTokens() ) + { + String pathComponent = tokenizer.nextToken(); + pathComponent = pathComponent.replace( '/', File.separatorChar ); + pathComponent = pathComponent.replace( '\\', File.separatorChar ); + if( path.length() != 0 ) + { + path.append( File.pathSeparatorChar ); + } + path.append( pathComponent ); + } + + return path.toString(); + } + + private static BuildException makeCircularException( String end, Stack stk ) + { + StringBuffer sb = new StringBuffer( "Circular dependency: " ); + sb.append( end ); + String c; + do + { + c = ( String )stk.pop(); + sb.append( " <- " ); + sb.append( c ); + }while ( !c.equals( end ) ); + return new BuildException( new String( sb ) ); + } + + /** + * set the base directory; XML attribute. checks for the directory existing + * and being a directory type + * + * @param baseDir project base directory. + * @throws BuildException if the directory was invalid + */ + public void setBaseDir( File baseDir ) + throws BuildException + { + baseDir = fileUtils.normalize( baseDir.getAbsolutePath() ); + if( !baseDir.exists() ) + throw new BuildException( "Basedir " + baseDir.getAbsolutePath() + " does not exist" ); + if( !baseDir.isDirectory() ) + throw new BuildException( "Basedir " + baseDir.getAbsolutePath() + " is not a directory" ); + this.baseDir = baseDir; + setPropertyInternal( "basedir", this.baseDir.getPath() ); + String msg = "Project base dir set to: " + this.baseDir; + log( msg, MSG_VERBOSE ); + } + + /** + * match basedir attribute in xml + * + * @param baseD project base directory. + * @throws BuildException if the directory was invalid + */ + public void setBasedir( String baseD ) + throws BuildException + { + setBaseDir( new File( baseD ) ); + } + + public void setCoreLoader( ClassLoader coreLoader ) + { + this.coreLoader = coreLoader; + } + + + /** + * set the default target of the project XML attribute name. + * + * @param defaultTarget The new Default value + */ + public void setDefault( String defaultTarget ) + { + this.defaultTarget = defaultTarget; + } + + /** + * set the default target of the project + * + * @param defaultTarget The new DefaultTarget value + * @see #setDefault(String) + * @deprecated, use setDefault + */ + public void setDefaultTarget( String defaultTarget ) + { + this.defaultTarget = defaultTarget; + } + + /** + * set the project description + * + * @param description text + */ + public void setDescription( String description ) + { + this.description = description; + } + + /** + * Calls File.setLastModified(long time) in a Java 1.1 compatible way. + * + * @param file The new FileLastModified value + * @param time The new FileLastModified value + * @exception BuildException Description of Exception + * @deprecated + */ + public void setFileLastModified( File file, long time ) + throws BuildException + { + if( getJavaVersion() == JAVA_1_1 ) + { + log( "Cannot change the modification time of " + file + + " in JDK 1.1", Project.MSG_WARN ); + return; + } + fileUtils.setFileLastModified( file, time ); + log( "Setting modification time for " + file, MSG_VERBOSE ); + } + + /** + * set the ant.java.version property, also tests for unsupported JVM + * versions, prints the verbose log messages + * + * @throws BuildException if this Java version is not supported + */ + public void setJavaVersionProperty() + throws BuildException + { + setPropertyInternal( "ant.java.version", javaVersion ); + + // sanity check + if( javaVersion == JAVA_1_0 ) + { + throw new BuildException( "Ant cannot work on Java 1.0" ); + } + + log( "Detected Java version: " + javaVersion + " in: " + System.getProperty( "java.home" ), MSG_VERBOSE ); + + log( "Detected OS: " + System.getProperty( "os.name" ), MSG_VERBOSE ); + } + + /** + * ant xml property. Set the project name as an attribute of this class, and + * of the property ant.project.name + * + * @param name The new Name value + */ + public void setName( String name ) + { + setUserProperty( "ant.project.name", name ); + this.name = name; + } + + /** + * set a property. An existing property of the same name will not be + * overwritten. + * + * @param name name of property + * @param value new value of the property + * @since 1.5 + */ + public void setNewProperty( String name, String value ) + { + if( null != properties.get( name ) ) + { + log( "Override ignored for property " + name, MSG_VERBOSE ); + return; + } + log( "Setting project property: " + name + " -> " + + value, MSG_DEBUG ); + properties.put( name, value ); + } + + /** + * set a property. Any existing property of the same name is overwritten, + * unless it is a user property. + * + * @param name name of property + * @param value new value of the property + */ + public void setProperty( String name, String value ) + { + // command line properties take precedence + if( null != userProperties.get( name ) ) + { + log( "Override ignored for user property " + name, MSG_VERBOSE ); + return; + } + + if( null != properties.get( name ) ) + { + log( "Overriding previous definition of property " + name, + MSG_VERBOSE ); + } + + log( "Setting project property: " + name + " -> " + + value, MSG_DEBUG ); + properties.put( name, value ); + } + + /** + * turn all the system properties into ant properties. user properties still + * override these values + */ + public void setSystemProperties() + { + Properties systemP = System.getProperties(); + Enumeration e = systemP.keys(); + while( e.hasMoreElements() ) + { + Object name = e.nextElement(); + String value = systemP.get( name ).toString(); + this.setPropertyInternal( name.toString(), value ); + } + } + + /** + * set a user property, which can not be overwritten by set/unset property + * calls + * + * @param name The new UserProperty value + * @param value The new UserProperty value + * @see #setProperty(String,String) + */ + public void setUserProperty( String name, String value ) + { + log( "Setting ro project property: " + name + " -> " + + value, MSG_DEBUG ); + userProperties.put( name, value ); + properties.put( name, value ); + } + + /** + * get the base directory of the project as a file object + * + * @return the base directory. If this is null, then the base dir is not + * valid + */ + public File getBaseDir() + { + if( baseDir == null ) + { + try + { + setBasedir( "." ); + } + catch( BuildException ex ) + { + ex.printStackTrace(); + } + } + return baseDir; + } + + public Vector getBuildListeners() + { + return listeners; + } + + public ClassLoader getCoreLoader() + { + return coreLoader; + } + + /** + * get the current task definition hashtable + * + * @return The DataTypeDefinitions value + */ + public Hashtable getDataTypeDefinitions() + { + return dataClassDefinitions; + } + + /** + * get the default target of the project + * + * @return default target or null + */ + public String getDefaultTarget() + { + return defaultTarget; + } + + /** + * get the project description + * + * @return description or null if no description has been set + */ + public String getDescription() + { + return description; + } + + /** + * @return The Filters value + * @deprecated + */ + public Hashtable getFilters() + { + // we need to build the hashtable dynamically + return globalFilterSet.getFilterHash(); + } + + + public FilterSet getGlobalFilterSet() + { + return globalFilterSet; + } + + /** + * get the project name + * + * @return name string + */ + public String getName() + { + return name; + } + + /** + * get a copy of the property hashtable + * + * @return the hashtable containing all properties, user included + */ + public Hashtable getProperties() + { + Hashtable propertiesCopy = new Hashtable(); + + Enumeration e = properties.keys(); + while( e.hasMoreElements() ) + { + Object name = e.nextElement(); + Object value = properties.get( name ); + propertiesCopy.put( name, value ); + } + + return propertiesCopy; + } + + /** + * query a property. + * + * @param name the name of the property + * @return the property value, or null for no match + */ + public String getProperty( String name ) + { + if( name == null ) + return null; + String property = ( String )properties.get( name ); + return property; + } + + /** + * @param key Description of Parameter + * @return The object with the "id" key. + */ + public Object getReference( String key ) + { + return references.get( key ); + } + + public Hashtable getReferences() + { + return references; + } + + /** + * get the target hashtable + * + * @return hashtable, the contents of which can be cast to Target + */ + public Hashtable getTargets() + { + return targets; + } + + /** + * get the current task definition hashtable + * + * @return The TaskDefinitions value + */ + public Hashtable getTaskDefinitions() + { + return taskClassDefinitions; + } + + /** + * get a copy of the user property hashtable + * + * @return the hashtable user properties only + */ + public Hashtable getUserProperties() + { + Hashtable propertiesCopy = new Hashtable(); + + Enumeration e = userProperties.keys(); + while( e.hasMoreElements() ) + { + Object name = e.nextElement(); + Object value = properties.get( name ); + propertiesCopy.put( name, value ); + } + + return propertiesCopy; + } + + /** + * query a user property. + * + * @param name the name of the property + * @return the property value, or null for no match + */ + public String getUserProperty( String name ) + { + if( name == null ) + return null; + String property = ( String )userProperties.get( name ); + return property; + } + + /** + * Topologically sort a set of Targets. + * + * @param root is the (String) name of the root Target. The sort is created + * in such a way that the sequence of Targets uptil the root target is + * the minimum possible such sequence. + * @param targets is a Hashtable representing a "name to Target" mapping + * @return a Vector of Strings with the names of the targets in sorted + * order. + * @exception BuildException if there is a cyclic dependency among the + * Targets, or if a Target does not exist. + */ + public final Vector topoSort( String root, Hashtable targets ) + throws BuildException + { + Vector ret = new Vector(); + Hashtable state = new Hashtable(); + Stack visiting = new Stack(); + + // We first run a DFS based sort using the root as the starting node. + // This creates the minimum sequence of Targets to the root node. + // We then do a sort on any remaining unVISITED targets. + // This is unnecessary for doing our build, but it catches + // circular dependencies or missing Targets on the entire + // dependency tree, not just on the Targets that depend on the + // build Target. + + tsort( root, targets, state, visiting, ret ); + log( "Build sequence for target `" + root + "' is " + ret, MSG_VERBOSE ); + for( Enumeration en = targets.keys(); en.hasMoreElements(); ) + { + String curTarget = ( String )( en.nextElement() ); + String st = ( String )state.get( curTarget ); + if( st == null ) + { + tsort( curTarget, targets, state, visiting, ret ); + } + else if( st == VISITING ) + { + throw new RuntimeException( "Unexpected node in visiting state: " + curTarget ); + } + } + log( "Complete build sequence is " + ret, MSG_VERBOSE ); + return ret; + } + + public void addBuildListener( BuildListener listener ) + { + listeners.addElement( listener ); + } + + /** + * add a new datatype + * + * @param typeName name of the datatype + * @param typeClass full datatype classname + */ + public void addDataTypeDefinition( String typeName, Class typeClass ) + { + if( null != dataClassDefinitions.get( typeName ) ) + { + log( "Trying to override old definition of datatype " + typeName, + MSG_WARN ); + } + + String msg = " +User datatype: " + typeName + " " + typeClass.getName(); + log( msg, MSG_DEBUG ); + dataClassDefinitions.put( typeName, typeClass ); + } + + /** + * @param token The feature to be added to the Filter attribute + * @param value The feature to be added to the Filter attribute + * @deprecated + */ + public void addFilter( String token, String value ) + { + if( token == null ) + { + return; + } + + globalFilterSet.addFilter( new FilterSet.Filter( token, value ) ); + } + + /** + * @param target is the Target to be added or replaced in the current + * Project. + */ + public void addOrReplaceTarget( Target target ) + { + addOrReplaceTarget( target.getName(), target ); + } + + /** + * @param target is the Target to be added/replaced in the current Project. + * @param targetName is the name to use for the Target + */ + public void addOrReplaceTarget( String targetName, Target target ) + { + String msg = " +Target: " + targetName; + log( msg, MSG_DEBUG ); + target.setProject( this ); + targets.put( targetName, target ); + } + + public void addReference( String name, Object value ) + { + if( null != references.get( name ) ) + { + log( "Overriding previous definition of reference to " + name, + MSG_WARN ); + } + log( "Adding reference: " + name + " -> " + value, MSG_DEBUG ); + references.put( name, value ); + } + + /** + * This call expects to add a new Target. + * + * @param target is the Target to be added to the current Project. + * @see Project#addOrReplaceTarget to replace existing Targets. + */ + public void addTarget( Target target ) + { + String name = target.getName(); + if( targets.get( name ) != null ) + { + throw new BuildException( "Duplicate target: `" + name + "'" ); + } + addOrReplaceTarget( name, target ); + } + + /** + * This call expects to add a new Target. + * + * @param target is the Target to be added to the current Project. + * @param targetName is the name to use for the Target + * @exception BuildException if the Target already exists in the project. + * @see Project#addOrReplaceTarget to replace existing Targets. + */ + public void addTarget( String targetName, Target target ) + throws BuildException + { + if( targets.get( targetName ) != null ) + { + throw new BuildException( "Duplicate target: `" + targetName + "'" ); + } + addOrReplaceTarget( targetName, target ); + } + + /** + * add a new task definition, complain if there is an overwrite attempt + * + * @param taskName name of the task + * @param taskClass full task classname + * @throws BuildException and logs as Project.MSG_ERR for conditions, that + * will cause the task execution to fail. + */ + public void addTaskDefinition( String taskName, Class taskClass ) + throws BuildException + { + Class old = ( Class )taskClassDefinitions.get( taskName ); + if( null != old ) + { + if( old.equals( taskClass ) ) + { + log( "Ignoring override for task " + taskName + + ", it is already defined by the same class.", + MSG_VERBOSE ); + return; + } + else + { + log( "Trying to override old definition of task " + taskName, + MSG_WARN ); + invalidateCreatedTasks( taskName ); + } + } + + String msg = " +User task: " + taskName + " " + taskClass.getName(); + log( msg, MSG_DEBUG ); + checkTaskClass( taskClass ); + taskClassDefinitions.put( taskName, taskClass ); + } + + /** + * Checks a class, whether it is suitable for serving as ant task. + * + * @param taskClass Description of Parameter + * @throws BuildException and logs as Project.MSG_ERR for conditions, that + * will cause the task execution to fail. + */ + public void checkTaskClass( final Class taskClass ) + throws BuildException + { + if( !Modifier.isPublic( taskClass.getModifiers() ) ) + { + final String message = taskClass + " is not public"; + log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + if( Modifier.isAbstract( taskClass.getModifiers() ) ) + { + final String message = taskClass + " is abstract"; + log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + try + { + taskClass.getConstructor( null ); + // don't have to check for public, since + // getConstructor finds public constructors only. + } + catch( NoSuchMethodException e ) + { + final String message = "No public default constructor in " + taskClass; + log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + if( !Task.class.isAssignableFrom( taskClass ) ) + TaskAdapter.checkTaskClass( taskClass, this ); + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile, boolean filtering ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile, boolean filtering, + boolean overwrite ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, overwrite ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( String sourceFile, String destFile, boolean filtering, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, + overwrite, preserveLastModified ); + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile, boolean filtering ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile, boolean filtering, + boolean overwrite ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, overwrite ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + * @deprecated + */ + public void copyFile( File sourceFile, File destFile, boolean filtering, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + fileUtils.copyFile( sourceFile, destFile, filtering ? globalFilters : null, + overwrite, preserveLastModified ); + } + + /** + * create a new DataType instance + * + * @param typeName name of the datatype + * @return null if the datatype name is unknown + * @throws BuildException when datatype creation goes bad + */ + public Object createDataType( String typeName ) + throws BuildException + { + Class c = ( Class )dataClassDefinitions.get( typeName ); + + if( c == null ) + return null; + + try + { + java.lang.reflect.Constructor ctor = null; + boolean noArg = false; + // DataType can have a "no arg" constructor or take a single + // Project argument. + try + { + ctor = c.getConstructor( new Class[0] ); + noArg = true; + } + catch( NoSuchMethodException nse ) + { + ctor = c.getConstructor( new Class[]{Project.class} ); + noArg = false; + } + + Object o = null; + if( noArg ) + { + o = ctor.newInstance( new Object[0] ); + } + else + { + o = ctor.newInstance( new Object[]{this} ); + } + if( o instanceof ProjectComponent ) + { + ( ( ProjectComponent )o ).setProject( this ); + } + String msg = " +DataType: " + typeName; + log( msg, MSG_DEBUG ); + return o; + } + catch( java.lang.reflect.InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + String msg = "Could not create datatype of type: " + + typeName + " due to " + t; + throw new BuildException( msg, t ); + } + catch( Throwable t ) + { + String msg = "Could not create datatype of type: " + + typeName + " due to " + t; + throw new BuildException( msg, t ); + } + } + + /** + * create a new task instance + * + * @param taskType name of the task + * @return null if the task name is unknown + * @throws BuildException when task creation goes bad + */ + public Task createTask( String taskType ) + throws BuildException + { + Class c = ( Class )taskClassDefinitions.get( taskType ); + + if( c == null ) + return null; + try + { + Object o = c.newInstance(); + Task task = null; + if( o instanceof Task ) + { + task = ( Task )o; + } + else + { + // "Generic" Bean - use the setter pattern + // and an Adapter + TaskAdapter taskA = new TaskAdapter(); + taskA.setProxy( o ); + task = taskA; + } + task.setProject( this ); + task.setTaskType( taskType ); + + // set default value, can be changed by the user + task.setTaskName( taskType ); + + String msg = " +Task: " + taskType; + log( msg, MSG_DEBUG ); + addCreatedTask( taskType, task ); + return task; + } + catch( Throwable t ) + { + String msg = "Could not create task of type: " + + taskType + " due to " + t; + throw new BuildException( msg, t ); + } + } + + public void demuxOutput( String line, boolean isError ) + { + Task task = ( Task )threadTasks.get( Thread.currentThread() ); + if( task == null ) + { + fireMessageLogged( this, line, isError ? MSG_ERR : MSG_INFO ); + } + else + { + if( isError ) + { + task.handleErrorOutput( line ); + } + else + { + task.handleOutput( line ); + } + } + } + + /** + * execute the targets and any targets it depends on + * + * @param targetName the target to execute + * @throws BuildException if the build failed + */ + public void executeTarget( String targetName ) + throws BuildException + { + + // sanity check ourselves, if we've been asked to build nothing + // then we should complain + + if( targetName == null ) + { + String msg = "No target specified"; + throw new BuildException( msg ); + } + + // Sort the dependency tree, and run everything from the + // beginning until we hit our targetName. + // Sorting checks if all the targets (and dependencies) + // exist, and if there is any cycle in the dependency + // graph. + Vector sortedTargets = topoSort( targetName, targets ); + + int curidx = 0; + Target curtarget; + + do + { + curtarget = ( Target )sortedTargets.elementAt( curidx++ ); + curtarget.performTasks(); + }while ( !curtarget.getName().equals( targetName ) ); + } + + /** + * execute the sequence of targets, and the targets they depend on + * + * @param targetNames Description of Parameter + * @throws BuildException if the build failed + */ + public void executeTargets( Vector targetNames ) + throws BuildException + { + Throwable error = null; + + for( int i = 0; i < targetNames.size(); i++ ) + { + executeTarget( ( String )targetNames.elementAt( i ) ); + } + } + + /** + * Initialise the project. This involves setting the default task + * definitions and loading the system properties. + * + * @exception BuildException Description of Exception + */ + public void init() + throws BuildException + { + setJavaVersionProperty(); + + String defs = "/org/apache/tools/ant/taskdefs/defaults.properties"; + + try + { + Properties props = new Properties(); + InputStream in = this.getClass().getResourceAsStream( defs ); + if( in == null ) + { + throw new BuildException( "Can't load default task list" ); + } + props.load( in ); + in.close(); + + Enumeration enum = props.propertyNames(); + while( enum.hasMoreElements() ) + { + String key = ( String )enum.nextElement(); + String value = props.getProperty( key ); + try + { + Class taskClass = Class.forName( value ); + addTaskDefinition( key, taskClass ); + } + catch( NoClassDefFoundError ncdfe ) + { + // ignore... + } + catch( ClassNotFoundException cnfe ) + { + // ignore... + } + } + } + catch( IOException ioe ) + { + throw new BuildException( "Can't load default task list" ); + } + + String dataDefs = "/org/apache/tools/ant/types/defaults.properties"; + + try + { + Properties props = new Properties(); + InputStream in = this.getClass().getResourceAsStream( dataDefs ); + if( in == null ) + { + throw new BuildException( "Can't load default datatype list" ); + } + props.load( in ); + in.close(); + + Enumeration enum = props.propertyNames(); + while( enum.hasMoreElements() ) + { + String key = ( String )enum.nextElement(); + String value = props.getProperty( key ); + try + { + Class dataClass = Class.forName( value ); + addDataTypeDefinition( key, dataClass ); + } + catch( NoClassDefFoundError ncdfe ) + { + // ignore... + } + catch( ClassNotFoundException cnfe ) + { + // ignore... + } + } + } + catch( IOException ioe ) + { + throw new BuildException( "Can't load default datatype list" ); + } + + setSystemProperties(); + } + + /** + * Output a message to the log with the default log level of MSG_INFO + * + * @param msg text to log + */ + + public void log( String msg ) + { + log( msg, MSG_INFO ); + } + + /** + * Output a message to the log with the given log level and an event scope + * of project + * + * @param msg text to log + * @param msgLevel level to log at + */ + public void log( String msg, int msgLevel ) + { + fireMessageLogged( this, msg, msgLevel ); + } + + /** + * Output a message to the log with the given log level and an event scope + * of a task + * + * @param task task to use in the log + * @param msg text to log + * @param msgLevel level to log at + */ + public void log( Task task, String msg, int msgLevel ) + { + fireMessageLogged( task, msg, msgLevel ); + } + + /** + * Output a message to the log with the given log level and an event scope + * of a target + * + * @param target target to use in the log + * @param msg text to log + * @param msgLevel level to log at + */ + public void log( Target target, String msg, int msgLevel ) + { + fireMessageLogged( target, msg, msgLevel ); + } + + public void removeBuildListener( BuildListener listener ) + { + listeners.removeElement( listener ); + } + + /** + * Replace ${} style constructions in the given value with the string value + * of the corresponding data types. + * + * @param value the string to be scanned for property references. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public String replaceProperties( String value ) + throws BuildException + { + return ProjectHelper.replaceProperties( this, value ); + } + + /** + * Return the canonical form of fileName as an absolute path.

          + * + * If fileName is a relative file name, resolve it relative to rootDir.

          + * + * @param fileName Description of Parameter + * @param rootDir Description of Parameter + * @return Description of the Returned Value + * @deprecated + */ + public File resolveFile( String fileName, File rootDir ) + { + return fileUtils.resolveFile( rootDir, fileName ); + } + + public File resolveFile( String fileName ) + { + return fileUtils.resolveFile( baseDir, fileName ); + } + + /** + * send build finished event to the listeners + * + * @param exception exception which indicates failure if not null + */ + protected void fireBuildFinished( Throwable exception ) + { + BuildEvent event = new BuildEvent( this ); + event.setException( exception ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.buildFinished( event ); + } + } + + /** + * send build started event to the listeners + */ + protected void fireBuildStarted() + { + BuildEvent event = new BuildEvent( this ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.buildStarted( event ); + } + } + + protected void fireMessageLogged( Project project, String message, int priority ) + { + BuildEvent event = new BuildEvent( project ); + fireMessageLoggedEvent( event, message, priority ); + } + + protected void fireMessageLogged( Target target, String message, int priority ) + { + BuildEvent event = new BuildEvent( target ); + fireMessageLoggedEvent( event, message, priority ); + } + + protected void fireMessageLogged( Task task, String message, int priority ) + { + BuildEvent event = new BuildEvent( task ); + fireMessageLoggedEvent( event, message, priority ); + } + + /** + * send build finished event to the listeners + * + * @param exception exception which indicates failure if not null + * @param target Description of Parameter + */ + protected void fireTargetFinished( Target target, Throwable exception ) + { + BuildEvent event = new BuildEvent( target ); + event.setException( exception ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.targetFinished( event ); + } + } + + + /** + * send target started event to the listeners + * + * @param target Description of Parameter + */ + protected void fireTargetStarted( Target target ) + { + BuildEvent event = new BuildEvent( target ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.targetStarted( event ); + } + } + + protected void fireTaskFinished( Task task, Throwable exception ) + { + threadTasks.remove( Thread.currentThread() ); + System.out.flush(); + System.err.flush(); + BuildEvent event = new BuildEvent( task ); + event.setException( exception ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.taskFinished( event ); + } + } + + protected void fireTaskStarted( Task task ) + { + // register this as the current task on the current thread. + threadTasks.put( Thread.currentThread(), task ); + BuildEvent event = new BuildEvent( task ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.taskStarted( event ); + } + } + + /** + * Allows Project and subclasses to set a property unless its already + * defined as a user property. There are a few cases internally to Project + * that need to do this currently. + * + * @param name The new PropertyInternal value + * @param value The new PropertyInternal value + */ + private void setPropertyInternal( String name, String value ) + { + if( null != userProperties.get( name ) ) + { + return; + } + properties.put( name, value ); + } + + // one step in a recursive DFS traversal of the Target dependency tree. + // - The Hashtable "state" contains the state (VISITED or VISITING or null) + // of all the target names. + // - The Stack "visiting" contains a stack of target names that are + // currently on the DFS stack. (NB: the target names in "visiting" are + // exactly the target names in "state" that are in the VISITING state.) + // 1. Set the current target to the VISITING state, and push it onto + // the "visiting" stack. + // 2. Throw a BuildException if any child of the current node is + // in the VISITING state (implies there is a cycle.) It uses the + // "visiting" Stack to construct the cycle. + // 3. If any children have not been VISITED, tsort() the child. + // 4. Add the current target to the Vector "ret" after the children + // have been visited. Move the current target to the VISITED state. + // "ret" now contains the sorted sequence of Targets upto the current + // Target. + + private final void tsort( String root, Hashtable targets, + Hashtable state, Stack visiting, + Vector ret ) + throws BuildException + { + state.put( root, VISITING ); + visiting.push( root ); + + Target target = ( Target )( targets.get( root ) ); + + // Make sure we exist + if( target == null ) + { + StringBuffer sb = new StringBuffer( "Target `" ); + sb.append( root ); + sb.append( "' does not exist in this project. " ); + visiting.pop(); + if( !visiting.empty() ) + { + String parent = ( String )visiting.peek(); + sb.append( "It is used from target `" ); + sb.append( parent ); + sb.append( "'." ); + } + + throw new BuildException( new String( sb ) ); + } + + for( Enumeration en = target.getDependencies(); en.hasMoreElements(); ) + { + String cur = ( String )en.nextElement(); + String m = ( String )state.get( cur ); + if( m == null ) + { + // Not been visited + tsort( cur, targets, state, visiting, ret ); + } + else if( m == VISITING ) + { + // Currently visiting this node, so have a cycle + throw makeCircularException( cur, visiting ); + } + } + + String p = ( String )visiting.pop(); + if( root != p ) + { + throw new RuntimeException( "Unexpected internal error: expected to pop " + root + " but got " + p ); + } + state.put( root, VISITED ); + ret.addElement( target ); + } + + /** + * Keep a record of all tasks that have been created so that they can be + * invalidated if a taskdef overrides the definition. + * + * @param type The feature to be added to the CreatedTask attribute + * @param task The feature to be added to the CreatedTask attribute + */ + private void addCreatedTask( String type, Task task ) + { + synchronized( createdTasks ) + { + Vector v = ( Vector )createdTasks.get( type ); + if( v == null ) + { + v = new Vector(); + createdTasks.put( type, v ); + } + v.addElement( task ); + } + } + + private void fireMessageLoggedEvent( BuildEvent event, String message, int priority ) + { + event.setMessage( message, priority ); + for( int i = 0; i < listeners.size(); i++ ) + { + BuildListener listener = ( BuildListener )listeners.elementAt( i ); + listener.messageLogged( event ); + } + } + + /** + * Mark tasks as invalid which no longer are of the correct type for a given + * taskname. + * + * @param type Description of Parameter + */ + private void invalidateCreatedTasks( String type ) + { + synchronized( createdTasks ) + { + Vector v = ( Vector )createdTasks.get( type ); + if( v != null ) + { + Enumeration enum = v.elements(); + while( enum.hasMoreElements() ) + { + Task t = ( Task )enum.nextElement(); + t.markInvalid(); + } + v.removeAllElements(); + createdTasks.remove( type ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectComponent.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectComponent.java new file mode 100644 index 000000000..c7ddf7f96 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectComponent.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +import org.apache.myrmidon.api.AbstractTask; + +/** + * Base class for components of a project, including tasks and data types. + * Provides common facilities. + * + * @author Conor MacNeill + */ + +public abstract class ProjectComponent + extends AbstractTask +{ + protected Project project = null; + + /** + * Sets the project object of this component. This method is used by project + * when a component is added to it so that the component has access to the + * functions of the project. It should not be used for any other purpose. + * + * @param project Project in whose scope this component belongs. + */ + public void setProject( Project project ) + { + this.project = project; + } + + /** + * Get the Project to which this component belongs + * + * @return the components's project. + */ + public Project getProject() + { + return project; + } + + /** + * Log a message with the default (INFO) priority. + * + * @param msg Description of Parameter + */ + public void log( String msg ) + { + log( msg, Project.MSG_INFO ); + } + + /** + * Log a mesage with the give priority. + * + * @param msgLevel the message priority at which this message is to be + * logged. + * @param msg Description of Parameter + */ + public void log( String msg, int msgLevel ) + { + if( project != null ) + { + project.log( msg, msgLevel ); + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectHelper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectHelper.java new file mode 100644 index 000000000..896a0d8f8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/ProjectHelper.java @@ -0,0 +1,1061 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import java.util.Vector; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.AttributeList; +import org.xml.sax.DocumentHandler; +import org.xml.sax.HandlerBase; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +/** + * Configures a Project (complete with Targets and Tasks) based on a XML build + * file. + * + * @author duncan@x180.com + */ + +public class ProjectHelper +{ + + private static SAXParserFactory parserFactory = null; + private File buildFile; + private File buildFileParent; + private Locator locator; + + private org.xml.sax.Parser parser; + private Project project; + + /** + * Constructs a new Ant parser for the specified XML file. + * + * @param project Description of Parameter + * @param buildFile Description of Parameter + */ + private ProjectHelper( Project project, File buildFile ) + { + this.project = project; + this.buildFile = new File( buildFile.getAbsolutePath() ); + buildFileParent = new File( this.buildFile.getParent() ); + } + + /** + * Adds the content of #PCDATA sections to an element. + * + * @param project The feature to be added to the Text attribute + * @param target The feature to be added to the Text attribute + * @param buf The feature to be added to the Text attribute + * @param start The feature to be added to the Text attribute + * @param end The feature to be added to the Text attribute + * @exception BuildException Description of Exception + */ + public static void addText( Project project, Object target, char[] buf, int start, int end ) + throws BuildException + { + addText( project, target, new String( buf, start, end ) ); + } + + /** + * Adds the content of #PCDATA sections to an element. + * + * @param project The feature to be added to the Text attribute + * @param target The feature to be added to the Text attribute + * @param text The feature to be added to the Text attribute + * @exception BuildException Description of Exception + */ + public static void addText( Project project, Object target, String text ) + throws BuildException + { + + if( text == null || text.trim().length() == 0 ) + { + return; + } + + if( target instanceof TaskAdapter ) + target = ( ( TaskAdapter )target ).getProxy(); + + IntrospectionHelper.getHelper( target.getClass() ).addText( project, target, text ); + } + + public static void configure( Object target, AttributeList attrs, + Project project ) + throws BuildException + { + if( target instanceof TaskAdapter ) + target = ( ( TaskAdapter )target ).getProxy(); + + IntrospectionHelper ih = + IntrospectionHelper.getHelper( target.getClass() ); + + project.addBuildListener( ih ); + + for( int i = 0; i < attrs.getLength(); i++ ) + { + // reflect these into the target + String value = replaceProperties( project, attrs.getValue( i ), + project.getProperties() ); + try + { + ih.setAttribute( project, target, + attrs.getName( i ).toLowerCase( Locale.US ), value ); + + } + catch( BuildException be ) + { + // id attribute must be set externally + if( !attrs.getName( i ).equals( "id" ) ) + { + throw be; + } + } + } + } + + /** + * Configures the Project with the contents of the specified XML file. + * + * @param project Description of Parameter + * @param buildFile Description of Parameter + * @exception BuildException Description of Exception + */ + public static void configureProject( Project project, File buildFile ) + throws BuildException + { + new ProjectHelper( project, buildFile ).parse(); + } + + /** + * This method will parse a string containing ${value} style property values + * into two lists. The first list is a collection of text fragments, while + * the other is a set of string property names null entries in the first + * list indicate a property reference from the second list. + * + * @param value Description of Parameter + * @param fragments Description of Parameter + * @param propertyRefs Description of Parameter + * @exception BuildException Description of Exception + */ + public static void parsePropertyString( String value, Vector fragments, Vector propertyRefs ) + throws BuildException + { + int prev = 0; + int pos; + while( ( pos = value.indexOf( "$", prev ) ) >= 0 ) + { + if( pos > 0 ) + { + fragments.addElement( value.substring( prev, pos ) ); + } + + if( pos == ( value.length() - 1 ) ) + { + fragments.addElement( "$" ); + prev = pos + 1; + } + else if( value.charAt( pos + 1 ) != '{' ) + { + fragments.addElement( value.substring( pos + 1, pos + 2 ) ); + prev = pos + 2; + } + else + { + int endName = value.indexOf( '}', pos ); + if( endName < 0 ) + { + throw new BuildException( "Syntax error in property: " + + value ); + } + String propertyName = value.substring( pos + 2, endName ); + fragments.addElement( null ); + propertyRefs.addElement( propertyName ); + prev = endName + 1; + } + } + + if( prev < value.length() ) + { + fragments.addElement( value.substring( prev ) ); + } + } + + /** + * Replace ${} style constructions in the given value with the string value + * of the corresponding data types. + * + * @param value the string to be scanned for property references. + * @param project Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @since 1.5 + */ + public static String replaceProperties( Project project, String value ) + throws BuildException + { + return replaceProperties( project, value, project.getProperties() ); + } + + /** + * Replace ${} style constructions in the given value with the string value + * of the corresponding data types. + * + * @param value the string to be scanned for property references. + * @param project Description of Parameter + * @param keys Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public static String replaceProperties( Project project, String value, Hashtable keys ) + throws BuildException + { + if( value == null ) + { + return null; + } + + Vector fragments = new Vector(); + Vector propertyRefs = new Vector(); + parsePropertyString( value, fragments, propertyRefs ); + + StringBuffer sb = new StringBuffer(); + Enumeration i = fragments.elements(); + Enumeration j = propertyRefs.elements(); + while( i.hasMoreElements() ) + { + String fragment = ( String )i.nextElement(); + if( fragment == null ) + { + String propertyName = ( String )j.nextElement(); + if( !keys.containsKey( propertyName ) ) + { + project.log( "Property ${" + propertyName + "} has not been set", Project.MSG_VERBOSE ); + } + fragment = ( keys.containsKey( propertyName ) ) ? ( String )keys.get( propertyName ) + : "${" + propertyName + "}"; + } + sb.append( fragment ); + } + + return sb.toString(); + } + + /** + * Stores a configured child element into its parent object + * + * @param project Description of Parameter + * @param parent Description of Parameter + * @param child Description of Parameter + * @param tag Description of Parameter + */ + public static void storeChild( Project project, Object parent, Object child, String tag ) + { + IntrospectionHelper ih = IntrospectionHelper.getHelper( parent.getClass() ); + ih.storeElement( project, parent, child, tag ); + } + + private static SAXParserFactory getParserFactory() + { + if( parserFactory == null ) + { + parserFactory = SAXParserFactory.newInstance(); + } + + return parserFactory; + } + + /** + * Scan AttributeList for the id attribute and maybe add a reference to + * project.

          + * + * Moved out of {@link #configure configure} to make it happen at parser + * time.

          + * + * @param target Description of Parameter + * @param attr Description of Parameter + */ + private void configureId( Object target, AttributeList attr ) + { + String id = attr.getValue( "id" ); + if( id != null ) + { + project.addReference( id, target ); + } + } + + /** + * Parses the project file. + * + * @exception BuildException Description of Exception + */ + private void parse() + throws BuildException + { + FileInputStream inputStream = null; + InputSource inputSource = null; + + try + { + SAXParser saxParser = getParserFactory().newSAXParser(); + parser = saxParser.getParser(); + + String uri = "file:" + buildFile.getAbsolutePath().replace( '\\', '/' ); + for( int index = uri.indexOf( '#' ); index != -1; index = uri.indexOf( '#' ) ) + { + uri = uri.substring( 0, index ) + "%23" + uri.substring( index + 1 ); + } + + inputStream = new FileInputStream( buildFile ); + inputSource = new InputSource( inputStream ); + inputSource.setSystemId( uri ); + project.log( "parsing buildfile " + buildFile + " with URI = " + uri, Project.MSG_VERBOSE ); + saxParser.parse( inputSource, new RootHandler() ); + } + catch( ParserConfigurationException exc ) + { + throw new BuildException( "Parser has not been configured correctly", exc ); + } + catch( SAXParseException exc ) + { + Location location = + new Location( buildFile.toString(), exc.getLineNumber(), exc.getColumnNumber() ); + + Throwable t = exc.getException(); + if( t instanceof BuildException ) + { + BuildException be = ( BuildException )t; + if( be.getLocation() == Location.UNKNOWN_LOCATION ) + { + be.setLocation( location ); + } + throw be; + } + + throw new BuildException( exc.getMessage(), t, location ); + } + catch( SAXException exc ) + { + Throwable t = exc.getException(); + if( t instanceof BuildException ) + { + throw ( BuildException )t; + } + throw new BuildException( exc.getMessage(), t ); + } + catch( FileNotFoundException exc ) + { + throw new BuildException( exc ); + } + catch( IOException exc ) + { + throw new BuildException( "Error reading project file", exc ); + } + finally + { + if( inputStream != null ) + { + try + { + inputStream.close(); + } + catch( IOException ioe ) + { + // ignore this + } + } + } + } + + /** + * The common superclass for all sax event handlers in Ant. Basically throws + * an exception in each method, so subclasses should override what they can + * handle. Each type of xml element (task, target, etc) in ant will have its + * own subclass of AbstractHandler. In the constructor, this class takes + * over the handling of sax events from the parent handler, and returns + * control back to the parent in the endElement method. + * + * @author RT + */ + private class AbstractHandler extends HandlerBase + { + protected DocumentHandler parentHandler; + + public AbstractHandler( DocumentHandler parentHandler ) + { + this.parentHandler = parentHandler; + + // Start handling SAX events + parser.setDocumentHandler( this ); + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + String s = new String( buf, start, end ).trim(); + + if( s.length() > 0 ) + { + throw new SAXParseException( "Unexpected text \"" + s + "\"", locator ); + } + } + + public void endElement( String name ) + throws SAXException + { + + finished(); + // Let parent resume handling SAX events + parser.setDocumentHandler( parentHandler ); + } + + public void startElement( String tag, AttributeList attrs ) + throws SAXParseException + { + throw new SAXParseException( "Unexpected element \"" + tag + "\"", locator ); + } + + /** + * Called when this element and all elements nested into it have been + * handled. + */ + protected void finished() { } + } + + /** + * Handler for all data types at global level. + * + * @author RT + */ + private class DataTypeHandler extends AbstractHandler + { + private RuntimeConfigurable wrapper = null; + private Object element; + private Target target; + + public DataTypeHandler( DocumentHandler parentHandler ) + { + this( parentHandler, null ); + } + + public DataTypeHandler( DocumentHandler parentHandler, Target target ) + { + super( parentHandler ); + this.target = target; + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + try + { + addText( project, element, buf, start, end ); + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + + public void init( String propType, AttributeList attrs ) + throws SAXParseException + { + try + { + element = project.createDataType( propType ); + if( element == null ) + { + throw new BuildException( "Unknown data type " + propType ); + } + + if( target != null ) + { + wrapper = new RuntimeConfigurable( element, propType ); + wrapper.setAttributes( attrs ); + target.addDataType( wrapper ); + } + else + { + configure( element, attrs, project ); + configureId( element, attrs ); + } + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + new NestedElementHandler( this, element, wrapper, target ).init( name, attrs ); + } + } + + /** + * Handler for all nested properties. + * + * @author RT + */ + private class NestedElementHandler extends AbstractHandler + { + private RuntimeConfigurable childWrapper = null; + private Object child; + private Object parent; + private RuntimeConfigurable parentWrapper; + private Target target; + + public NestedElementHandler( DocumentHandler parentHandler, + Object parent, + RuntimeConfigurable parentWrapper, + Target target ) + { + super( parentHandler ); + + if( parent instanceof TaskAdapter ) + { + this.parent = ( ( TaskAdapter )parent ).getProxy(); + } + else + { + this.parent = parent; + } + this.parentWrapper = parentWrapper; + this.target = target; + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + if( parentWrapper == null ) + { + try + { + addText( project, child, buf, start, end ); + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + else + { + childWrapper.addText( buf, start, end ); + } + } + + public void init( String propType, AttributeList attrs ) + throws SAXParseException + { + Class parentClass = parent.getClass(); + IntrospectionHelper ih = + IntrospectionHelper.getHelper( parentClass ); + + try + { + String elementName = propType.toLowerCase( Locale.US ); + if( parent instanceof UnknownElement ) + { + UnknownElement uc = new UnknownElement( elementName ); + uc.setProject( project ); + ( ( UnknownElement )parent ).addChild( uc ); + child = uc; + } + else + { + child = ih.createElement( project, parent, elementName ); + } + + configureId( child, attrs ); + + if( parentWrapper != null ) + { + childWrapper = new RuntimeConfigurable( child, propType ); + childWrapper.setAttributes( attrs ); + parentWrapper.addChild( childWrapper ); + } + else + { + configure( child, attrs, project ); + ih.storeElement( project, parent, child, elementName ); + } + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( child instanceof TaskContainer ) + { + // taskcontainer nested element can contain other tasks - no other + // nested elements possible + new TaskHandler( this, ( TaskContainer )child, childWrapper, target ).init( name, attrs ); + } + else + { + new NestedElementHandler( this, child, childWrapper, target ).init( name, attrs ); + } + } + } + + /** + * Handler for the top level "project" element. + * + * @author RT + */ + private class ProjectHandler extends AbstractHandler + { + public ProjectHandler( DocumentHandler parentHandler ) + { + super( parentHandler ); + } + + public void init( String tag, AttributeList attrs ) + throws SAXParseException + { + String def = null; + String name = null; + String id = null; + String baseDir = null; + + for( int i = 0; i < attrs.getLength(); i++ ) + { + String key = attrs.getName( i ); + String value = attrs.getValue( i ); + + if( key.equals( "default" ) ) + { + def = value; + } + else if( key.equals( "name" ) ) + { + name = value; + } + else if( key.equals( "id" ) ) + { + id = value; + } + else if( key.equals( "basedir" ) ) + { + baseDir = value; + } + else + { + throw new SAXParseException( "Unexpected attribute \"" + attrs.getName( i ) + "\"", locator ); + } + } + + if( def == null ) + { + throw new SAXParseException( "The default attribute of project is required", + locator ); + } + + project.setDefaultTarget( def ); + + if( name != null ) + { + project.setName( name ); + project.addReference( name, project ); + } + + if( id != null ) + project.addReference( id, project ); + + if( project.getProperty( "basedir" ) != null ) + { + project.setBasedir( project.getProperty( "basedir" ) ); + } + else + { + if( baseDir == null ) + { + project.setBasedir( buildFileParent.getAbsolutePath() ); + } + else + { + // check whether the user has specified an absolute path + if( ( new File( baseDir ) ).isAbsolute() ) + { + project.setBasedir( baseDir ); + } + else + { + project.setBaseDir( project.resolveFile( baseDir, buildFileParent ) ); + } + } + } + + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( name.equals( "taskdef" ) ) + { + handleTaskdef( name, attrs ); + } + else if( name.equals( "typedef" ) ) + { + handleTypedef( name, attrs ); + } + else if( name.equals( "property" ) ) + { + handleProperty( name, attrs ); + } + else if( name.equals( "target" ) ) + { + handleTarget( name, attrs ); + } + else if( project.getDataTypeDefinitions().get( name ) != null ) + { + handleDataType( name, attrs ); + } + else + { + throw new SAXParseException( "Unexpected element \"" + name + "\"", locator ); + } + } + + private void handleDataType( String name, AttributeList attrs ) + throws SAXParseException + { + new DataTypeHandler( this ).init( name, attrs ); + } + + private void handleProperty( String name, AttributeList attrs ) + throws SAXParseException + { + ( new TaskHandler( this, null, null, null ) ).init( name, attrs ); + } + + private void handleTarget( String tag, AttributeList attrs ) + throws SAXParseException + { + new TargetHandler( this ).init( tag, attrs ); + } + + private void handleTaskdef( String name, AttributeList attrs ) + throws SAXParseException + { + ( new TaskHandler( this, null, null, null ) ).init( name, attrs ); + } + + private void handleTypedef( String name, AttributeList attrs ) + throws SAXParseException + { + ( new TaskHandler( this, null, null, null ) ).init( name, attrs ); + } + + } + + /** + * Handler for the root element. It's only child must be the "project" + * element. + * + * @author RT + */ + private class RootHandler extends HandlerBase + { + + public void setDocumentLocator( Locator locator ) + { + ProjectHelper.this.locator = locator; + } + + /** + * resolve file: URIs as relative to the build file. + * + * @param publicId Description of Parameter + * @param systemId Description of Parameter + * @return Description of the Returned Value + */ + public InputSource resolveEntity( String publicId, + String systemId ) + { + + project.log( "resolving systemId: " + systemId, Project.MSG_VERBOSE ); + + if( systemId.startsWith( "file:" ) ) + { + String path = systemId.substring( 5 ); + int index = path.indexOf( "file:" ); + + // we only have to handle these for backward compatibility + // since they are in the FAQ. + while( index != -1 ) + { + path = path.substring( 0, index ) + path.substring( index + 5 ); + index = path.indexOf( "file:" ); + } + + String entitySystemId = path; + index = path.indexOf( "%23" ); + // convert these to # + while( index != -1 ) + { + path = path.substring( 0, index ) + "#" + path.substring( index + 3 ); + index = path.indexOf( "%23" ); + } + + File file = new File( path ); + if( !file.isAbsolute() ) + { + file = new File( buildFileParent, path ); + } + + try + { + InputSource inputSource = new InputSource( new FileInputStream( file ) ); + inputSource.setSystemId( "file:" + entitySystemId ); + return inputSource; + } + catch( FileNotFoundException fne ) + { + project.log( file.getAbsolutePath() + " could not be found", + Project.MSG_WARN ); + } + } + // use default if not file or file not found + return null; + } + + public void startElement( String tag, AttributeList attrs ) + throws SAXParseException + { + if( tag.equals( "project" ) ) + { + new ProjectHandler( this ).init( tag, attrs ); + } + else + { + throw new SAXParseException( "Config file is not of expected XML type", locator ); + } + } + } + + /** + * Handler for "target" elements. + * + * @author RT + */ + private class TargetHandler extends AbstractHandler + { + private Target target; + + public TargetHandler( DocumentHandler parentHandler ) + { + super( parentHandler ); + } + + public void init( String tag, AttributeList attrs ) + throws SAXParseException + { + String name = null; + String depends = ""; + String ifCond = null; + String unlessCond = null; + String id = null; + String description = null; + + for( int i = 0; i < attrs.getLength(); i++ ) + { + String key = attrs.getName( i ); + String value = attrs.getValue( i ); + + if( key.equals( "name" ) ) + { + name = value; + } + else if( key.equals( "depends" ) ) + { + depends = value; + } + else if( key.equals( "if" ) ) + { + ifCond = value; + } + else if( key.equals( "unless" ) ) + { + unlessCond = value; + } + else if( key.equals( "id" ) ) + { + id = value; + } + else if( key.equals( "description" ) ) + { + description = value; + } + else + { + throw new SAXParseException( "Unexpected attribute \"" + key + "\"", locator ); + } + } + + if( name == null ) + { + throw new SAXParseException( "target element appears without a name attribute", locator ); + } + + target = new Target(); + target.setName( name ); + target.setIf( ifCond ); + target.setUnless( unlessCond ); + target.setDescription( description ); + project.addTarget( name, target ); + + if( id != null && !id.equals( "" ) ) + project.addReference( id, target ); + + // take care of dependencies + + if( depends.length() > 0 ) + { + target.setDepends( depends ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( project.getDataTypeDefinitions().get( name ) != null ) + { + new DataTypeHandler( this, target ).init( name, attrs ); + } + else + { + new TaskHandler( this, target, null, target ).init( name, attrs ); + } + } + } + + /** + * Handler for all task elements. + * + * @author RT + */ + private class TaskHandler extends AbstractHandler + { + private RuntimeConfigurable wrapper = null; + private TaskContainer container; + private RuntimeConfigurable parentWrapper; + private Target target; + private Task task; + + public TaskHandler( DocumentHandler parentHandler, TaskContainer container, RuntimeConfigurable parentWrapper, Target target ) + { + super( parentHandler ); + this.container = container; + this.parentWrapper = parentWrapper; + this.target = target; + } + + public void characters( char[] buf, int start, int end ) + throws SAXParseException + { + if( wrapper == null ) + { + try + { + addText( project, task, buf, start, end ); + } + catch( BuildException exc ) + { + throw new SAXParseException( exc.getMessage(), locator, exc ); + } + } + else + { + wrapper.addText( buf, start, end ); + } + } + + public void init( String tag, AttributeList attrs ) + throws SAXParseException + { + try + { + task = project.createTask( tag ); + } + catch( BuildException e ) + { + // swallow here, will be thrown again in + // UnknownElement.maybeConfigure if the problem persists. + } + + if( task == null ) + { + task = new UnknownElement( tag ); + task.setProject( project ); + task.setTaskType( tag ); + task.setTaskName( tag ); + } + + task.setLocation( new Location( buildFile.toString(), locator.getLineNumber(), locator.getColumnNumber() ) ); + configureId( task, attrs ); + + // Top level tasks don't have associated targets + if( target != null ) + { + task.setOwningTarget( target ); + container.addTask( task ); + task.init(); + wrapper = task.getRuntimeConfigurableWrapper(); + wrapper.setAttributes( attrs ); + if( parentWrapper != null ) + { + parentWrapper.addChild( wrapper ); + } + } + else + { + task.init(); + configure( task, attrs, project ); + } + } + + public void startElement( String name, AttributeList attrs ) + throws SAXParseException + { + if( task instanceof TaskContainer ) + { + // task can contain other tasks - no other nested elements possible + new TaskHandler( this, ( TaskContainer )task, wrapper, target ).init( name, attrs ); + } + else + { + new NestedElementHandler( this, task, wrapper, target ).init( name, attrs ); + } + } + + protected void finished() + { + if( task != null && target == null ) + { + task.execute(); + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/RuntimeConfigurable.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/RuntimeConfigurable.java new file mode 100644 index 000000000..4acd55108 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/RuntimeConfigurable.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.Enumeration; +import java.util.Locale; +import java.util.Vector; +import org.xml.sax.AttributeList; +import org.xml.sax.helpers.AttributeListImpl; + +/** + * Wrapper class that holds the attributes of a Task (or elements nested below + * that level) and takes care of configuring that element at runtime. + * + * @author Stefan Bodewig + */ +public class RuntimeConfigurable +{ + + private String elementTag = null; + private Vector children = new Vector(); + private Object wrappedObject = null; + private StringBuffer characters = new StringBuffer(); + private AttributeList attributes; + + /** + * @param proxy The element to wrap. + * @param elementTag Description of Parameter + */ + public RuntimeConfigurable( Object proxy, String elementTag ) + { + wrappedObject = proxy; + this.elementTag = elementTag; + } + + /** + * Set's the attributes for the wrapped element. + * + * @param attributes The new Attributes value + */ + public void setAttributes( AttributeList attributes ) + { + this.attributes = new AttributeListImpl( attributes ); + } + + /** + * Returns the AttributeList of the wrapped element. + * + * @return The Attributes value + */ + public AttributeList getAttributes() + { + return attributes; + } + + public String getElementTag() + { + return elementTag; + } + + /** + * Adds child elements to the wrapped element. + * + * @param child The feature to be added to the Child attribute + */ + public void addChild( RuntimeConfigurable child ) + { + children.addElement( child ); + } + + /** + * Add characters from #PCDATA areas to the wrapped element. + * + * @param data The feature to be added to the Text attribute + */ + public void addText( String data ) + { + characters.append( data ); + } + + /** + * Add characters from #PCDATA areas to the wrapped element. + * + * @param buf The feature to be added to the Text attribute + * @param start The feature to be added to the Text attribute + * @param end The feature to be added to the Text attribute + */ + public void addText( char[] buf, int start, int end ) + { + addText( new String( buf, start, end ) ); + } + + + /** + * Configure the wrapped element and all children. + * + * @param p Description of Parameter + * @exception BuildException Description of Exception + */ + public void maybeConfigure( Project p ) + throws BuildException + { + String id = null; + + if( attributes != null ) + { + ProjectHelper.configure( wrappedObject, attributes, p ); + id = attributes.getValue( "id" ); + attributes = null; + } + if( characters.length() != 0 ) + { + ProjectHelper.addText( p, wrappedObject, characters.toString() ); + characters.setLength( 0 ); + } + Enumeration enum = children.elements(); + while( enum.hasMoreElements() ) + { + RuntimeConfigurable child = ( RuntimeConfigurable )enum.nextElement(); + if( child.wrappedObject instanceof Task ) + { + Task childTask = ( Task )child.wrappedObject; + childTask.setRuntimeConfigurableWrapper( child ); + childTask.maybeConfigure(); + } + else + { + child.maybeConfigure( p ); + } + ProjectHelper.storeChild( p, wrappedObject, child.wrappedObject, child.getElementTag().toLowerCase( Locale.US ) ); + } + + if( id != null ) + { + p.addReference( id, wrappedObject ); + } + } + + void setProxy( Object proxy ) + { + wrappedObject = proxy; + } + + /** + * Returns the child with index index. + * + * @param index Description of Parameter + * @return The Child value + */ + RuntimeConfigurable getChild( int index ) + { + return ( RuntimeConfigurable )children.elementAt( index ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Target.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Target.java new file mode 100644 index 000000000..78cb4f980 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Target.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; + +/** + * This class implements a target object with required parameters. + * + * @author James Davidson duncan@x180.com + */ + +public class Target implements TaskContainer +{ + private String ifCondition = ""; + private String unlessCondition = ""; + private Vector dependencies = new Vector( 2 ); + private Vector children = new Vector( 5 ); + private String description = null; + + private String name; + private Project project; + + public void setDepends( String depS ) + { + if( depS.length() > 0 ) + { + StringTokenizer tok = + new StringTokenizer( depS, ",", true ); + while( tok.hasMoreTokens() ) + { + String token = tok.nextToken().trim(); + + //Make sure the dependency is not empty string + if( token.equals( "" ) || token.equals( "," ) ) + { + throw new BuildException( "Syntax Error: Depend attribute " + + "for target \"" + getName() + + "\" has an empty string for dependency." ); + } + + addDependency( token ); + + //Make sure that depends attribute does not + //end in a , + if( tok.hasMoreTokens() ) + { + token = tok.nextToken(); + if( !tok.hasMoreTokens() || !token.equals( "," ) ) + { + throw new BuildException( "Syntax Error: Depend attribute " + + "for target \"" + getName() + + "\" ends with a , character" ); + } + } + } + } + } + + public void setDescription( String description ) + { + this.description = description; + } + + public void setIf( String property ) + { + this.ifCondition = ( property == null ) ? "" : property; + } + + public void setName( String name ) + { + this.name = name; + } + + public void setProject( Project project ) + { + this.project = project; + } + + public void setUnless( String property ) + { + this.unlessCondition = ( property == null ) ? "" : property; + } + + public Enumeration getDependencies() + { + return dependencies.elements(); + } + + public String getDescription() + { + return description; + } + + public String getName() + { + return name; + } + + public Project getProject() + { + return project; + } + + /** + * Get the current set of tasks to be executed by this target. + * + * @return The current set of tasks. + */ + public Task[] getTasks() + { + Vector tasks = new Vector( children.size() ); + Enumeration enum = children.elements(); + while( enum.hasMoreElements() ) + { + Object o = enum.nextElement(); + if( o instanceof Task ) + { + tasks.addElement( o ); + } + } + + Task[] retval = new Task[tasks.size()]; + tasks.copyInto( retval ); + return retval; + } + + public final void performTasks() + { + try + { + project.fireTargetStarted( this ); + execute(); + project.fireTargetFinished( this, null ); + } + catch( RuntimeException exc ) + { + project.fireTargetFinished( this, exc ); + throw exc; + } + } + + public void addDataType( RuntimeConfigurable r ) + { + children.addElement( r ); + } + + public void addDependency( String dependency ) + { + dependencies.addElement( dependency ); + } + + public void addTask( Task task ) + { + children.addElement( task ); + } + + public void execute() + throws BuildException + { + if( testIfCondition() && testUnlessCondition() ) + { + Enumeration enum = children.elements(); + while( enum.hasMoreElements() ) + { + Object o = enum.nextElement(); + if( o instanceof Task ) + { + Task task = ( Task )o; + task.perform(); + } + else + { + RuntimeConfigurable r = ( RuntimeConfigurable )o; + r.maybeConfigure( project ); + } + } + } + else if( !testIfCondition() ) + { + project.log( this, "Skipped because property '" + this.ifCondition + "' not set.", + Project.MSG_VERBOSE ); + } + else + { + project.log( this, "Skipped because property '" + this.unlessCondition + "' set.", + Project.MSG_VERBOSE ); + } + } + + public String toString() + { + return name; + } + + void replaceChild( Task el, Object o ) + { + int index = -1; + while( ( index = children.indexOf( el ) ) >= 0 ) + { + children.setElementAt( o, index ); + } + } + + private boolean testIfCondition() + { + if( "".equals( ifCondition ) ) + { + return true; + } + + String test = project.replaceProperties( ifCondition ); + return project.getProperty( test ) != null; + } + + private boolean testUnlessCondition() + { + if( "".equals( unlessCondition ) ) + { + return true; + } + String test = project.replaceProperties( unlessCondition ); + return project.getProperty( test ) == null; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/Task.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/Task.java new file mode 100644 index 000000000..7a08963ea --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/Task.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +import org.apache.myrmidon.api.TaskException; + +public abstract class Task + extends ProjectComponent + implements org.apache.myrmidon.api.Task +{ + protected Target target; + protected String description; + protected Location location = Location.UNKNOWN_LOCATION; + protected String taskName; + protected String taskType; + private boolean invalid; + protected RuntimeConfigurable wrapper; + + private UnknownElement replacement; + + /** + * Sets a description of the current action. It will be usefull in + * commenting what we are doing. + * + * @param desc The new Description value + */ + public void setDescription( String desc ) + { + description = desc; + } + + /** + * Sets the file location where this task was defined. + * + * @param location The new Location value + */ + public void setLocation( Location location ) + { + this.location = location; + } + + /** + * Sets the target object of this task. + * + * @param target Target in whose scope this task belongs. + */ + public void setOwningTarget( Target target ) + { + this.target = target; + } + + /** + * Set the name to use in logging messages. + * + * @param name the name to use in logging messages. + */ + public void setTaskName( String name ) + { + this.taskName = name; + } + + public String getDescription() + { + return description; + } + + /** + * Returns the file location where this task was defined. + * + * @return The Location value + */ + public Location getLocation() + { + return location; + } + + /** + * Get the Target to which this task belongs + * + * @return the task's target. + */ + public Target getOwningTarget() + { + return target; + } + + /** + * Returns the wrapper class for runtime configuration. + * + * @return The RuntimeConfigurableWrapper value + */ + public RuntimeConfigurable getRuntimeConfigurableWrapper() + { + if( wrapper == null ) + { + wrapper = new RuntimeConfigurable( this, getTaskName() ); + } + return wrapper; + } + + /** + * Get the name to use in logging messages. + * + * @return the name to use in logging messages. + */ + public String getTaskName() + { + return taskName; + } + + /** + * Perform this task + */ + public final void perform() + throws TaskException + { + if( !invalid ) + { + try + { + project.fireTaskStarted( this ); + maybeConfigure(); + execute(); + project.fireTaskFinished( this, null ); + } + catch( TaskException te ) + { + if( te instanceof BuildException ) + { + BuildException be = (BuildException)te; + if( be.getLocation() == Location.UNKNOWN_LOCATION ) + { + be.setLocation( getLocation() ); + } + } + project.fireTaskFinished( this, te ); + throw te; + } + catch( RuntimeException re ) + { + project.fireTaskFinished( this, re ); + throw re; + } + } + else + { + UnknownElement ue = getReplacement(); + Task task = ue.getTask(); + task.perform(); + } + } + + /** + * Called by the project to let the task do it's work. This method may be + * called more than once, if the task is invoked more than once. For + * example, if target1 and target2 both depend on target3, then running "ant + * target1 target2" will run all tasks in target3 twice. + * + * @throws BuildException if someting goes wrong with the build + */ + public void execute() + throws TaskException + { + } + + /** + * Called by the project to let the task initialize properly. + * + * @throws BuildException if someting goes wrong with the build + */ + public void init() + throws TaskException + { + } + + /** + * Log a message with the default (INFO) priority. + * + * @param msg Description of Parameter + */ + public void log( String msg ) + { + log( msg, Project.MSG_INFO ); + } + + /** + * Log a mesage with the give priority. + * + * @param msgLevel the message priority at which this message is to be + * logged. + * @param msg Description of Parameter + */ + public void log( String msg, int msgLevel ) + { + project.log( this, msg, msgLevel ); + } + + /** + * Configure this task - if it hasn't been done already. + * + * @exception BuildException Description of Exception + */ + public void maybeConfigure() + throws TaskException + { + if( !invalid ) + { + if( wrapper != null ) + { + wrapper.maybeConfigure( project ); + } + } + else + { + getReplacement(); + } + } + + protected void setRuntimeConfigurableWrapper( RuntimeConfigurable wrapper ) + { + this.wrapper = wrapper; + } + + protected void handleErrorOutput( String line ) + { + log( line, Project.MSG_ERR ); + } + + protected void handleOutput( String line ) + { + log( line, Project.MSG_INFO ); + } + + /** + * Set the name with which the task has been invoked. + * + * @param type the name the task has been invoked as. + */ + void setTaskType( String type ) + { + this.taskType = type; + } + + /** + * Mark this task as invalid. + */ + final void markInvalid() + { + invalid = true; + } + + /** + * Create an UnknownElement that can be used to replace this task. + * + * @return The Replacement value + */ + private UnknownElement getReplacement() + throws TaskException + { + if( replacement == null ) + { + replacement = new UnknownElement( taskType ); + replacement.setProject( project ); + replacement.setTaskType( taskType ); + replacement.setTaskName( taskName ); + replacement.setLocation( location ); + replacement.setOwningTarget( target ); + replacement.setRuntimeConfigurableWrapper( wrapper ); + wrapper.setProxy( replacement ); + target.replaceChild( this, replacement ); + replacement.maybeConfigure(); + } + return replacement; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/TaskAdapter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/TaskAdapter.java new file mode 100644 index 000000000..6f7ebcbf1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/TaskAdapter.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + + +/** + * Use introspection to "adapt" an arbitrary Bean ( not extending Task, but with + * similar patterns). + * + * @author costin@dnt.ro + */ +public class TaskAdapter extends Task +{ + + Object proxy; + + /** + * Checks a class, whether it is suitable to be adapted by TaskAdapter. + * Checks conditions only, which are additionally required for a tasks + * adapted by TaskAdapter. Thus, this method should be called by {@link + * Project#checkTaskClass}. Throws a BuildException and logs as + * Project.MSG_ERR for conditions, that will cause the task execution to + * fail. Logs other suspicious conditions with Project.MSG_WARN. + * + * @param taskClass Description of Parameter + * @param project Description of Parameter + */ + public static void checkTaskClass( final Class taskClass, final Project project ) + { + // don't have to check for interface, since then + // taskClass would be abstract too. + try + { + final Method executeM = taskClass.getMethod( "execute", null ); + // don't have to check for public, since + // getMethod finds public method only. + // don't have to check for abstract, since then + // taskClass would be abstract too. + if( !Void.TYPE.equals( executeM.getReturnType() ) ) + { + final String message = "return type of execute() should be void but was \"" + executeM.getReturnType() + "\" in " + taskClass; + project.log( message, Project.MSG_WARN ); + } + } + catch( NoSuchMethodException e ) + { + final String message = "No public execute() in " + taskClass; + project.log( message, Project.MSG_ERR ); + throw new BuildException( message ); + } + } + + /** + * Set the target object class + * + * @param o The new Proxy value + */ + public void setProxy( Object o ) + { + this.proxy = o; + } + + public Object getProxy() + { + return this.proxy; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Method setProjectM = null; + try + { + Class c = proxy.getClass(); + setProjectM = + c.getMethod( "setProject", new Class[]{Project.class} ); + if( setProjectM != null ) + { + setProjectM.invoke( proxy, new Object[]{project} ); + } + } + catch( NoSuchMethodException e ) + { + // ignore this if the class being used as a task does not have + // a set project method. + } + catch( Exception ex ) + { + log( "Error setting project in " + proxy.getClass(), + Project.MSG_ERR ); + throw new BuildException( ex ); + } + + Method executeM = null; + try + { + Class c = proxy.getClass(); + executeM = c.getMethod( "execute", new Class[0] ); + if( executeM == null ) + { + log( "No public execute() in " + proxy.getClass(), Project.MSG_ERR ); + throw new BuildException( "No public execute() in " + proxy.getClass() ); + } + executeM.invoke( proxy, null ); + return; + } + catch( Exception ex ) + { + log( "Error in " + proxy.getClass(), Project.MSG_ERR ); + throw new BuildException( ex ); + } + + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/TaskContainer.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/TaskContainer.java new file mode 100644 index 000000000..0802b18c4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/TaskContainer.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; + +/** + * Interface for objects which can contain tasks

          + * + * It is recommended that implementations call {@link Task#perform perform} + * instead of {@link Task#execute execute} for the tasks they contain, as this + * method ensures that {@link BuildEvent BuildEvents} will be generated.

          + * + * @author Conor MacNeill + */ +public interface TaskContainer +{ + /** + * Add a task to this task container + * + * @param task the task to be added to this container + */ + void addTask( Task task ); +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/UnknownElement.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/UnknownElement.java new file mode 100644 index 000000000..efe3b6046 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/UnknownElement.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant; +import java.util.Vector; + +/** + * Wrapper class that holds all information necessary to create a task or data + * type that did not exist when Ant started. + * + * @author Stefan Bodewig + */ +public class UnknownElement extends Task +{ + + /** + * Childelements, holds UnknownElement instances. + */ + private Vector children = new Vector(); + + /** + * Holds the name of the task/type or nested child element of a task/type + * that hasn't been defined at parser time. + */ + private String elementName; + + /** + * The real object after it has been loaded. + */ + private Object realThing; + + public UnknownElement( String elementName ) + { + this.elementName = elementName; + } + + /** + * return the corresponding XML element name. + * + * @return The Tag value + */ + public String getTag() + { + return elementName; + } + + /** + * Return the task instance after it has been created (and if it is a task. + * + * @return The Task value + */ + public Task getTask() + { + if( realThing != null && realThing instanceof Task ) + { + return ( Task )realThing; + } + return null; + } + + /** + * Get the name to use in logging messages. + * + * @return the name to use in logging messages. + */ + public String getTaskName() + { + return realThing == null || !( realThing instanceof Task ) ? + super.getTaskName() : ( ( Task )realThing ).getTaskName(); + } + + /** + * Adds a child element to this element. + * + * @param child The feature to be added to the Child attribute + */ + public void addChild( UnknownElement child ) + { + children.addElement( child ); + } + + /** + * Called when the real task has been configured for the first time. + */ + public void execute() + { + if( realThing == null ) + { + // plain impossible to get here, maybeConfigure should + // have thrown an exception. + throw new BuildException( "Could not create task of type: " + + elementName, location ); + } + + if( realThing instanceof Task ) + { + ( ( Task )realThing ).perform(); + } + } + + /** + * creates the real object instance, creates child elements, configures the + * attributes of the real object. + * + * @exception BuildException Description of Exception + */ + public void maybeConfigure() + throws BuildException + { + realThing = makeObject( this, wrapper ); + + wrapper.setProxy( realThing ); + if( realThing instanceof Task ) + { + ( ( Task )realThing ).setRuntimeConfigurableWrapper( wrapper ); + } + + handleChildren( realThing, wrapper ); + + wrapper.maybeConfigure( project ); + if( realThing instanceof Task ) + { + target.replaceChild( this, realThing ); + } + else + { + target.replaceChild( this, wrapper ); + } + } + + protected BuildException getNotFoundException( String what, + String elementName ) + { + String lSep = System.getProperty( "line.separator" ); + String msg = "Could not create " + what + " of type: " + elementName + + "." + lSep + + "Ant could not find the task or a class this" + lSep + + "task relies upon." + lSep + + "Common solutions are to use taskdef to declare" + lSep + + "your task, or, if this is an optional task," + lSep + + "to put the optional.jar and all required libraries of" + lSep + + "this task in the lib directory of" + lSep + + "your ant installation (ANT_HOME)." + lSep + + "There is also the possibility that your build file " + lSep + + "is written to work with a more recent version of ant " + lSep + + "than the one you are using, in which case you have to " + lSep + + "upgrade."; + return new BuildException( msg, location ); + } + + /** + * Creates child elements, creates children of the children, sets attributes + * of the child elements. + * + * @param parent Description of Parameter + * @param parentWrapper Description of Parameter + * @exception BuildException Description of Exception + */ + protected void handleChildren( Object parent, + RuntimeConfigurable parentWrapper ) + throws BuildException + { + + if( parent instanceof TaskAdapter ) + { + parent = ( ( TaskAdapter )parent ).getProxy(); + } + + Class parentClass = parent.getClass(); + IntrospectionHelper ih = IntrospectionHelper.getHelper( parentClass ); + + for( int i = 0; i < children.size(); i++ ) + { + RuntimeConfigurable childWrapper = parentWrapper.getChild( i ); + UnknownElement child = ( UnknownElement )children.elementAt( i ); + Object realChild = null; + + if( parent instanceof TaskContainer ) + { + realChild = makeTask( child, childWrapper, false ); + ( ( TaskContainer )parent ).addTask( ( Task )realChild ); + } + else + { + realChild = ih.createElement( project, parent, child.getTag() ); + } + + childWrapper.setProxy( realChild ); + if( parent instanceof TaskContainer ) + { + ( ( Task )realChild ).setRuntimeConfigurableWrapper( childWrapper ); + } + + child.handleChildren( realChild, childWrapper ); + + if( parent instanceof TaskContainer ) + { + ( ( Task )realChild ).maybeConfigure(); + } + } + } + + /** + * Creates a named task or data type - if it is a task, configure it up to + * the init() stage. + * + * @param ue Description of Parameter + * @param w Description of Parameter + * @return Description of the Returned Value + */ + protected Object makeObject( UnknownElement ue, RuntimeConfigurable w ) + { + Object o = makeTask( ue, w, true ); + if( o == null ) + { + o = project.createDataType( ue.getTag() ); + } + if( o == null ) + { + throw getNotFoundException( "task or type", ue.getTag() ); + } + return o; + } + + /** + * Create a named task and configure it up to the init() stage. + * + * @param ue Description of Parameter + * @param w Description of Parameter + * @param onTopLevel Description of Parameter + * @return Description of the Returned Value + */ + protected Task makeTask( UnknownElement ue, RuntimeConfigurable w, + boolean onTopLevel ) + { + Task task = project.createTask( ue.getTag() ); + if( task == null && !onTopLevel ) + { + throw getNotFoundException( "task", ue.getTag() ); + } + + if( task != null ) + { + task.setLocation( getLocation() ); + // UnknownElement always has an associated target + task.setOwningTarget( target ); + task.init(); + } + return task; + } + +}// UnknownElement diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/defaultManifest.mf b/proposal/myrmidon/src/todo/org/apache/tools/ant/defaultManifest.mf new file mode 100644 index 000000000..1dc733da7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/defaultManifest.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Created-By: Apache Ant @VERSION@ + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ant.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ant.java new file mode 100644 index 000000000..937f44cd2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ant.java @@ -0,0 +1,550 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.DefaultLogger; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.util.FileUtils; + +/** + * Call Ant in a sub-project
          + *  <target name="foo" depends="init">
          + *    <ant antfile="build.xml" target="bar" >
          + *      <property name="property1" value="aaaaa" />
          + *      <property name="foo" value="baz" />
          + *    </ant> </target> <target name="bar"
          + * depends="init"> <echo message="prop is ${property1}
          + * ${foo}" /> </target> 
          + * + * @author costin@dnt.ro + */ +public class Ant extends Task +{ + + /** + * the basedir where is executed the build file + */ + private File dir = null; + + /** + * the build.xml file (can be absolute) in this case dir will be ignored + */ + private String antFile = null; + + /** + * the target to call if any + */ + private String target = null; + + /** + * the output + */ + private String output = null; + + /** + * should we inherit properties from the parent ? + */ + private boolean inheritAll = true; + + /** + * should we inherit references from the parent ? + */ + private boolean inheritRefs = false; + + /** + * the properties to pass to the new project + */ + private Vector properties = new Vector(); + + /** + * the references to pass to the new project + */ + private Vector references = new Vector(); + + /** + * the temporary project created to run the build file + */ + private Project newProject; + + /** + * set the build file, it can be either absolute or relative. If it is + * absolute, dir will be ignored, if it is relative it will be + * resolved relative to dir . + * + * @param s The new Antfile value + */ + public void setAntfile( String s ) + { + // @note: it is a string and not a file to handle relative/absolute + // otherwise a relative file will be resolved based on the current + // basedir. + this.antFile = s; + } + + /** + * ... + * + * @param d The new Dir value + */ + public void setDir( File d ) + { + this.dir = d; + } + + /** + * If true, inherit all properties from parent Project If false, inherit + * only userProperties and those defined inside the ant call itself + * + * @param value The new InheritAll value + */ + public void setInheritAll( boolean value ) + { + inheritAll = value; + } + + /** + * If true, inherit all references from parent Project If false, inherit + * only those defined inside the ant call itself + * + * @param value The new InheritRefs value + */ + public void setInheritRefs( boolean value ) + { + inheritRefs = value; + } + + public void setOutput( String s ) + { + this.output = s; + } + + /** + * set the target to execute. If none is defined it will execute the default + * target of the build file + * + * @param s The new Target value + */ + public void setTarget( String s ) + { + this.target = s; + } + + /** + * create a reference element that identifies a data type that should be + * carried over to the new project. + * + * @param r The feature to be added to the Reference attribute + */ + public void addReference( Reference r ) + { + references.addElement( r ); + } + + /** + * create a property to pass to the new project as a 'user property' + * + * @return Description of the Returned Value + */ + public Property createProperty() + { + if( newProject == null ) + { + reinit(); + } + Property p = new Property( true ); + p.setProject( newProject ); + p.setTaskName( "property" ); + properties.addElement( p ); + return p; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + try + { + if( newProject == null ) + { + reinit(); + } + + if( ( dir == null ) && ( inheritAll == true ) ) + { + dir = project.getBaseDir(); + } + + initializeProject(); + + if( dir != null ) + { + newProject.setBaseDir( dir ); + newProject.setUserProperty( "basedir", dir.getAbsolutePath() ); + } + else + { + dir = project.getBaseDir(); + } + + overrideProperties(); + + if( antFile == null ) + { + antFile = "build.xml"; + } + + File file = FileUtils.newFileUtils().resolveFile( dir, antFile ); + antFile = file.getAbsolutePath(); + + newProject.setUserProperty( "ant.file", antFile ); + ProjectHelper.configureProject( newProject, new File( antFile ) ); + + if( target == null ) + { + target = newProject.getDefaultTarget(); + } + + addReferences(); + + // Are we trying to call the target in which we are defined? + if( newProject.getBaseDir().equals( project.getBaseDir() ) && + newProject.getProperty( "ant.file" ).equals( project.getProperty( "ant.file" ) ) && + getOwningTarget() != null && + target.equals( this.getOwningTarget().getName() ) ) + { + + throw new BuildException( "ant task calling its own parent target" ); + } + + newProject.executeTarget( target ); + } + finally + { + // help the gc + newProject = null; + } + } + + public void init() + { + newProject = new Project(); + newProject.setJavaVersionProperty(); + newProject.addTaskDefinition( "property", + ( Class )project.getTaskDefinitions().get( "property" ) ); + } + + protected void handleErrorOutput( String line ) + { + if( newProject != null ) + { + newProject.demuxOutput( line, true ); + } + else + { + super.handleErrorOutput( line ); + } + } + + protected void handleOutput( String line ) + { + if( newProject != null ) + { + newProject.demuxOutput( line, false ); + } + else + { + super.handleOutput( line ); + } + } + + /** + * Add the references explicitly defined as nested elements to the new + * project. Also copy over all references that don't override existing + * references in the new project if inheritall has been requested. + * + * @exception BuildException Description of Exception + */ + private void addReferences() + throws BuildException + { + Hashtable thisReferences = ( Hashtable )project.getReferences().clone(); + Hashtable newReferences = newProject.getReferences(); + Enumeration e; + if( references.size() > 0 ) + { + for( e = references.elements(); e.hasMoreElements(); ) + { + Reference ref = ( Reference )e.nextElement(); + String refid = ref.getRefId(); + if( refid == null ) + { + throw new BuildException( "the refid attribute is required for reference elements" ); + } + if( !thisReferences.containsKey( refid ) ) + { + log( "Parent project doesn't contain any reference '" + + refid + "'", + Project.MSG_WARN ); + continue; + } + + Object o = thisReferences.remove( refid ); + String toRefid = ref.getToRefid(); + if( toRefid == null ) + { + toRefid = refid; + } + copyReference( refid, toRefid ); + } + } + + // Now add all references that are not defined in the + // subproject, if inheritRefs is true + if( inheritRefs ) + { + for( e = thisReferences.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + if( newReferences.containsKey( key ) ) + { + continue; + } + copyReference( key, key ); + } + } + } + + /** + * Try to clone and reconfigure the object referenced by oldkey in the + * parent project and add it to the new project with the key newkey.

          + * + * If we cannot clone it, copy the referenced object itself and keep our + * fingers crossed.

          + * + * @param oldKey Description of Parameter + * @param newKey Description of Parameter + */ + private void copyReference( String oldKey, String newKey ) + { + Object orig = project.getReference( oldKey ); + Class c = orig.getClass(); + Object copy = orig; + try + { + Method cloneM = c.getMethod( "clone", new Class[0] ); + if( cloneM != null ) + { + copy = cloneM.invoke( orig, new Object[0] ); + } + } + catch( Exception e ) + { + // not Clonable + } + + if( copy instanceof ProjectComponent ) + { + ( ( ProjectComponent )copy ).setProject( newProject ); + } + else + { + try + { + Method setProjectM = + c.getMethod( "setProject", new Class[]{Project.class} ); + if( setProjectM != null ) + { + setProjectM.invoke( copy, new Object[]{newProject} ); + } + } + catch( NoSuchMethodException e ) + { + // ignore this if the class being referenced does not have + // a set project method. + } + catch( Exception e2 ) + { + String msg = "Error setting new project instance for reference with id " + + oldKey; + throw new BuildException( msg, e2, location ); + } + } + newProject.addReference( newKey, copy ); + } + + private void initializeProject() + { + Vector listeners = project.getBuildListeners(); + for( int i = 0; i < listeners.size(); i++ ) + { + newProject.addBuildListener( ( BuildListener )listeners.elementAt( i ) ); + } + + if( output != null ) + { + try + { + PrintStream out = new PrintStream( new FileOutputStream( output ) ); + DefaultLogger logger = new DefaultLogger(); + logger.setMessageOutputLevel( Project.MSG_INFO ); + logger.setOutputPrintStream( out ); + logger.setErrorPrintStream( out ); + newProject.addBuildListener( logger ); + } + catch( IOException ex ) + { + log( "Ant: Can't set output to " + output ); + } + } + + Hashtable taskdefs = project.getTaskDefinitions(); + Enumeration et = taskdefs.keys(); + while( et.hasMoreElements() ) + { + String taskName = ( String )et.nextElement(); + if( taskName.equals( "property" ) ) + { + // we have already added this taskdef in #init + continue; + } + Class taskClass = ( Class )taskdefs.get( taskName ); + newProject.addTaskDefinition( taskName, taskClass ); + } + + Hashtable typedefs = project.getDataTypeDefinitions(); + Enumeration e = typedefs.keys(); + while( e.hasMoreElements() ) + { + String typeName = ( String )e.nextElement(); + Class typeClass = ( Class )typedefs.get( typeName ); + newProject.addDataTypeDefinition( typeName, typeClass ); + } + + // set user-defined or all properties from calling project + Hashtable prop1; + if( inheritAll == true ) + { + prop1 = project.getProperties(); + } + else + { + prop1 = project.getUserProperties(); + + // set Java built-in properties separately, + // b/c we won't inherit them. + newProject.setSystemProperties(); + } + + e = prop1.keys(); + while( e.hasMoreElements() ) + { + String arg = ( String )e.nextElement(); + if( "basedir".equals( arg ) || "ant.file".equals( arg ) ) + { + // basedir and ant.file get special treatment in execute() + continue; + } + + String value = ( String )prop1.get( arg ); + if( inheritAll == true ) + { + newProject.setProperty( arg, value ); + } + else + { + newProject.setUserProperty( arg, value ); + } + } + } + + /** + * Override the properties in the new project with the one explicitly + * defined as nested elements here. + * + * @exception BuildException Description of Exception + */ + private void overrideProperties() + throws BuildException + { + Enumeration e = properties.elements(); + while( e.hasMoreElements() ) + { + Property p = ( Property )e.nextElement(); + p.setProject( newProject ); + p.execute(); + } + } + + private void reinit() + { + init(); + for( int i = 0; i < properties.size(); i++ ) + { + Property p = ( Property )properties.elementAt( i ); + Property newP = ( Property )newProject.createTask( "property" ); + newP.setName( p.getName() ); + if( p.getValue() != null ) + { + newP.setValue( p.getValue() ); + } + if( p.getFile() != null ) + { + newP.setFile( p.getFile() ); + } + if( p.getResource() != null ) + { + newP.setResource( p.getResource() ); + } + properties.setElementAt( newP, i ); + } + } + + /** + * Helper class that implements the nested <reference> element of + * <ant> and <antcall>. + * + * @author RT + */ + public static class Reference + extends org.apache.tools.ant.types.Reference + { + + private String targetid = null; + + public Reference() + { + super(); + } + + public void setToRefid( String targetid ) + { + this.targetid = targetid; + } + + public String getToRefid() + { + return targetid; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/AntStructure.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/AntStructure.java new file mode 100644 index 000000000..1b1007267 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/AntStructure.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.IntrospectionHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.TaskContainer; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Creates a partial DTD for Ant from the currently known tasks. + * + * @author Stefan Bodewig + * @version $Revision$ + */ + +public class AntStructure extends Task +{ + + private final String lSep = System.getProperty( "line.separator" ); + + private final String BOOLEAN = "%boolean;"; + private final String TASKS = "%tasks;"; + private final String TYPES = "%types;"; + + private Hashtable visited = new Hashtable(); + + private File output; + + /** + * The output file. + * + * @param output The new Output value + */ + public void setOutput( File output ) + { + this.output = output; + } + + public void execute() + throws BuildException + { + + if( output == null ) + { + throw new BuildException( "output attribute is required", location ); + } + + PrintWriter out = null; + try + { + try + { + out = new PrintWriter( new OutputStreamWriter( new FileOutputStream( output ), "UTF8" ) ); + } + catch( UnsupportedEncodingException ue ) + { + /* + * Plain impossible with UTF8, see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * + * fallback to platform specific anyway. + */ + out = new PrintWriter( new FileWriter( output ) ); + } + + printHead( out, project.getTaskDefinitions().keys(), + project.getDataTypeDefinitions().keys() ); + + printTargetDecl( out ); + + Enumeration dataTypes = project.getDataTypeDefinitions().keys(); + while( dataTypes.hasMoreElements() ) + { + String typeName = ( String )dataTypes.nextElement(); + printElementDecl( out, typeName, + ( Class )project.getDataTypeDefinitions().get( typeName ) ); + } + + Enumeration tasks = project.getTaskDefinitions().keys(); + while( tasks.hasMoreElements() ) + { + String taskName = ( String )tasks.nextElement(); + printElementDecl( out, taskName, + ( Class )project.getTaskDefinitions().get( taskName ) ); + } + + printTail( out ); + + } + catch( IOException ioe ) + { + throw new BuildException( "Error writing " + output.getAbsolutePath(), + ioe, location ); + } + finally + { + if( out != null ) + { + out.close(); + } + } + } + + /** + * Does this String match the XML-NMTOKEN production? + * + * @param s Description of Parameter + * @return The Nmtoken value + */ + protected boolean isNmtoken( String s ) + { + for( int i = 0; i < s.length(); i++ ) + { + char c = s.charAt( i ); + // XXX - we are ommitting CombiningChar and Extender here + if( !Character.isLetterOrDigit( c ) && + c != '.' && c != '-' && + c != '_' && c != ':' ) + { + return false; + } + } + return true; + } + + /** + * Do the Strings all match the XML-NMTOKEN production?

          + * + * Otherwise they are not suitable as an enumerated attribute, for example. + *

          + * + * @param s Description of Parameter + * @return Description of the Returned Value + */ + protected boolean areNmtokens( String[] s ) + { + for( int i = 0; i < s.length; i++ ) + { + if( !isNmtoken( s[i] ) ) + { + return false; + } + } + return true; + } + + private void printElementDecl( PrintWriter out, String name, Class element ) + throws BuildException + { + + if( visited.containsKey( name ) ) + { + return; + } + visited.put( name, "" ); + + IntrospectionHelper ih = null; + try + { + ih = IntrospectionHelper.getHelper( element ); + } + catch( Throwable t ) + { + /* + * XXX - failed to load the class properly. + * + * should we print a warning here? + */ + return; + } + + StringBuffer sb = new StringBuffer( "" ).append( lSep ); + sb.append( "" ).append( lSep ); + out.println( sb ); + return; + } + + Vector v = new Vector(); + if( ih.supportsCharacters() ) + { + v.addElement( "#PCDATA" ); + } + + if( TaskContainer.class.isAssignableFrom( element ) ) + { + v.addElement( TASKS ); + } + + Enumeration enum = ih.getNestedElements(); + while( enum.hasMoreElements() ) + { + v.addElement( ( String )enum.nextElement() ); + } + + if( v.isEmpty() ) + { + sb.append( "EMPTY" ); + } + else + { + sb.append( "(" ); + for( int i = 0; i < v.size(); i++ ) + { + if( i != 0 ) + { + sb.append( " | " ); + } + sb.append( v.elementAt( i ) ); + } + sb.append( ")" ); + if( v.size() > 1 || !v.elementAt( 0 ).equals( "#PCDATA" ) ) + { + sb.append( "*" ); + } + } + sb.append( ">" ); + out.println( sb ); + + sb.setLength( 0 ); + sb.append( "" ).append( lSep ); + out.println( sb ); + + for( int i = 0; i < v.size(); i++ ) + { + String nestedName = ( String )v.elementAt( i ); + if( !"#PCDATA".equals( nestedName ) && + !TASKS.equals( nestedName ) && + !TYPES.equals( nestedName ) + ) + { + printElementDecl( out, nestedName, ih.getElementType( nestedName ) ); + } + } + } + + private void printHead( PrintWriter out, Enumeration tasks, + Enumeration types ) + { + out.println( "" ); + out.println( "" ); + out.print( "" ); + out.print( "" ); + + out.println( "" ); + + out.print( "" ); + out.println( "" ); + out.println( "" ); + } + + private void printTail( PrintWriter out ) { } + + private void printTargetDecl( PrintWriter out ) + { + out.print( "" ); + out.println( "" ); + + out.println( "" ); + out.println( "" ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Available.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Available.java new file mode 100644 index 000000000..a4396a6a0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Available.java @@ -0,0 +1,418 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileUtils; + +/** + * Will set the given property if the requested resource is available at + * runtime. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Magesh Umasankar + */ + +public class Available extends Task implements Condition +{ + private String value = "true"; + private String classname; + private Path classpath; + private String file; + private Path filepath; + private AntClassLoader loader; + + private String property; + private String resource; + private FileDir type; + + public void setClassname( String classname ) + { + if( !"".equals( classname ) ) + { + this.classname = classname; + } + } + + public void setClasspath( Path classpath ) + { + createClasspath().append( classpath ); + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setFile( String file ) + { + this.file = file; + } + + public void setFilepath( Path filepath ) + { + createFilepath().append( filepath ); + } + + public void setProperty( String property ) + { + this.property = property; + } + + public void setResource( String resource ) + { + this.resource = resource; + } + + /** + * @param type The new Type value + * @deprecated setType(String) is deprecated and is replaced with + * setType(Available.FileDir) to make Ant's Introspection mechanism do + * the work and also to encapsulate operations on the type in its own + * class. + */ + public void setType( String type ) + { + log( "DEPRECATED - The setType(String) method has been deprecated." + + " Use setType(Available.FileDir) instead." ); + this.type = new FileDir(); + this.type.setValue( type ); + } + + public void setType( FileDir type ) + { + this.type = type; + } + + public void setValue( String value ) + { + this.value = value; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + public Path createFilepath() + { + if( this.filepath == null ) + { + this.filepath = new Path( project ); + } + return this.filepath.createPath(); + } + + public boolean eval() + throws BuildException + { + if( classname == null && file == null && resource == null ) + { + throw new BuildException( "At least one of (classname|file|resource) is required", location ); + } + + if( type != null ) + { + if( file == null ) + { + throw new BuildException( "The type attribute is only valid when specifying the file attribute." ); + } + } + + if( classpath != null ) + { + classpath.setProject( project ); + this.loader = new AntClassLoader( project, classpath ); + } + + if( ( classname != null ) && !checkClass( classname ) ) + { + log( "Unable to load class " + classname + " to set property " + property, Project.MSG_VERBOSE ); + return false; + } + + if( ( file != null ) && !checkFile() ) + { + if( type != null ) + { + log( "Unable to find " + type + " " + file + " to set property " + property, Project.MSG_VERBOSE ); + } + else + { + log( "Unable to find " + file + " to set property " + property, Project.MSG_VERBOSE ); + } + return false; + } + + if( ( resource != null ) && !checkResource( resource ) ) + { + log( "Unable to load resource " + resource + " to set property " + property, Project.MSG_VERBOSE ); + return false; + } + + if( loader != null ) + { + loader.cleanup(); + } + + return true; + } + + public void execute() + throws BuildException + { + if( property == null ) + { + throw new BuildException( "property attribute is required", location ); + } + + if( eval() ) + { + String lSep = System.getProperty( "line.separator" ); + if( null != project.getProperty( property ) ) + { + log( "DEPRECATED - used to overide an existing property. " + + lSep + + " Build writer should not reuse the same property name for " + + lSep + "different values." ); + } + this.project.setProperty( property, value ); + } + } + + private boolean checkClass( String classname ) + { + try + { + if( loader != null ) + { + loader.loadClass( classname ); + } + else + { + ClassLoader l = this.getClass().getClassLoader(); + // Can return null to represent the bootstrap class loader. + // see API docs of Class.getClassLoader. + if( l != null ) + { + l.loadClass( classname ); + } + else + { + Class.forName( classname ); + } + } + return true; + } + catch( ClassNotFoundException e ) + { + return false; + } + catch( NoClassDefFoundError e ) + { + return false; + } + } + + private boolean checkFile() + { + if( filepath == null ) + { + return checkFile( project.resolveFile( file ), file ); + } + else + { + String[] paths = filepath.list(); + for( int i = 0; i < paths.length; ++i ) + { + log( "Searching " + paths[i], Project.MSG_DEBUG ); + /* + * filepath can be a list of directory and/or + * file names (gen'd via ) + * + * look for: + * full-pathname specified == path in list + * full-pathname specified == parent dir of path in list + * simple name specified == path in list + * simple name specified == path in list + name + * simple name specified == parent dir + name + * simple name specified == parent of parent dir + name + * + */ + File path = new File( paths[i] ); + + // ** full-pathname specified == path in list + // ** simple name specified == path in list + if( path.exists() && file.equals( paths[i] ) ) + { + if( type == null ) + { + log( "Found: " + path, Project.MSG_VERBOSE ); + return true; + } + else if( type.isDir() + && path.isDirectory() ) + { + log( "Found directory: " + path, Project.MSG_VERBOSE ); + return true; + } + else if( type.isFile() + && path.isFile() ) + { + log( "Found file: " + path, Project.MSG_VERBOSE ); + return true; + } + // not the requested type + return false; + } + + FileUtils fileUtils = FileUtils.newFileUtils(); + File parent = fileUtils.getParentFile( path ); + // ** full-pathname specified == parent dir of path in list + if( parent != null && parent.exists() + && file.equals( parent.getAbsolutePath() ) ) + { + if( type == null ) + { + log( "Found: " + parent, Project.MSG_VERBOSE ); + return true; + } + else if( type.isDir() ) + { + log( "Found directory: " + parent, Project.MSG_VERBOSE ); + return true; + } + // not the requested type + return false; + } + + // ** simple name specified == path in list + name + if( path.exists() && path.isDirectory() ) + { + if( checkFile( new File( path, file ), + file + " in " + path ) ) + { + return true; + } + } + + // ** simple name specified == parent dir + name + if( parent != null && parent.exists() ) + { + if( checkFile( new File( parent, file ), + file + " in " + parent ) ) + { + return true; + } + } + + // ** simple name specified == parent of parent dir + name + if( parent != null ) + { + File grandParent = fileUtils.getParentFile( parent ); + if( grandParent != null && grandParent.exists() ) + { + if( checkFile( new File( grandParent, file ), + file + " in " + grandParent ) ) + { + return true; + } + } + } + } + } + return false; + } + + private boolean checkFile( File f, String text ) + { + if( type != null ) + { + if( type.isDir() ) + { + if( f.isDirectory() ) + { + log( "Found directory: " + text, Project.MSG_VERBOSE ); + } + return f.isDirectory(); + } + else if( type.isFile() ) + { + if( f.isFile() ) + { + log( "Found file: " + text, Project.MSG_VERBOSE ); + } + return f.isFile(); + } + } + if( f.exists() ) + { + log( "Found: " + text, Project.MSG_VERBOSE ); + } + return f.exists(); + } + + private boolean checkResource( String resource ) + { + if( loader != null ) + { + return ( loader.getResourceAsStream( resource ) != null ); + } + else + { + ClassLoader cL = this.getClass().getClassLoader(); + if( cL != null ) + { + return ( cL.getResourceAsStream( resource ) != null ); + } + else + { + return + ( ClassLoader.getSystemResourceAsStream( resource ) != null ); + } + } + } + + public static class FileDir extends EnumeratedAttribute + { + + private final static String[] values = {"file", "dir"}; + + public String[] getValues() + { + return values; + } + + public boolean isDir() + { + return "dir".equalsIgnoreCase( getValue() ); + } + + public boolean isFile() + { + return "file".equalsIgnoreCase( getValue() ); + } + + public String toString() + { + return getValue(); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BUnzip2.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BUnzip2.java new file mode 100644 index 000000000..882af9655 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BUnzip2.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedInputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.bzip2.CBZip2InputStream; + +/** + * Expands a file that has been compressed with the BZIP2 algorithm. Normally + * used to compress non-compressed archives such as TAR files. + * + * @author Magesh Umasankar + */ + +public class BUnzip2 extends Unpack +{ + + private final static String DEFAULT_EXTENSION = ".bz2"; + + protected String getDefaultExtension() + { + return DEFAULT_EXTENSION; + } + + protected void extract() + { + if( source.lastModified() > dest.lastModified() ) + { + log( "Expanding " + source.getAbsolutePath() + " to " + + dest.getAbsolutePath() ); + + FileOutputStream out = null; + CBZip2InputStream zIn = null; + FileInputStream fis = null; + BufferedInputStream bis = null; + try + { + out = new FileOutputStream( dest ); + fis = new FileInputStream( source ); + bis = new BufferedInputStream( fis ); + int b = bis.read(); + if( b != 'B' ) + { + throw new BuildException( "Invalid bz2 file.", location ); + } + b = bis.read(); + if( b != 'Z' ) + { + throw new BuildException( "Invalid bz2 file.", location ); + } + zIn = new CBZip2InputStream( bis ); + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + out.write( buffer, 0, count ); + count = zIn.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } + catch( IOException ioe ) + { + String msg = "Problem expanding bzip2 " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( bis != null ) + { + try + { + bis.close(); + } + catch( IOException ioex ) + {} + } + if( fis != null ) + { + try + { + fis.close(); + } + catch( IOException ioex ) + {} + } + if( out != null ) + { + try + { + out.close(); + } + catch( IOException ioex ) + {} + } + if( zIn != null ) + { + try + { + zIn.close(); + } + catch( IOException ioex ) + {} + } + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BZip2.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BZip2.java new file mode 100644 index 000000000..9c3cdc401 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/BZip2.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Pack; +import org.apache.tools.bzip2.CBZip2OutputStream; + +/** + * Compresses a file with the BZip2 algorithm. Normally used to compress + * non-compressed archives such as TAR files. + * + * @author Magesh Umasankar + */ + +public class BZip2 extends Pack +{ + protected void pack() + { + CBZip2OutputStream zOut = null; + try + { + BufferedOutputStream bos = + new BufferedOutputStream( new FileOutputStream( zipFile ) ); + bos.write( 'B' ); + bos.write( 'Z' ); + zOut = new CBZip2OutputStream( bos ); + zipFile( source, zOut ); + } + catch( IOException ioe ) + { + String msg = "Problem creating bzip2 " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( zOut != null ) + { + try + { + // close up + zOut.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CVSPass.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CVSPass.java new file mode 100644 index 000000000..356579a4a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CVSPass.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * CVSLogin Adds an new entry to a CVS password file + * + * @author Jeff Martin + * @version $Revision$ + */ +public class CVSPass extends Task +{ + /** + * CVS Root + */ + private String cvsRoot = null; + /** + * Password file to add password to + */ + private File passFile = null; + /** + * Password to add to file + */ + private String password = null; + /** + * End of line character + */ + private final String EOL = System.getProperty( "line.separator" ); + + /** + * Array contain char conversion data + */ + private final char shifts[] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 114, 120, 53, 79, 96, 109, 72, 108, 70, 64, 76, 67, 116, 74, 68, 87, + 111, 52, 75, 119, 49, 34, 82, 81, 95, 65, 112, 86, 118, 110, 122, 105, + 41, 57, 83, 43, 46, 102, 40, 89, 38, 103, 45, 50, 42, 123, 91, 35, + 125, 55, 54, 66, 124, 126, 59, 47, 92, 71, 115, 78, 88, 107, 106, 56, + 36, 121, 117, 104, 101, 100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48, + 58, 113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85, 223, + 225, 216, 187, 166, 229, 189, 222, 188, 141, 249, 148, 200, 184, 136, 248, 190, + 199, 170, 181, 204, 138, 232, 218, 183, 255, 234, 220, 247, 213, 203, 226, 193, + 174, 172, 228, 252, 217, 201, 131, 230, 197, 211, 145, 238, 161, 179, 160, 212, + 207, 221, 254, 173, 202, 146, 224, 151, 140, 196, 205, 130, 135, 133, 143, 246, + 192, 159, 244, 239, 185, 168, 215, 144, 139, 165, 180, 157, 147, 186, 214, 176, + 227, 231, 219, 169, 175, 156, 206, 198, 129, 164, 150, 210, 154, 177, 134, 127, + 182, 128, 158, 208, 162, 132, 167, 209, 149, 241, 153, 251, 237, 236, 171, 195, + 243, 233, 253, 240, 194, 250, 191, 155, 142, 137, 245, 235, 163, 242, 178, 152}; + + public CVSPass() + { + passFile = new File( System.getProperty( "user.home" ) + "/.cvspass" ); + } + + /** + * Sets cvs root to be added to the password file + * + * @param cvsRoot The new Cvsroot value + */ + public void setCvsroot( String cvsRoot ) + { + this.cvsRoot = cvsRoot; + } + + /** + * Sets the password file attribute. + * + * @param passFile The new Passfile value + */ + public void setPassfile( File passFile ) + { + this.passFile = passFile; + } + + /** + * Sets the password attribute. + * + * @param password The new Password value + */ + public void setPassword( String password ) + { + this.password = password; + } + + /** + * Does the work. + * + * @exception BuildException if someting goes wrong with the build + */ + public final void execute() + throws BuildException + { + if( cvsRoot == null ) + throw new BuildException( "cvsroot is required" ); + if( password == null ) + throw new BuildException( "password is required" ); + + log( "cvsRoot: " + cvsRoot, project.MSG_DEBUG ); + log( "password: " + password, project.MSG_DEBUG ); + log( "passFile: " + passFile, project.MSG_DEBUG ); + + try + { + StringBuffer buf = new StringBuffer(); + + if( passFile.exists() ) + { + BufferedReader reader = + new BufferedReader( new FileReader( passFile ) ); + + String line = null; + + while( ( line = reader.readLine() ) != null ) + { + if( !line.startsWith( cvsRoot ) ) + { + buf.append( line + EOL ); + } + } + + reader.close(); + } + + String pwdfile = buf.toString() + cvsRoot + " A" + mangle( password ); + + log( "Writing -> " + pwdfile, project.MSG_DEBUG ); + + PrintWriter writer = new PrintWriter( new FileWriter( passFile ) ); + + writer.println( pwdfile ); + + writer.close(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + } + + private final String mangle( String password ) + { + StringBuffer buf = new StringBuffer(); + for( int i = 0; i < password.length(); i++ ) + { + buf.append( shifts[password.charAt( i )] ); + } + return buf.toString(); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CallTarget.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CallTarget.java new file mode 100644 index 000000000..80f570e08 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CallTarget.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Call another target in the same project.
          + *    <target name="foo">
          + *      <antcall target="bar">
          + *        <param name="property1" value="aaaaa" />
          + *        <param name="foo" value="baz" />
          + *       </antcall>
          + *    </target>
          + *
          + *    <target name="bar" depends="init">
          + *      <echo message="prop is ${property1} ${foo}" />
          + *    </target>
          + * 

          + * + * This only works as expected if neither property1 nor foo are defined in the + * project itself. + * + * @author Stefan Bodewig + */ +public class CallTarget extends Task +{ + private boolean initialized = false; + private boolean inheritAll = true; + + private Ant callee; + private String subTarget; + + /** + * If true, inherit all properties from parent Project If false, inherit + * only userProperties and those defined inside the antcall call itself + * + * @param inherit The new InheritAll value + */ + public void setInheritAll( boolean inherit ) + { + inheritAll = inherit; + } + + public void setTarget( String target ) + { + subTarget = target; + } + + /** + * create a reference element that identifies a data type that should be + * carried over to the new project. + * + * @param r The feature to be added to the Reference attribute + */ + public void addReference( Ant.Reference r ) + { + callee.addReference( r ); + } + + public Property createParam() + { + return callee.createProperty(); + } + + public void execute() + { + if( !initialized ) + { + init(); + } + + if( subTarget == null ) + { + throw new BuildException( "Attribute target is required.", + location ); + } + + callee.setDir( project.getBaseDir() ); + callee.setAntfile( project.getProperty( "ant.file" ) ); + callee.setTarget( subTarget ); + callee.setInheritAll( inheritAll ); + callee.execute(); + }//-- setInheritAll + + public void init() + { + callee = ( Ant )project.createTask( "ant" ); + callee.setOwningTarget( target ); + callee.setTaskName( getTaskName() ); + callee.setLocation( location ); + callee.init(); + initialized = true; + } + + protected void handleErrorOutput( String line ) + { + if( callee != null ) + { + callee.handleErrorOutput( line ); + } + else + { + super.handleErrorOutput( line ); + } + } + + protected void handleOutput( String line ) + { + if( callee != null ) + { + callee.handleOutput( line ); + } + else + { + super.handleOutput( line ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Checksum.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Checksum.java new file mode 100644 index 000000000..a778428c5 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Checksum.java @@ -0,0 +1,489 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.types.FileSet; + +/** + * This task can be used to create checksums for files. It can also be used to + * verify checksums. + * + * @author Magesh Umasankar + */ +public class Checksum extends MatchingTask implements Condition +{ + /** + * File for which checksum is to be calculated. + */ + private File file = null; + /** + * MessageDigest algorithm to be used. + */ + private String algorithm = "MD5"; + /** + * MessageDigest Algorithm provider + */ + private String provider = null; + /** + * Vector to hold source file sets. + */ + private Vector filesets = new Vector(); + /** + * Stores SourceFile, DestFile pairs and SourceFile, Property String pairs. + */ + private Hashtable includeFileMap = new Hashtable(); + /** + * File Extension that is be to used to create or identify destination file + */ + private String fileext; + /** + * Create new destination file? Defaults to false. + */ + private boolean forceOverwrite; + /** + * is this task being used as a nested condition element? + */ + private boolean isCondition; + /** + * Message Digest instance + */ + private MessageDigest messageDigest; + /** + * Holds generated checksum and gets set as a Project Property. + */ + private String property; + /** + * Contains the result of a checksum verification. ("true" or "false") + */ + private String verifyProperty; + + /** + * Sets the MessageDigest algorithm to be used to calculate the checksum. + * + * @param algorithm The new Algorithm value + */ + public void setAlgorithm( String algorithm ) + { + this.algorithm = algorithm; + } + + /** + * Sets the file for which the checksum is to be calculated. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Sets the File Extension that is be to used to create or identify + * destination file + * + * @param fileext The new Fileext value + */ + public void setFileext( String fileext ) + { + this.fileext = fileext; + } + + /** + * Overwrite existing file irrespective of whether it is newer than the + * source file? Defaults to false. + * + * @param forceOverwrite The new ForceOverwrite value + */ + public void setForceOverwrite( boolean forceOverwrite ) + { + this.forceOverwrite = forceOverwrite; + } + + /** + * Sets the property to hold the generated checksum + * + * @param property The new Property value + */ + public void setProperty( String property ) + { + this.property = property; + } + + /** + * Sets the MessageDigest algorithm provider to be used to calculate the + * checksum. + * + * @param provider The new Provider value + */ + public void setProvider( String provider ) + { + this.provider = provider; + } + + /** + * Sets verify property. This project property holds the result of a + * checksum verification - "true" or "false" + * + * @param verifyProperty The new Verifyproperty value + */ + public void setVerifyproperty( String verifyProperty ) + { + this.verifyProperty = verifyProperty; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Calculate the checksum(s) + * + * @return Returns true if the checksum verification test passed, false + * otherwise. + * @exception BuildException Description of Exception + */ + public boolean eval() + throws BuildException + { + isCondition = true; + return validateAndExecute(); + } + + /** + * Calculate the checksum(s). + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + boolean value = validateAndExecute(); + if( verifyProperty != null ) + { + project.setNewProperty( verifyProperty, + new Boolean( value ).toString() ); + } + } + + /** + * Add key-value pair to the hashtable upon which to later operate upon. + * + * @param file The feature to be added to the ToIncludeFileMap attribute + * @exception BuildException Description of Exception + */ + private void addToIncludeFileMap( File file ) + throws BuildException + { + if( file != null ) + { + if( file.exists() ) + { + if( property == null ) + { + File dest = new File( file.getParent(), file.getName() + fileext ); + if( forceOverwrite || isCondition || + ( file.lastModified() > dest.lastModified() ) ) + { + includeFileMap.put( file, dest ); + } + else + { + log( file + " omitted as " + dest + " is up to date.", + Project.MSG_VERBOSE ); + } + } + else + { + includeFileMap.put( file, property ); + } + } + else + { + String message = "Could not find file " + + file.getAbsolutePath() + + " to generate checksum for."; + log( message ); + throw new BuildException( message, location ); + } + } + } + + /** + * Generate checksum(s) using the message digest created earlier. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private boolean generateChecksums() + throws BuildException + { + boolean checksumMatches = true; + FileInputStream fis = null; + FileOutputStream fos = null; + try + { + for( Enumeration e = includeFileMap.keys(); e.hasMoreElements(); ) + { + messageDigest.reset(); + File src = ( File )e.nextElement(); + if( !isCondition ) + { + log( "Calculating " + algorithm + " checksum for " + src ); + } + fis = new FileInputStream( src ); + DigestInputStream dis = new DigestInputStream( fis, + messageDigest ); + while( dis.read() != -1 ) + ; + dis.close(); + fis.close(); + fis = null; + byte[] fileDigest = messageDigest.digest(); + String checksum = ""; + for( int i = 0; i < fileDigest.length; i++ ) + { + String hexStr = Integer.toHexString( 0x00ff & fileDigest[i] ); + if( hexStr.length() < 2 ) + { + checksum += "0"; + } + checksum += hexStr; + } + //can either be a property name string or a file + Object destination = includeFileMap.get( src ); + if( destination instanceof java.lang.String ) + { + String prop = ( String )destination; + if( isCondition ) + { + checksumMatches = checksum.equals( property ); + } + else + { + project.setProperty( prop, checksum ); + } + } + else if( destination instanceof java.io.File ) + { + if( isCondition ) + { + File existingFile = ( File )destination; + if( existingFile.exists() && + existingFile.length() == checksum.length() ) + { + fis = new FileInputStream( existingFile ); + InputStreamReader isr = new InputStreamReader( fis ); + BufferedReader br = new BufferedReader( isr ); + String suppliedChecksum = br.readLine(); + fis.close(); + fis = null; + br.close(); + isr.close(); + checksumMatches = + checksum.equals( suppliedChecksum ); + } + else + { + checksumMatches = false; + } + } + else + { + File dest = ( File )destination; + fos = new FileOutputStream( dest ); + fos.write( checksum.getBytes() ); + fos.close(); + fos = null; + } + } + } + } + catch( Exception e ) + { + throw new BuildException( e ); + } + finally + { + if( fis != null ) + { + try + { + fis.close(); + } + catch( IOException e ) + {} + } + if( fos != null ) + { + try + { + fos.close(); + } + catch( IOException e ) + {} + } + } + return checksumMatches; + } + + /** + * Validate attributes and get down to business. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private boolean validateAndExecute() + throws BuildException + { + + if( file == null && filesets.size() == 0 ) + { + throw new BuildException( + "Specify at least one source - a file or a fileset." ); + } + + if( file != null && file.exists() && file.isDirectory() ) + { + throw new BuildException( + "Checksum cannot be generated for directories" ); + } + + if( property != null && fileext != null ) + { + throw new BuildException( + "Property and FileExt cannot co-exist." ); + } + + if( property != null ) + { + if( forceOverwrite ) + { + throw new BuildException( + "ForceOverwrite cannot be used when Property is specified" ); + } + + if( file != null ) + { + if( filesets.size() > 0 ) + { + throw new BuildException( + "Multiple files cannot be used when Property is specified" ); + } + } + else + { + if( filesets.size() > 1 ) + { + throw new BuildException( + "Multiple files cannot be used when Property is specified" ); + } + } + } + + if( verifyProperty != null ) + { + isCondition = true; + } + + if( verifyProperty != null && forceOverwrite ) + { + throw new BuildException( + "VerifyProperty and ForceOverwrite cannot co-exist." ); + } + + if( isCondition && forceOverwrite ) + { + throw new BuildException( + "ForceOverwrite cannot be used when conditions are being used." ); + } + + if( fileext == null ) + { + fileext = "." + algorithm; + } + else if( fileext.trim().length() == 0 ) + { + throw new BuildException( + "File extension when specified must not be an empty string" ); + } + + messageDigest = null; + if( provider != null ) + { + try + { + messageDigest = MessageDigest.getInstance( algorithm, provider ); + } + catch( NoSuchAlgorithmException noalgo ) + { + throw new BuildException( noalgo ); + } + catch( NoSuchProviderException noprovider ) + { + throw new BuildException( noprovider ); + } + } + else + { + try + { + messageDigest = MessageDigest.getInstance( algorithm ); + } + catch( NoSuchAlgorithmException noalgo ) + { + throw new BuildException( noalgo ); + } + } + + if( messageDigest == null ) + { + throw new BuildException( "Unable to create Message Digest", + location ); + } + + addToIncludeFileMap( file ); + + int sizeofFileSet = filesets.size(); + for( int i = 0; i < sizeofFileSet; i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] srcFiles = ds.getIncludedFiles(); + for( int j = 0; j < srcFiles.length; j++ ) + { + File src = new File( fs.getDir( project ), srcFiles[j] ); + addToIncludeFileMap( src ); + } + } + + return generateChecksums(); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Chmod.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Chmod.java new file mode 100644 index 000000000..96f736e03 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Chmod.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; + + +/** + * Chmod equivalent for unix-like environments. + * + * @author costin@eng.sun.com + * @author Mariusz Nowostawski (Marni) + * mnowostawski@infoscience.otago.ac.nz + * @author Stefan Bodewig + */ + +public class Chmod extends ExecuteOn +{ + + private FileSet defaultSet = new FileSet(); + private boolean defaultSetDefined = false; + private boolean havePerm = false; + + public Chmod() + { + super.setExecutable( "chmod" ); + super.setParallel( true ); + super.setSkipEmptyFilesets( true ); + } + + public void setCommand( String e ) + { + throw new BuildException( taskType + " doesn\'t support the command attribute", location ); + } + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + defaultSetDefined = true; + defaultSet.setDefaultexcludes( useDefaultExcludes ); + } + + public void setDir( File src ) + { + defaultSet.setDir( src ); + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + defaultSetDefined = true; + defaultSet.setExcludes( excludes ); + } + + + public void setExecutable( String e ) + { + throw new BuildException( taskType + " doesn\'t support the executable attribute", location ); + } + + public void setFile( File src ) + { + FileSet fs = new FileSet(); + fs.setDir( new File( src.getParent() ) ); + fs.createInclude().setName( src.getName() ); + addFileset( fs ); + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space. + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + defaultSetDefined = true; + defaultSet.setIncludes( includes ); + } + + public void setPerm( String perm ) + { + createArg().setValue( perm ); + havePerm = true; + } + + public void setSkipEmptyFilesets( boolean skip ) + { + throw new BuildException( taskType + " doesn\'t support the skipemptyfileset attribute", location ); + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + defaultSetDefined = true; + return defaultSet.createExclude(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + defaultSetDefined = true; + return defaultSet.createInclude(); + } + + /** + * add a set of patterns + * + * @return Description of the Returned Value + */ + public PatternSet createPatternSet() + { + defaultSetDefined = true; + return defaultSet.createPatternSet(); + } + + public void execute() + throws BuildException + { + if( defaultSetDefined || defaultSet.getDir( project ) == null ) + { + super.execute(); + } + else if( isValidOs() ) + { + // we are chmodding the given directory + createArg().setValue( defaultSet.getDir( project ).getPath() ); + Execute execute = prepareExec(); + try + { + execute.setCommandline( cmdl.getCommandline() ); + runExecute( execute ); + } + catch( IOException e ) + { + throw new BuildException( "Execute failed: " + e, e, location ); + } + finally + { + // close the output file if required + logFlush(); + } + } + } + + protected boolean isValidOs() + { + return Os.isFamily( "unix" ) && super.isValidOs(); + } + + protected void checkConfiguration() + { + if( !havePerm ) + { + throw new BuildException( "Required attribute perm not set in chmod", + location ); + } + + if( defaultSetDefined && defaultSet.getDir( project ) != null ) + { + addFileset( defaultSet ); + } + super.checkConfiguration(); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CompileTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CompileTask.java new file mode 100644 index 000000000..787a43f73 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/CompileTask.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.types.PatternSet; + +/** + * This task will compile and load a new taskdef all in one step. At times, this + * is useful for eliminating ordering dependencies which otherwise would require + * multiple executions of Ant. + * + * @author Sam Ruby rubys@us.ibm.com + * @deprecated use <taskdef> elements nested into <target>s instead + */ + +public class CompileTask extends Javac +{ + + protected Vector taskList = new Vector(); + + /** + * add a new task entry on the task list + * + * @return Description of the Returned Value + */ + public Taskdef createTaskdef() + { + Taskdef task = new Taskdef(); + taskList.addElement( task ); + return task; + } + + /** + * have execute do nothing + */ + public void execute() { } + + /** + * do all the real work in init + */ + public void init() + { + log( "!! CompileTask is deprecated. !!" ); + log( "Use elements nested into s instead" ); + + // create all the include entries from the task defs + for( Enumeration e = taskList.elements(); e.hasMoreElements(); ) + { + Taskdef task = ( Taskdef )e.nextElement(); + String source = task.getClassname().replace( '.', '/' ) + ".java"; + PatternSet.NameEntry include = super.createInclude(); + include.setName( "**/" + source ); + } + + // execute Javac + super.init(); + super.execute(); + + // now define all the new tasks + for( Enumeration e = taskList.elements(); e.hasMoreElements(); ) + { + Taskdef task = ( Taskdef )e.nextElement(); + task.init(); + } + + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ConditionTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ConditionTask.java new file mode 100644 index 000000000..da72ebc93 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ConditionTask.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.taskdefs.condition.ConditionBase; + +/** + * <condition> task as a generalization of <available> and + * <uptodate>

          + * + * This task supports boolean logic as well as pluggable conditions to decide, + * whether a property should be set.

          + * + * This task does not extend Task to take advantage of ConditionBase.

          + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ConditionTask extends ConditionBase +{ + private String value = "true"; + + private String property; + + /** + * The name of the property to set. Required. + * + * @param p The new Property value + * @since 1.1 + */ + public void setProperty( String p ) + { + property = p; + } + + /** + * The value for the property to set. Defaults to "true". + * + * @param v The new Value value + * @since 1.1 + */ + public void setValue( String v ) + { + value = v; + } + + /** + * See whether our nested condition holds and set the property. + * + * @exception BuildException Description of Exception + * @since 1.1 + */ + public void execute() + throws BuildException + { + if( countConditions() > 1 ) + { + throw new BuildException( "You must not nest more than one condition into " ); + } + if( countConditions() < 1 ) + { + throw new BuildException( "You must nest a condition into " ); + } + Condition c = ( Condition )getConditions().nextElement(); + if( c.eval() ) + { + getProject().setNewProperty( property, value ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copy.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copy.java new file mode 100644 index 000000000..055c978fd --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copy.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.FlatFileNameMapper; +import org.apache.tools.ant.util.IdentityMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * A consolidated copy task. Copies a file or directory to a new file or + * directory. Files are only copied if the source file is newer than the + * destination file, or when the destination file does not exist. It is possible + * to explicitly overwrite existing files.

          + * + * This implementation is based on Arnout Kuiper's initial design document, the + * following mailing list discussions, and the copyfile/copydir tasks.

          + * + * @author Glenn McAllister glennm@ca.ibm.com + * + * @author Stefan Bodewig + * @author Michael McCallum + * @author Magesh Umasankar + */ +public class Copy extends Task +{ + protected File file = null;// the source file + protected File destFile = null;// the destination file + protected File destDir = null;// the destination directory + protected Vector filesets = new Vector(); + + protected boolean filtering = false; + protected boolean preserveLastModified = false; + protected boolean forceOverwrite = false; + protected boolean flatten = false; + protected int verbosity = Project.MSG_VERBOSE; + protected boolean includeEmpty = true; + + protected Hashtable fileCopyMap = new Hashtable(); + protected Hashtable dirCopyMap = new Hashtable(); + protected Hashtable completeDirMap = new Hashtable(); + + protected Mapper mapperElement = null; + private Vector filterSets = new Vector(); + private FileUtils fileUtils; + + public Copy() + { + fileUtils = FileUtils.newFileUtils(); + } + + /** + * Sets a single source file to copy. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Sets filtering. + * + * @param filtering The new Filtering value + */ + public void setFiltering( boolean filtering ) + { + this.filtering = filtering; + } + + /** + * When copying directory trees, the files can be "flattened" into a single + * directory. If there are multiple files with the same name in the source + * directory tree, only the first file will be copied into the "flattened" + * directory, unless the forceoverwrite attribute is true. + * + * @param flatten The new Flatten value + */ + public void setFlatten( boolean flatten ) + { + this.flatten = flatten; + } + + /** + * Used to copy empty directories. + * + * @param includeEmpty The new IncludeEmptyDirs value + */ + public void setIncludeEmptyDirs( boolean includeEmpty ) + { + this.includeEmpty = includeEmpty; + } + + /** + * Overwrite any existing destination file(s). + * + * @param overwrite The new Overwrite value + */ + public void setOverwrite( boolean overwrite ) + { + this.forceOverwrite = overwrite; + } + + /** + * Give the copied files the same last modified time as the original files. + * + * @param preserve The new PreserveLastModified value + * @deprecated setPreserveLastModified(String) has been deprecated and + * replaced with setPreserveLastModified(boolean) to consistently let + * the Introspection mechanism work. + */ + public void setPreserveLastModified( String preserve ) + { + setPreserveLastModified( Project.toBoolean( preserve ) ); + } + + /** + * Give the copied files the same last modified time as the original files. + * + * @param preserve The new PreserveLastModified value + */ + public void setPreserveLastModified( boolean preserve ) + { + preserveLastModified = preserve; + } + + /** + * Sets the destination directory. + * + * @param destDir The new Todir value + */ + public void setTodir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Sets the destination file. + * + * @param destFile The new Tofile value + */ + public void setTofile( File destFile ) + { + this.destFile = destFile; + } + + /** + * Used to force listing of all names of copied files. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + if( verbose ) + { + this.verbosity = Project.MSG_INFO; + } + else + { + this.verbosity = Project.MSG_VERBOSE; + } + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Create a nested filterset + * + * @return Description of the Returned Value + */ + public FilterSet createFilterSet() + { + FilterSet filterSet = new FilterSet(); + filterSets.addElement( filterSet ); + return filterSet; + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapperElement != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapperElement = new Mapper( project ); + return mapperElement; + } + + /** + * Performs the copy operation. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // make sure we don't have an illegal set of options + validateAttributes(); + + // deal with the single file + if( file != null ) + { + if( file.exists() ) + { + if( destFile == null ) + { + destFile = new File( destDir, file.getName() ); + } + + if( forceOverwrite || + ( file.lastModified() > destFile.lastModified() ) ) + { + fileCopyMap.put( file.getAbsolutePath(), destFile.getAbsolutePath() ); + } + else + { + log( file + " omitted as " + destFile + " is up to date.", + Project.MSG_VERBOSE ); + } + } + else + { + String message = "Could not find file " + + file.getAbsolutePath() + " to copy."; + log( message ); + throw new BuildException( message ); + } + } + + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File fromDir = fs.getDir( project ); + + String[] srcFiles = ds.getIncludedFiles(); + String[] srcDirs = ds.getIncludedDirectories(); + boolean isEverythingIncluded = ds.isEverythingIncluded(); + if( isEverythingIncluded + && !flatten && mapperElement == null ) + { + completeDirMap.put( fromDir, destDir ); + } + scan( fromDir, destDir, srcFiles, srcDirs ); + } + + // do all the copy operations now... + doFileOperations(); + + // clean up destDir again - so this instance can be used a second + // time without throwing an exception + if( destFile != null ) + { + destDir = null; + } + } + + protected FileUtils getFileUtils() + { + return fileUtils; + } + + /** + * Get the filtersets being applied to this operation. + * + * @return a vector of FilterSet objects + */ + protected Vector getFilterSets() + { + return filterSets; + } + + protected void buildMap( File fromDir, File toDir, String[] names, + FileNameMapper mapper, Hashtable map ) + { + + String[] toCopy = null; + if( forceOverwrite ) + { + Vector v = new Vector(); + for( int i = 0; i < names.length; i++ ) + { + if( mapper.mapFileName( names[i] ) != null ) + { + v.addElement( names[i] ); + } + } + toCopy = new String[v.size()]; + v.copyInto( toCopy ); + } + else + { + SourceFileScanner ds = new SourceFileScanner( this ); + toCopy = ds.restrict( names, fromDir, toDir, mapper ); + } + + for( int i = 0; i < toCopy.length; i++ ) + { + File src = new File( fromDir, toCopy[i] ); + File dest = new File( toDir, mapper.mapFileName( toCopy[i] )[0] ); + map.put( src.getAbsolutePath(), dest.getAbsolutePath() ); + } + } + + /** + * Actually does the file (and possibly empty directory) copies. This is a + * good method for subclasses to override. + */ + protected void doFileOperations() + { + if( fileCopyMap.size() > 0 ) + { + log( "Copying " + fileCopyMap.size() + + " file" + ( fileCopyMap.size() == 1 ? "" : "s" ) + + " to " + destDir.getAbsolutePath() ); + + Enumeration e = fileCopyMap.keys(); + while( e.hasMoreElements() ) + { + String fromFile = ( String )e.nextElement(); + String toFile = ( String )fileCopyMap.get( fromFile ); + + if( fromFile.equals( toFile ) ) + { + log( "Skipping self-copy of " + fromFile, verbosity ); + continue; + } + + try + { + log( "Copying " + fromFile + " to " + toFile, verbosity ); + + FilterSetCollection executionFilters = new FilterSetCollection(); + if( filtering ) + { + executionFilters.addFilterSet( project.getGlobalFilterSet() ); + } + for( Enumeration filterEnum = filterSets.elements(); filterEnum.hasMoreElements(); ) + { + executionFilters.addFilterSet( ( FilterSet )filterEnum.nextElement() ); + } + fileUtils.copyFile( fromFile, toFile, executionFilters, + forceOverwrite, preserveLastModified ); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + + if( includeEmpty ) + { + Enumeration e = dirCopyMap.elements(); + int count = 0; + while( e.hasMoreElements() ) + { + File d = new File( ( String )e.nextElement() ); + if( !d.exists() ) + { + if( !d.mkdirs() ) + { + log( "Unable to create directory " + d.getAbsolutePath(), Project.MSG_ERR ); + } + else + { + count++; + } + } + } + + if( count > 0 ) + { + log( "Copied " + count + + " empty director" + + ( count == 1 ? "y" : "ies" ) + + " to " + destDir.getAbsolutePath() ); + } + } + } + + /** + * Compares source files to destination files to see if they should be + * copied. + * + * @param fromDir Description of Parameter + * @param toDir Description of Parameter + * @param files Description of Parameter + * @param dirs Description of Parameter + */ + protected void scan( File fromDir, File toDir, String[] files, String[] dirs ) + { + FileNameMapper mapper = null; + if( mapperElement != null ) + { + mapper = mapperElement.getImplementation(); + } + else if( flatten ) + { + mapper = new FlatFileNameMapper(); + } + else + { + mapper = new IdentityMapper(); + } + + buildMap( fromDir, toDir, files, mapper, fileCopyMap ); + + if( includeEmpty ) + { + buildMap( fromDir, toDir, dirs, mapper, dirCopyMap ); + } + } + +//************************************************************************ +// protected and private methods +//************************************************************************ + + /** + * Ensure we have a consistent and legal set of attributes, and set any + * internal flags necessary based on different combinations of attributes. + * + * @exception BuildException Description of Exception + */ + protected void validateAttributes() + throws BuildException + { + if( file == null && filesets.size() == 0 ) + { + throw new BuildException( "Specify at least one source - a file or a fileset." ); + } + + if( destFile != null && destDir != null ) + { + throw new BuildException( "Only one of tofile and todir may be set." ); + } + + if( destFile == null && destDir == null ) + { + throw new BuildException( "One of tofile or todir must be set." ); + } + + if( file != null && file.exists() && file.isDirectory() ) + { + throw new BuildException( "Use a fileset to copy directories." ); + } + + if( destFile != null && filesets.size() > 0 ) + { + if( filesets.size() > 1 ) + { + throw new BuildException( + "Cannot concatenate multiple files into a single file." ); + } + else + { + FileSet fs = ( FileSet )filesets.elementAt( 0 ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] srcFiles = ds.getIncludedFiles(); + + if( srcFiles.length > 0 ) + { + if( file == null ) + { + file = new File( srcFiles[0] ); + filesets.removeElementAt( 0 ); + } + else + { + throw new BuildException( + "Cannot concatenate multiple files into a single file." ); + } + } + else + { + throw new BuildException( + "Cannot perform operation from directory to file." ); + } + } + } + + if( destFile != null ) + { + destDir = new File( destFile.getParent() );// be 1.1 friendly + } + + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copydir.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copydir.java new file mode 100644 index 000000000..f539fffe2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copydir.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; + +/** + * Copies a directory. + * + * @author James Davidson duncan@x180.com + * @deprecated The copydir task is deprecated. Use copy instead. + */ + +public class Copydir extends MatchingTask +{ + private boolean filtering = false; + private boolean flatten = false; + private boolean forceOverwrite = false; + private Hashtable filecopyList = new Hashtable(); + private File destDir; + + private File srcDir; + + public void setDest( File dest ) + { + destDir = dest; + } + + public void setFiltering( boolean filter ) + { + filtering = filter; + } + + public void setFlatten( boolean flatten ) + { + this.flatten = flatten; + } + + public void setForceoverwrite( boolean force ) + { + forceOverwrite = force; + } + + public void setSrc( File src ) + { + srcDir = src; + } + + public void execute() + throws BuildException + { + log( "DEPRECATED - The copydir task is deprecated. Use copy instead." ); + + if( srcDir == null ) + { + throw new BuildException( "src attribute must be set!", + location ); + } + + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir " + srcDir.toString() + + " does not exist!", location ); + } + + if( destDir == null ) + { + throw new BuildException( "The dest attribute must be set.", location ); + } + + if( srcDir.equals( destDir ) ) + { + log( "Warning: src == dest" ); + } + + DirectoryScanner ds = super.getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + scanDir( srcDir, destDir, files ); + if( filecopyList.size() > 0 ) + { + log( "Copying " + filecopyList.size() + " file" + + ( filecopyList.size() == 1 ? "" : "s" ) + + " to " + destDir.getAbsolutePath() ); + Enumeration enum = filecopyList.keys(); + while( enum.hasMoreElements() ) + { + String fromFile = ( String )enum.nextElement(); + String toFile = ( String )filecopyList.get( fromFile ); + try + { + project.copyFile( fromFile, toFile, filtering, + forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + } + + private void scanDir( File from, File to, String[] files ) + { + for( int i = 0; i < files.length; i++ ) + { + String filename = files[i]; + File srcFile = new File( from, filename ); + File destFile; + if( flatten ) + { + destFile = new File( to, new File( filename ).getName() ); + } + else + { + destFile = new File( to, filename ); + } + if( forceOverwrite || + ( srcFile.lastModified() > destFile.lastModified() ) ) + { + filecopyList.put( srcFile.getAbsolutePath(), + destFile.getAbsolutePath() ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copyfile.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copyfile.java new file mode 100644 index 000000000..8d3398975 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Copyfile.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Copies a file. + * + * @author duncan@x180.com + * @deprecated The copyfile task is deprecated. Use copy instead. + */ + +public class Copyfile extends Task +{ + private boolean filtering = false; + private boolean forceOverwrite = false; + private File destFile; + + private File srcFile; + + public void setDest( File dest ) + { + destFile = dest; + } + + public void setFiltering( String filter ) + { + filtering = Project.toBoolean( filter ); + } + + public void setForceoverwrite( boolean force ) + { + forceOverwrite = force; + } + + public void setSrc( File src ) + { + srcFile = src; + } + + public void execute() + throws BuildException + { + log( "DEPRECATED - The copyfile task is deprecated. Use copy instead." ); + + if( srcFile == null ) + { + throw new BuildException( "The src attribute must be present.", location ); + } + + if( !srcFile.exists() ) + { + throw new BuildException( "src " + srcFile.toString() + + " does not exist.", location ); + } + + if( destFile == null ) + { + throw new BuildException( "The dest attribute must be present.", location ); + } + + if( srcFile.equals( destFile ) ) + { + log( "Warning: src == dest" ); + } + + if( forceOverwrite || srcFile.lastModified() > destFile.lastModified() ) + { + try + { + project.copyFile( srcFile, destFile, filtering, forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Error copying file: " + srcFile.getAbsolutePath() + + " due to " + ioe.getMessage(); + throw new BuildException( msg ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Cvs.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Cvs.java new file mode 100644 index 000000000..63bda456c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Cvs.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Environment; + +/** + * @author costin@dnt.ro + * @author stefano@apache.org + * @author Wolfgang Werner + * wwerner@picturesafe.de + */ + +public class Cvs extends Task +{ + + private Commandline cmd = new Commandline(); + + /** + * the CVS command to execute. + */ + private String command = "checkout"; + + /** + * suppress information messages. + */ + private boolean quiet = false; + + /** + * report only, don't change any files. + */ + private boolean noexec = false; + + /** + * CVS port + */ + private int port = 0; + + /** + * CVS password file + */ + private File passFile = null; + + /** + * If true it will stop the build if cvs exits with error. Default is false. + * (Iulian) + */ + private boolean failOnError = false; + + /** + * the CVSROOT variable. + */ + private String cvsRoot; + + /** + * the CVS_RSH variable. + */ + private String cvsRsh; + + /** + * the directory where the checked out files should be placed. + */ + private File dest; + + /** + * the file to direct standard error from the command. + */ + private File error; + + /** + * the file to direct standard output from the command. + */ + private File output; + + /** + * the package/module to check out. + */ + private String pack; + + public void setCommand( String c ) + { + this.command = c; + } + + public void setCvsRoot( String root ) + { + // Check if not real cvsroot => set it to null + if( root != null ) + { + if( root.trim().equals( "" ) ) + root = null; + } + + this.cvsRoot = root; + } + + public void setCvsRsh( String rsh ) + { + // Check if not real cvsrsh => set it to null + if( rsh != null ) + { + if( rsh.trim().equals( "" ) ) + rsh = null; + } + + this.cvsRsh = rsh; + } + + + public void setDate( String p ) + { + if( p != null && p.trim().length() > 0 ) + { + cmd.createArgument().setValue( "-D" ); + cmd.createArgument().setValue( p ); + } + } + + public void setDest( File dest ) + { + this.dest = dest; + } + + public void setError( File error ) + { + this.error = error; + } + + public void setFailOnError( boolean failOnError ) + { + this.failOnError = failOnError; + } + + public void setNoexec( boolean ne ) + { + noexec = ne; + } + + public void setOutput( File output ) + { + this.output = output; + } + + public void setPackage( String p ) + { + this.pack = p; + } + + public void setPassfile( File passFile ) + { + this.passFile = passFile; + } + + public void setPort( int port ) + { + this.port = port; + } + + public void setQuiet( boolean q ) + { + quiet = q; + } + + public void setTag( String p ) + { + // Check if not real tag => set it to null + if( p != null && p.trim().length() > 0 ) + { + cmd.createArgument().setValue( "-r" ); + cmd.createArgument().setValue( p ); + } + } + + + public void execute() + throws BuildException + { + + // XXX: we should use JCVS (www.ice.com/JCVS) instead of command line + // execution so that we don't rely on having native CVS stuff around (SM) + + // We can't do it ourselves as jCVS is GPLed, a third party task + // outside of jakarta repositories would be possible though (SB). + + Commandline toExecute = new Commandline(); + + toExecute.setExecutable( "cvs" ); + if( cvsRoot != null ) + { + toExecute.createArgument().setValue( "-d" ); + toExecute.createArgument().setValue( cvsRoot ); + } + if( noexec ) + { + toExecute.createArgument().setValue( "-n" ); + } + if( quiet ) + { + toExecute.createArgument().setValue( "-q" ); + } + toExecute.createArgument().setLine( command ); + toExecute.addArguments( cmd.getCommandline() ); + + if( pack != null ) + { + toExecute.createArgument().setLine( pack ); + } + + Environment env = new Environment(); + + if( port > 0 ) + { + Environment.Variable var = new Environment.Variable(); + var.setKey( "CVS_CLIENT_PORT" ); + var.setValue( String.valueOf( port ) ); + env.addVariable( var ); + } + + if( passFile != null ) + { + Environment.Variable var = new Environment.Variable(); + var.setKey( "CVS_PASSFILE" ); + var.setValue( String.valueOf( passFile ) ); + env.addVariable( var ); + } + + if( cvsRsh != null ) + { + Environment.Variable var = new Environment.Variable(); + var.setKey( "CVS_RSH" ); + var.setValue( String.valueOf( cvsRsh ) ); + env.addVariable( var ); + } + + ExecuteStreamHandler streamhandler = null; + OutputStream outputstream = null; + OutputStream errorstream = null; + if( error == null && output == null ) + { + streamhandler = new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ); + } + else + { + if( output != null ) + { + try + { + outputstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( output ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + outputstream = new LogOutputStream( this, Project.MSG_INFO ); + } + if( error != null ) + { + try + { + errorstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( error ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + errorstream = new LogOutputStream( this, Project.MSG_WARN ); + } + streamhandler = new PumpStreamHandler( outputstream, errorstream ); + } + + Execute exe = new Execute( streamhandler, + null ); + + exe.setAntRun( project ); + if( dest == null ) + dest = project.getBaseDir(); + exe.setWorkingDirectory( dest ); + + exe.setCommandline( toExecute.getCommandline() ); + exe.setEnvironment( env.getVariables() ); + try + { + int retCode = exe.execute(); + /* + * Throw an exception if cvs exited with error. (Iulian) + */ + if( failOnError && retCode != 0 ) + throw new BuildException( "cvs exited with error code " + retCode ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + if( output != null ) + { + try + { + outputstream.close(); + } + catch( IOException e ) + {} + } + if( error != null ) + { + try + { + errorstream.close(); + } + catch( IOException e ) + {} + } + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Definer.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Definer.java new file mode 100644 index 000000000..0186ee465 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Definer.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Properties; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Base class for Taskdef and Typedef - does all the classpath handling and and + * class loading. + * + * @author Costin Manolache + * @author Stefan Bodewig + */ +public abstract class Definer extends Task +{ + private boolean reverseLoader = false; + private Path classpath; + private File file; + private String name; + private String resource; + private String value; + + public void setClassname( String v ) + { + value = v; + } + + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setFile( File file ) + { + this.file = file; + } + + public void setName( String name ) + { + this.name = name; + } + + public void setResource( String res ) + { + this.resource = res; + } + + public void setReverseLoader( boolean reverseLoader ) + { + this.reverseLoader = reverseLoader; + log( "The reverseloader attribute is DEPRECATED. It will be removed", Project.MSG_WARN ); + } + + public String getClassname() + { + return value; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + public void execute() + throws BuildException + { + AntClassLoader al = createLoader(); + + if( file == null && resource == null ) + { + + // simple case - one definition + if( name == null || value == null ) + { + String msg = "name or classname attributes of " + + getTaskName() + " element " + + "are undefined"; + throw new BuildException( msg ); + } + addDefinition( al, name, value ); + + } + else + { + + try + { + if( name != null || value != null ) + { + String msg = "You must not specify name or value " + + "together with file or resource."; + throw new BuildException( msg, location ); + } + + if( file != null && resource != null ) + { + String msg = "You must not specify both, file and resource."; + throw new BuildException( msg, location ); + } + + Properties props = new Properties(); + InputStream is = null; + if( file != null ) + { + log( "Loading definitions from file " + file, + Project.MSG_VERBOSE ); + is = new FileInputStream( file ); + if( is == null ) + { + log( "Could not load definitions from file " + file + + ". It doesn\'t exist.", Project.MSG_WARN ); + } + } + if( resource != null ) + { + log( "Loading definitions from resource " + resource, + Project.MSG_VERBOSE ); + is = al.getResourceAsStream( resource ); + if( is == null ) + { + log( "Could not load definitions from resource " + + resource + ". It could not be found.", + Project.MSG_WARN ); + } + } + + if( is != null ) + { + props.load( is ); + Enumeration keys = props.keys(); + while( keys.hasMoreElements() ) + { + String n = ( String )keys.nextElement(); + String v = props.getProperty( n ); + addDefinition( al, n, v ); + } + } + } + catch( IOException ex ) + { + throw new BuildException( ex); + } + } + } + + protected abstract void addDefinition( String name, Class c ); + + private void addDefinition( ClassLoader al, String name, String value ) + throws BuildException + { + try + { + Class c = al.loadClass( value ); + AntClassLoader.initializeClass( c ); + addDefinition( name, c ); + } + catch( ClassNotFoundException cnfe ) + { + String msg = getTaskName() + " class " + value + + " cannot be found"; + throw new BuildException( msg, cnfe, location ); + } + catch( NoClassDefFoundError ncdfe ) + { + String msg = getTaskName() + " class " + value + + " cannot be found"; + throw new BuildException( msg, ncdfe, location ); + } + } + + + private AntClassLoader createLoader() + { + AntClassLoader al = null; + if( classpath != null ) + { + al = new AntClassLoader( project, classpath, !reverseLoader ); + } + else + { + al = new AntClassLoader( project, Path.systemClasspath, !reverseLoader ); + } + // need to load Task via system classloader or the new + // task we want to define will never be a Task but always + // be wrapped into a TaskAdapter. + al.addSystemPackageRoot( "org.apache.tools.ant" ); + return al; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Delete.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Delete.java new file mode 100644 index 000000000..e7df0b543 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Delete.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; + +/** + * Deletes a file or directory, or set of files defined by a fileset. The + * original delete task would delete a file, or a set of files using the + * include/exclude syntax. The deltree task would delete a directory tree. This + * task combines the functionality of these two originally distinct tasks.

          + * + * Currently Delete extends MatchingTask. This is intend only to provide + * backwards compatibility for a release. The future position is to use nested + * filesets exclusively.

          + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Tom Dimock tad1@cornell.edu + * @author Glenn McAllister glennm@ca.ibm.com + * + * @author Jon S. Stevens jon@latchkey.com + */ +public class Delete extends MatchingTask +{ + protected File file = null; + protected File dir = null; + protected Vector filesets = new Vector(); + protected boolean usedMatchingTask = false; + protected boolean includeEmpty = false;// by default, remove matching empty dirs + + private int verbosity = Project.MSG_VERBOSE; + private boolean quiet = false; + private boolean failonerror = true; + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + usedMatchingTask = true; + super.setDefaultexcludes( useDefaultExcludes ); + } + + /** + * Set the directory from which files are to be deleted + * + * @param dir the directory path. + */ + public void setDir( File dir ) + { + this.dir = dir; + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + usedMatchingTask = true; + super.setExcludes( excludes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param excludesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setExcludesfile( File excludesfile ) + { + usedMatchingTask = true; + super.setExcludesfile( excludesfile ); + } + + /** + * this flag means 'note errors to the output, but keep going' + * + * @param failonerror true or false + */ + public void setFailOnError( boolean failonerror ) + { + this.failonerror = failonerror; + } + + /** + * Set the name of a single file to be removed. + * + * @param file the file to be deleted + */ + public void setFile( File file ) + { + this.file = file; + } + + + /** + * Used to delete empty directories. + * + * @param includeEmpty The new IncludeEmptyDirs value + */ + public void setIncludeEmptyDirs( boolean includeEmpty ) + { + this.includeEmpty = includeEmpty; + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space. + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + usedMatchingTask = true; + super.setIncludes( includes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param includesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setIncludesfile( File includesfile ) + { + usedMatchingTask = true; + super.setIncludesfile( includesfile ); + } + + /** + * If the file does not exist, do not display a diagnostic message or modify + * the exit status to reflect an error. This means that if a file or + * directory cannot be deleted, then no error is reported. This setting + * emulates the -f option to the Unix "rm" command. Default is + * false meaning things are "noisy" + * + * @param quiet "true" or "on" + */ + public void setQuiet( boolean quiet ) + { + this.quiet = quiet; + if( quiet ) + { + this.failonerror = false; + } + } + + /** + * Used to force listing of all names of deleted files. + * + * @param verbose "true" or "on" + */ + public void setVerbose( boolean verbose ) + { + if( verbose ) + { + this.verbosity = Project.MSG_INFO; + } + else + { + this.verbosity = Project.MSG_VERBOSE; + } + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + usedMatchingTask = true; + return super.createExclude(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + usedMatchingTask = true; + return super.createInclude(); + } + + /** + * add a set of patterns + * + * @return Description of the Returned Value + */ + public PatternSet createPatternSet() + { + usedMatchingTask = true; + return super.createPatternSet(); + } + + /** + * Delete the file(s). + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( usedMatchingTask ) + { + log( "DEPRECATED - Use of the implicit FileSet is deprecated. Use a nested fileset element instead." ); + } + + if( file == null && dir == null && filesets.size() == 0 ) + { + throw new BuildException( "At least one of the file or dir attributes, or a fileset element, must be set." ); + } + + if( quiet && failonerror ) + { + throw new BuildException( "quiet and failonerror cannot both be set to true", + location ); + } + + // delete the single file + if( file != null ) + { + if( file.exists() ) + { + if( file.isDirectory() ) + { + log( "Directory " + file.getAbsolutePath() + " cannot be removed using the file attribute. Use dir instead." ); + } + else + { + log( "Deleting: " + file.getAbsolutePath() ); + + if( !file.delete() ) + { + String message = "Unable to delete file " + file.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + else + { + log( "Could not find file " + file.getAbsolutePath() + " to delete.", + Project.MSG_VERBOSE ); + } + } + + // delete the directory + if( dir != null && dir.exists() && dir.isDirectory() && !usedMatchingTask ) + { + /* + * If verbosity is MSG_VERBOSE, that mean we are doing regular logging + * (backwards as that sounds). In that case, we want to print one + * message about deleting the top of the directory tree. Otherwise, + * the removeDir method will handle messages for _all_ directories. + */ + if( verbosity == Project.MSG_VERBOSE ) + { + log( "Deleting directory " + dir.getAbsolutePath() ); + } + removeDir( dir ); + } + + // delete the files in the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + try + { + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] files = ds.getIncludedFiles(); + String[] dirs = ds.getIncludedDirectories(); + removeFiles( fs.getDir( project ), files, dirs ); + } + catch( BuildException be ) + { + // directory doesn't exist or is not readable + if( failonerror ) + { + throw be; + } + else + { + log( be.getMessage(), + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + + // delete the files from the default fileset + if( usedMatchingTask && dir != null ) + { + try + { + DirectoryScanner ds = super.getDirectoryScanner( dir ); + String[] files = ds.getIncludedFiles(); + String[] dirs = ds.getIncludedDirectories(); + removeFiles( dir, files, dirs ); + } + catch( BuildException be ) + { + // directory doesn't exist or is not readable + if( failonerror ) + { + throw be; + } + else + { + log( be.getMessage(), + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + } + +//************************************************************************ +// protected and private methods +//************************************************************************ + + protected void removeDir( File d ) + { + String[] list = d.list(); + if( list == null ) + list = new String[0]; + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( d, s ); + if( f.isDirectory() ) + { + removeDir( f ); + } + else + { + log( "Deleting " + f.getAbsolutePath(), verbosity ); + if( !f.delete() ) + { + String message = "Unable to delete file " + f.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + log( "Deleting directory " + d.getAbsolutePath(), verbosity ); + if( !d.delete() ) + { + String message = "Unable to delete directory " + dir.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + + /** + * remove an array of files in a directory, and a list of subdirectories + * which will only be deleted if 'includeEmpty' is true + * + * @param d directory to work from + * @param files array of files to delete; can be of zero length + * @param dirs array of directories to delete; can of zero length + */ + protected void removeFiles( File d, String[] files, String[] dirs ) + { + if( files.length > 0 ) + { + log( "Deleting " + files.length + " files from " + d.getAbsolutePath() ); + for( int j = 0; j < files.length; j++ ) + { + File f = new File( d, files[j] ); + log( "Deleting " + f.getAbsolutePath(), verbosity ); + if( !f.delete() ) + { + String message = "Unable to delete file " + f.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + } + } + + if( dirs.length > 0 && includeEmpty ) + { + int dirCount = 0; + for( int j = dirs.length - 1; j >= 0; j-- ) + { + File dir = new File( d, dirs[j] ); + String[] dirFiles = dir.list(); + if( dirFiles == null || dirFiles.length == 0 ) + { + log( "Deleting " + dir.getAbsolutePath(), verbosity ); + if( !dir.delete() ) + { + String message = "Unable to delete directory " + + dir.getAbsolutePath(); + if( failonerror ) + throw new BuildException( message ); + else + log( message, + quiet ? Project.MSG_VERBOSE : Project.MSG_WARN ); + } + else + { + dirCount++; + } + } + } + + if( dirCount > 0 ) + { + log( "Deleted " + dirCount + " director" + + ( dirCount == 1 ? "y" : "ies" ) + + " from " + d.getAbsolutePath() ); + } + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Deltree.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Deltree.java new file mode 100644 index 000000000..f6e746e7b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Deltree.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * @author duncan@x180.com + * @deprecated The deltree task is deprecated. Use delete instead. + */ + +public class Deltree extends Task +{ + + private File dir; + + public void setDir( File dir ) + { + this.dir = dir; + } + + public void execute() + throws BuildException + { + log( "DEPRECATED - The deltree task is deprecated. Use delete instead." ); + + if( dir == null ) + { + throw new BuildException( "dir attribute must be set!", location ); + } + + if( dir.exists() ) + { + if( !dir.isDirectory() ) + { + if( !dir.delete() ) + { + throw new BuildException( "Unable to delete directory " + + dir.getAbsolutePath(), + location ); + } + return; + // String msg = "Given dir: " + dir.getAbsolutePath() + + // " is not a dir"; + // throw new BuildException(msg); + } + + log( "Deleting: " + dir.getAbsolutePath() ); + + try + { + removeDir( dir ); + } + catch( IOException ioe ) + { + String msg = "Unable to delete " + dir.getAbsolutePath(); + throw new BuildException( msg, location ); + } + } + } + + private void removeDir( File dir ) + throws IOException + { + + // check to make sure that the given dir isn't a symlink + // the comparison of absolute path and canonical path + // catches this + + // if (dir.getCanonicalPath().equals(dir.getAbsolutePath())) { + // (costin) It will not work if /home/costin is symlink to /da0/home/costin ( taz + // for example ) + String[] list = dir.list(); + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( dir, s ); + if( f.isDirectory() ) + { + removeDir( f ); + } + else + { + if( !f.delete() ) + { + throw new BuildException( "Unable to delete file " + f.getAbsolutePath() ); + } + } + } + if( !dir.delete() ) + { + throw new BuildException( "Unable to delete directory " + dir.getAbsolutePath() ); + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/DependSet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/DependSet.java new file mode 100644 index 000000000..0768f88e9 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/DependSet.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Date; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.FileList; +import org.apache.tools.ant.types.FileSet; + +/** + * A Task to record explicit dependencies. If any of the target files are out of + * date with respect to any of the source files, all target files are removed. + * This is useful where dependencies cannot be computed (for example, + * dynamically interpreted parameters or files that need to stay in synch but + * are not directly linked) or where the ant task in question could compute them + * but does not (for example, the linked DTD for an XML file using the style + * task). nested arguments: + *
            + *
          • srcfileset (fileset describing the source files to examine) + *
          • srcfilelist (filelist describing the source files to examine) + *
          • targetfileset (fileset describing the target files to examine) + *
          • targetfilelist (filelist describing the target files to examine) + *
          + * At least one instance of either a fileset or filelist for both source and + * target are required.

          + * + * This task will examine each of the source files against each of the target + * files. If any target files are out of date with respect to any of the source + * files, all targets are removed. If any files named in a (src or target) + * filelist do not exist, all targets are removed. Hint: If missing files should + * be ignored, specify them as include patterns in filesets, rather than using + * filelists.

          + * + * This task attempts to optimize speed of dependency checking. It will stop + * after the first out of date file is found and remove all targets, rather than + * exhaustively checking every source vs target combination unnecessarily.

          + *

          + * + * Example uses: + *

            + *
          • Record the fact that an XML file must be up to date with respect to + * its XSD (Schema file), even though the XML file itself includes no + * reference to its XSD.
          • + *
          • Record the fact that an XSL stylesheet includes other sub-stylesheets + *
          • + *
          • Record the fact that java files must be recompiled if the ant build + * file changes
          • + *
          + * + * + * @author Craeg Strong + * @version $Revision$ $Date$ + */ +public class DependSet extends MatchingTask +{ + + private Vector sourceFileSets = new Vector(); + private Vector sourceFileLists = new Vector(); + private Vector targetFileSets = new Vector(); + private Vector targetFileLists = new Vector(); + + /** + * Creates a new DependSet Task. + */ + public DependSet() { } + + /** + * Nested <srcfilelist> element. + * + * @param fl The feature to be added to the Srcfilelist attribute + */ + public void addSrcfilelist( FileList fl ) + { + sourceFileLists.addElement( fl ); + }//-- DependSet + + /** + * Nested <srcfileset> element. + * + * @param fs The feature to be added to the Srcfileset attribute + */ + public void addSrcfileset( FileSet fs ) + { + sourceFileSets.addElement( fs ); + } + + /** + * Nested <targetfilelist> element. + * + * @param fl The feature to be added to the Targetfilelist attribute + */ + public void addTargetfilelist( FileList fl ) + { + targetFileLists.addElement( fl ); + } + + /** + * Nested <targetfileset> element. + * + * @param fs The feature to be added to the Targetfileset attribute + */ + public void addTargetfileset( FileSet fs ) + { + targetFileSets.addElement( fs ); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + + public void execute() + throws BuildException + { + + if( ( sourceFileSets.size() == 0 ) && ( sourceFileLists.size() == 0 ) ) + { + throw new BuildException( "At least one or element must be set" ); + } + + if( ( targetFileSets.size() == 0 ) && ( targetFileLists.size() == 0 ) ) + { + throw new BuildException( "At least one or element must be set" ); + } + + long now = ( new Date() ).getTime(); + /* + * If we're on Windows, we have to munge the time up to 2 secs to + * be able to check file modification times. + * (Windows has a max resolution of two secs for modification times) + */ + if( Os.isFamily( "windows" ) ) + { + now += 2000; + } + + // + // Grab all the target files specified via filesets + // + Vector allTargets = new Vector(); + Enumeration enumTargetSets = targetFileSets.elements(); + while( enumTargetSets.hasMoreElements() ) + { + + FileSet targetFS = ( FileSet )enumTargetSets.nextElement(); + DirectoryScanner targetDS = targetFS.getDirectoryScanner( project ); + String[] targetFiles = targetDS.getIncludedFiles(); + + for( int i = 0; i < targetFiles.length; i++ ) + { + + File dest = new File( targetFS.getDir( project ), targetFiles[i] ); + allTargets.addElement( dest ); + + if( dest.lastModified() > now ) + { + log( "Warning: " + targetFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + } + } + + // + // Grab all the target files specified via filelists + // + boolean upToDate = true; + Enumeration enumTargetLists = targetFileLists.elements(); + while( enumTargetLists.hasMoreElements() ) + { + + FileList targetFL = ( FileList )enumTargetLists.nextElement(); + String[] targetFiles = targetFL.getFiles( project ); + + for( int i = 0; i < targetFiles.length; i++ ) + { + + File dest = new File( targetFL.getDir( project ), targetFiles[i] ); + if( !dest.exists() ) + { + log( targetFiles[i] + " does not exist.", Project.MSG_VERBOSE ); + upToDate = false; + continue; + } + else + { + allTargets.addElement( dest ); + } + if( dest.lastModified() > now ) + { + log( "Warning: " + targetFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + } + } + + // + // Check targets vs source files specified via filesets + // + if( upToDate ) + { + Enumeration enumSourceSets = sourceFileSets.elements(); + while( upToDate && enumSourceSets.hasMoreElements() ) + { + + FileSet sourceFS = ( FileSet )enumSourceSets.nextElement(); + DirectoryScanner sourceDS = sourceFS.getDirectoryScanner( project ); + String[] sourceFiles = sourceDS.getIncludedFiles(); + + for( int i = 0; upToDate && i < sourceFiles.length; i++ ) + { + File src = new File( sourceFS.getDir( project ), sourceFiles[i] ); + + if( src.lastModified() > now ) + { + log( "Warning: " + sourceFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + + Enumeration enumTargets = allTargets.elements(); + while( upToDate && enumTargets.hasMoreElements() ) + { + + File dest = ( File )enumTargets.nextElement(); + if( src.lastModified() > dest.lastModified() ) + { + log( dest.getPath() + " is out of date with respect to " + + sourceFiles[i], Project.MSG_VERBOSE ); + upToDate = false; + + } + } + } + } + } + + // + // Check targets vs source files specified via filelists + // + if( upToDate ) + { + Enumeration enumSourceLists = sourceFileLists.elements(); + while( upToDate && enumSourceLists.hasMoreElements() ) + { + + FileList sourceFL = ( FileList )enumSourceLists.nextElement(); + String[] sourceFiles = sourceFL.getFiles( project ); + + int i = 0; + do + { + File src = new File( sourceFL.getDir( project ), sourceFiles[i] ); + + if( src.lastModified() > now ) + { + log( "Warning: " + sourceFiles[i] + " modified in the future.", + Project.MSG_WARN ); + } + + if( !src.exists() ) + { + log( sourceFiles[i] + " does not exist.", Project.MSG_VERBOSE ); + upToDate = false; + break; + } + + Enumeration enumTargets = allTargets.elements(); + while( upToDate && enumTargets.hasMoreElements() ) + { + + File dest = ( File )enumTargets.nextElement(); + + if( src.lastModified() > dest.lastModified() ) + { + log( dest.getPath() + " is out of date with respect to " + + sourceFiles[i], Project.MSG_VERBOSE ); + upToDate = false; + + } + } + }while ( upToDate && ( ++i < sourceFiles.length ) ); + } + } + + if( !upToDate ) + { + log( "Deleting all target files. ", Project.MSG_VERBOSE ); + for( Enumeration e = allTargets.elements(); e.hasMoreElements(); ) + { + File fileToRemove = ( File )e.nextElement(); + log( "Deleting file " + fileToRemove.getAbsolutePath(), Project.MSG_VERBOSE ); + fileToRemove.delete(); + } + } + + }//-- execute + +}//-- DependSet.java diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ear.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ear.java new file mode 100644 index 000000000..955cbb6ca --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Ear.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.zip.ZipOutputStream; + + +/** + * Creates a EAR archive. Based on WAR task + * + * @author Stefan Bodewig + * @author Les Hughes + */ +public class Ear extends Jar +{ + + private File deploymentDescriptor; + private boolean descriptorAdded; + + public Ear() + { + super(); + archiveType = "ear"; + emptyBehavior = "create"; + } + + public void setAppxml( File descr ) + { + deploymentDescriptor = descr; + if( !deploymentDescriptor.exists() ) + throw new BuildException( "Deployment descriptor: " + deploymentDescriptor + " does not exist." ); + + // Create a ZipFileSet for this file, and pass it up. + ZipFileSet fs = new ZipFileSet(); + fs.setDir( new File( deploymentDescriptor.getParent() ) ); + fs.setIncludes( deploymentDescriptor.getName() ); + fs.setFullpath( "META-INF/application.xml" ); + super.addFileset( fs ); + } + + public void setEarfile( File earFile ) + { + log( "DEPRECATED - The earfile attribute is deprecated. Use file attribute instead." ); + setFile( earFile ); + } + + + public void addArchives( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + // Do we need to do this? LH + log( "addArchives called", Project.MSG_DEBUG ); + fs.setPrefix( "/" ); + super.addFileset( fs ); + } + + /** + * Make sure we don't think we already have a web.xml next time this task + * gets executed. + */ + protected void cleanUp() + { + descriptorAdded = false; + super.cleanUp(); + } + + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + // If no webxml file is specified, it's an error. + if( deploymentDescriptor == null && !isInUpdateMode() ) + { + throw new BuildException( "appxml attribute is required", location ); + } + + super.initZipOutputStream( zOut ); + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + // If the file being added is WEB-INF/web.xml, we warn if it's not the + // one specified in the "webxml" attribute - or if it's being added twice, + // meaning the same file is specified by the "webxml" attribute and in + // a element. + if( vPath.equalsIgnoreCase( "META-INF/aplication.xml" ) ) + { + if( deploymentDescriptor == null || !deploymentDescriptor.equals( file ) || descriptorAdded ) + { + log( "Warning: selected " + archiveType + " files include a META-INF/application.xml which will be ignored " + + "(please use appxml attribute to " + archiveType + " task)", Project.MSG_WARN ); + } + else + { + super.zipFile( file, zOut, vPath ); + descriptorAdded = true; + } + } + else + { + super.zipFile( file, zOut, vPath ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Echo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Echo.java new file mode 100644 index 000000000..1e57b259a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Echo.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Echo + * + * @author costin@dnt.ro + */ +public class Echo extends Task +{ + protected String message = "";// required + protected File file = null; + protected boolean append = false; + + // by default, messages are always displayed + protected int logLevel = Project.MSG_WARN; + + /** + * Shall we append to an existing file? + * + * @param append The new Append value + */ + public void setAppend( boolean append ) + { + this.append = append; + } + + /** + * Sets the file attribute. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Set the logging level to one of + *
            + *
          • error
          • + *
          • warning
          • + *
          • info
          • + *
          • verbose
          • + *
          • debug
          • + *

              + * + * The default is "warning" to ensure that messages are + * displayed by default when using the -quiet command line option.

              + * + * @param echoLevel The new Level value + */ + public void setLevel( EchoLevel echoLevel ) + { + String option = echoLevel.getValue(); + if( option.equals( "error" ) ) + { + logLevel = Project.MSG_ERR; + } + else if( option.equals( "warning" ) ) + { + logLevel = Project.MSG_WARN; + } + else if( option.equals( "info" ) ) + { + logLevel = Project.MSG_INFO; + } + else if( option.equals( "verbose" ) ) + { + logLevel = Project.MSG_VERBOSE; + } + else + { + // must be "debug" + logLevel = Project.MSG_DEBUG; + } + } + + /** + * Sets the message variable. + * + * @param msg Sets the value for the message variable. + */ + public void setMessage( String msg ) + { + this.message = msg; + } + + /** + * Set a multiline message. + * + * @param msg The feature to be added to the Text attribute + */ + public void addText( String msg ) + { + message += project.replaceProperties( msg ); + } + + /** + * Does the work. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( file == null ) + { + log( message, logLevel ); + } + else + { + FileWriter out = null; + try + { + out = new FileWriter( file.getAbsolutePath(), append ); + out.write( message, 0, message.length() ); + } + catch( IOException ioe ) + { + throw new BuildException( ioe); + } + finally + { + if( out != null ) + { + try + { + out.close(); + } + catch( IOException ioex ) + {} + } + } + } + } + + public static class EchoLevel extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"error", "warning", "info", "verbose", "debug"}; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exec.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exec.java new file mode 100644 index 000000000..076de6fde --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exec.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Executes a given command if the os platform is appropriate. + * + * @author duncan@x180.com + * @author rubys@us.ibm.com + * @deprecated Instead of using this class, please extend ExecTask or delegate + * to Execute. + */ +public class Exec extends Task +{ + + private final static int BUFFER_SIZE = 512; + protected PrintWriter fos = null; + private boolean failOnError = false; + private String command; + private File dir; + private String os; + private String out; + + public void setCommand( String command ) + { + this.command = command; + } + + public void setDir( String d ) + { + this.dir = project.resolveFile( d ); + } + + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + public void setOs( String os ) + { + this.os = os; + } + + public void setOutput( String out ) + { + this.out = out; + } + + public void execute() + throws BuildException + { + run( command ); + } + + protected void logFlush() + { + if( fos != null ) + fos.close(); + } + + protected void outputLog( String line, int messageLevel ) + { + if( fos == null ) + { + log( line, messageLevel ); + } + else + { + fos.println( line ); + } + } + + protected int run( String command ) + throws BuildException + { + + int err = -1;// assume the worst + + // test if os match + String myos = System.getProperty( "os.name" ); + log( "Myos = " + myos, Project.MSG_VERBOSE ); + if( ( os != null ) && ( os.indexOf( myos ) < 0 ) ) + { + // this command will be executed only on the specified OS + log( "Not found in " + os, Project.MSG_VERBOSE ); + return 0; + } + + // default directory to the project's base directory + if( dir == null ) + dir = project.getBaseDir(); + + if( myos.toLowerCase().indexOf( "windows" ) >= 0 ) + { + if( !dir.equals( project.resolveFile( "." ) ) ) + { + if( myos.toLowerCase().indexOf( "nt" ) >= 0 ) + { + command = "cmd /c cd " + dir + " && " + command; + } + else + { + String ant = project.getProperty( "ant.home" ); + if( ant == null ) + { + throw new BuildException( "Property 'ant.home' not found", location ); + } + + String antRun = project.resolveFile( ant + "/bin/antRun.bat" ).toString(); + command = antRun + " " + dir + " " + command; + } + } + } + else + { + String ant = project.getProperty( "ant.home" ); + if( ant == null ) + throw new BuildException( "Property 'ant.home' not found", location ); + String antRun = project.resolveFile( ant + "/bin/antRun" ).toString(); + + command = antRun + " " + dir + " " + command; + } + + try + { + // show the command + log( command, Project.MSG_VERBOSE ); + + // exec command on system runtime + Process proc = Runtime.getRuntime().exec( command ); + + if( out != null ) + { + fos = new PrintWriter( new FileWriter( out ) ); + log( "Output redirected to " + out, Project.MSG_VERBOSE ); + } + + // copy input and error to the output stream + StreamPumper inputPumper = + new StreamPumper( proc.getInputStream(), Project.MSG_INFO, this ); + StreamPumper errorPumper = + new StreamPumper( proc.getErrorStream(), Project.MSG_WARN, this ); + + // starts pumping away the generated output/error + inputPumper.start(); + errorPumper.start(); + + // Wait for everything to finish + proc.waitFor(); + inputPumper.join(); + errorPumper.join(); + proc.destroy(); + + // close the output file if required + logFlush(); + + // check its exit value + err = proc.exitValue(); + if( err != 0 ) + { + if( failOnError ) + { + throw new BuildException( "Exec returned: " + err, location ); + } + else + { + log( "Result: " + err, Project.MSG_ERR ); + } + } + } + catch( IOException ioe ) + { + throw new BuildException( "Error exec: " + command, ioe, location ); + } + catch( InterruptedException ex ) + {} + + return err; + } + + // Inner class for continually pumping the input stream during + // Process's runtime. + class StreamPumper extends Thread + { + private boolean endOfStream = false; + private int SLEEP_TIME = 5; + private BufferedReader din; + private int messageLevel; + private Exec parent; + + public StreamPumper( InputStream is, int messageLevel, Exec parent ) + { + this.din = new BufferedReader( new InputStreamReader( is ) ); + this.messageLevel = messageLevel; + this.parent = parent; + } + + public void pumpStream() + throws IOException + { + byte[] buf = new byte[BUFFER_SIZE]; + if( !endOfStream ) + { + String line = din.readLine(); + + if( line != null ) + { + outputLog( line, messageLevel ); + } + else + { + endOfStream = true; + } + } + } + + public void run() + { + try + { + try + { + while( !endOfStream ) + { + pumpStream(); + sleep( SLEEP_TIME ); + } + } + catch( InterruptedException ie ) + {} + din.close(); + } + catch( IOException ioe ) + {} + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecTask.java new file mode 100644 index 000000000..f236d8eae --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecTask.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringReader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Environment; + +/** + * Executes a given command if the os platform is appropriate. + * + * @author duncan@x180.com + * @author rubys@us.ibm.com + * @author thomas.haas@softwired-inc.com + * @author Stefan Bodewig + * @author Mariusz Nowostawski + */ +public class ExecTask extends Task +{ + + private static String lSep = System.getProperty( "line.separator" ); + protected boolean failOnError = false; + protected boolean newEnvironment = false; + private Integer timeout = null; + private Environment env = new Environment(); + protected Commandline cmdl = new Commandline(); + private FileOutputStream fos = null; + private ByteArrayOutputStream baos = null; + private boolean failIfExecFails = true; + + /** + * Controls whether the VM (1.3 and above) is used to execute the command + */ + private boolean vmLauncher = true; + private File dir; + + private String os; + private File out; + private String outputprop; + private String resultProperty; + + /** + * The full commandline to execute, executable + arguments. + * + * @param cmdl The new Command value + */ + public void setCommand( Commandline cmdl ) + { + log( "The command attribute is deprecated. " + + "Please use the executable attribute and nested arg elements.", + Project.MSG_WARN ); + this.cmdl = cmdl; + } + + /** + * The working directory of the process + * + * @param d The new Dir value + */ + public void setDir( File d ) + { + this.dir = d; + } + + /** + * The command to execute. + * + * @param value The new Executable value + */ + public void setExecutable( String value ) + { + cmdl.setExecutable( value ); + } + + /** + * ant attribute + * + * @param flag The new FailIfExecutionFails value + */ + public void setFailIfExecutionFails( boolean flag ) + { + failIfExecFails = flag; + } + + /** + * Throw a BuildException if process returns non 0. + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Use a completely new environment + * + * @param newenv The new Newenvironment value + */ + public void setNewenvironment( boolean newenv ) + { + newEnvironment = newenv; + } + + /** + * Only execute the process if os.name is included in this + * string. + * + * @param os The new Os value + */ + public void setOs( String os ) + { + this.os = os; + } + + /** + * File the output of the process is redirected to. + * + * @param out The new Output value + */ + public void setOutput( File out ) + { + this.out = out; + } + + /** + * Property name whose value should be set to the output of the process + * + * @param outputprop The new Outputproperty value + */ + public void setOutputproperty( String outputprop ) + { + this.outputprop = outputprop; + } + + /** + * fill a property in with a result. when no property is defined: failure to + * execute + * + * @param resultProperty The new ResultProperty value + * @since 1.5 + */ + public void setResultProperty( String resultProperty ) + { + this.resultProperty = resultProperty; + } + + /** + * Timeout in milliseconds after which the process will be killed. + * + * @param value The new Timeout value + */ + public void setTimeout( Integer value ) + { + timeout = value; + } + + /** + * Control whether the VM is used to launch the new process or whether the + * OS's shell is used. + * + * @param vmLauncher The new VMLauncher value + */ + public void setVMLauncher( boolean vmLauncher ) + { + this.vmLauncher = vmLauncher; + } + + /** + * Add a nested env element - an environment variable. + * + * @param var The feature to be added to the Env attribute + */ + public void addEnv( Environment.Variable var ) + { + env.addVariable( var ); + } + + /** + * Add a nested arg element - a command line argument. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createArg() + { + return cmdl.createArgument(); + } + + /** + * Do the work. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + checkConfiguration(); + if( isValidOs() ) + { + runExec( prepareExec() ); + } + } + + /** + * Is this the OS the user wanted? + * + * @return The ValidOs value + */ + protected boolean isValidOs() + { + // test if os match + String myos = System.getProperty( "os.name" ); + log( "Current OS is " + myos, Project.MSG_VERBOSE ); + if( ( os != null ) && ( os.indexOf( myos ) < 0 ) ) + { + // this command will be executed only on the specified OS + log( "This OS, " + myos + " was not found in the specified list of valid OSes: " + os, Project.MSG_VERBOSE ); + return false; + } + return true; + } + + /** + * A Utility method for this classes and subclasses to run an Execute + * instance (an external command). + * + * @param exe Description of Parameter + * @exception IOException Description of Exception + */ + protected final void runExecute( Execute exe ) + throws IOException + { + int err = -1;// assume the worst + + err = exe.execute(); + //test for and handle a forced process death + if( exe.killedProcess() ) + { + log( "Timeout: killed the sub-process", Project.MSG_WARN ); + } + maybeSetResultPropertyValue( err ); + if( err != 0 ) + { + if( failOnError ) + { + throw new BuildException( taskType + " returned: " + err, location ); + } + else + { + log( "Result: " + err, Project.MSG_ERR ); + } + } + if( baos != null ) + { + BufferedReader in = + new BufferedReader( new StringReader( baos.toString() ) ); + String line = null; + StringBuffer val = new StringBuffer(); + while( ( line = in.readLine() ) != null ) + { + if( val.length() != 0 ) + { + val.append( lSep ); + } + val.append( line ); + } + project.setNewProperty( outputprop, val.toString() ); + } + } + + /** + * Has the user set all necessary attributes? + * + * @exception BuildException Description of Exception + */ + protected void checkConfiguration() + throws BuildException + { + if( cmdl.getExecutable() == null ) + { + throw new BuildException( "no executable specified", location ); + } + if( dir != null && !dir.exists() ) + { + throw new BuildException( "The directory you specified does not exist" ); + } + if( dir != null && !dir.isDirectory() ) + { + throw new BuildException( "The directory you specified is not a directory" ); + } + } + + /** + * Create the StreamHandler to use with our Execute instance. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected ExecuteStreamHandler createHandler() + throws BuildException + { + if( out != null ) + { + try + { + fos = new FileOutputStream( out ); + log( "Output redirected to " + out, Project.MSG_VERBOSE ); + return new PumpStreamHandler( fos ); + } + catch( FileNotFoundException fne ) + { + throw new BuildException( "Cannot write to " + out, fne, location ); + } + catch( IOException ioe ) + { + throw new BuildException( "Cannot write to " + out, ioe, location ); + } + } + else if( outputprop != null ) + { + baos = new ByteArrayOutputStream(); + log( "Output redirected to ByteArray", Project.MSG_VERBOSE ); + return new PumpStreamHandler( baos ); + } + else + { + return new LogStreamHandler( this, + Project.MSG_INFO, Project.MSG_WARN ); + } + } + + /** + * Create the Watchdog to kill a runaway process. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected ExecuteWatchdog createWatchdog() + throws BuildException + { + if( timeout == null ) + return null; + return new ExecuteWatchdog( timeout.intValue() ); + } + + /** + * Flush the output stream - if there is one. + */ + protected void logFlush() + { + try + { + if( fos != null ) + fos.close(); + if( baos != null ) + baos.close(); + } + catch( IOException io ) + {} + } + + /** + * helper method to set result property to the passed in value if + * appropriate + * + * @param result Description of Parameter + */ + protected void maybeSetResultPropertyValue( int result ) + { + String res = Integer.toString( result ); + if( resultProperty != null ) + { + project.setNewProperty( resultProperty, res ); + } + } + + /** + * Create an Execute instance with the correct working directory set. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected Execute prepareExec() + throws BuildException + { + // default directory to the project's base directory + if( dir == null ) + dir = project.getBaseDir(); + // show the command + log( cmdl.toString(), Project.MSG_VERBOSE ); + + Execute exe = new Execute( createHandler(), createWatchdog() ); + exe.setAntRun( project ); + exe.setWorkingDirectory( dir ); + exe.setVMLauncher( vmLauncher ); + String[] environment = env.getVariables(); + if( environment != null ) + { + for( int i = 0; i < environment.length; i++ ) + { + log( "Setting environment variable: " + environment[i], + Project.MSG_VERBOSE ); + } + } + exe.setNewenvironment( newEnvironment ); + exe.setEnvironment( environment ); + return exe; + } + + /** + * Run the command using the given Execute instance. This may be overidden + * by subclasses + * + * @param exe Description of Parameter + * @exception BuildException Description of Exception + */ + protected void runExec( Execute exe ) + throws BuildException + { + exe.setCommandline( cmdl.getCommandline() ); + try + { + runExecute( exe ); + } + catch( IOException e ) + { + if( failIfExecFails ) + { + throw new BuildException( "Execute failed: " + e.toString(), e, location ); + } + else + { + log( "Execute failed: " + e.toString(), Project.MSG_ERR ); + } + } + finally + { + // close the output file if required + logFlush(); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Execute.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Execute.java new file mode 100644 index 000000000..c1fdd0c72 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Execute.java @@ -0,0 +1,947 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Locale; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; + +/** + * Runs an external program. + * + * @author thomas.haas@softwired-inc.com + */ +public class Execute +{ + /** + * Invalid exit code. + */ + public final static int INVALID = Integer.MAX_VALUE; + + private static String antWorkingDirectory = System.getProperty( "user.dir" ); + private static CommandLauncher vmLauncher; + private static CommandLauncher shellLauncher; + private static Vector procEnvironment; + + /** + * Used to destroy processes when the VM exits. + */ + private static ProcessDestroyer processDestroyer = new ProcessDestroyer(); + + private String[] cmdl = null; + private String[] env = null; + private int exitValue = INVALID; + private File workingDirectory = null; + private Project project = null; + private boolean newEnvironment = false; + + /** + * Controls whether the VM is used to launch commands, where possible + */ + private boolean useVMLauncher = true; + private ExecuteStreamHandler streamHandler; + private ExecuteWatchdog watchdog; + + /** + * Builds a command launcher for the OS and JVM we are running under + */ + static + { + // Try using a JDK 1.3 launcher + try + { + vmLauncher = new Java13CommandLauncher(); + } + catch( NoSuchMethodException exc ) + { + // Ignore and keep try + } + + if( Os.isFamily( "mac" ) ) + { + // Mac + shellLauncher = new MacCommandLauncher( new CommandLauncher() ); + } + else if( Os.isFamily( "os/2" ) ) + { + // OS/2 - use same mechanism as Windows 2000 + shellLauncher = new WinNTCommandLauncher( new CommandLauncher() ); + } + else if( Os.isFamily( "windows" ) ) + { + // Windows. Need to determine which JDK we're running in + + CommandLauncher baseLauncher; + if( System.getProperty( "java.version" ).startsWith( "1.1" ) ) + { + // JDK 1.1 + baseLauncher = new Java11CommandLauncher(); + } + else + { + // JDK 1.2 + baseLauncher = new CommandLauncher(); + } + + // Determine if we're running under 2000/NT or 98/95 + String osname = + System.getProperty( "os.name" ).toLowerCase( Locale.US ); + + if( osname.indexOf( "nt" ) >= 0 || osname.indexOf( "2000" ) >= 0 ) + { + // Windows 2000/NT + shellLauncher = new WinNTCommandLauncher( baseLauncher ); + } + else + { + // Windows 98/95 - need to use an auxiliary script + shellLauncher = new ScriptCommandLauncher( "bin/antRun.bat", baseLauncher ); + } + } + else if( ( new Os( "netware" ) ).eval() ) + { + // NetWare. Need to determine which JDK we're running in + CommandLauncher baseLauncher; + if( System.getProperty( "java.version" ).startsWith( "1.1" ) ) + { + // JDK 1.1 + baseLauncher = new Java11CommandLauncher(); + } + else + { + // JDK 1.2 + baseLauncher = new CommandLauncher(); + } + + shellLauncher = new PerlScriptCommandLauncher( "bin/antRun.pl", baseLauncher ); + } + else + { + // Generic + shellLauncher = new ScriptCommandLauncher( "bin/antRun", new CommandLauncher() ); + } + } + + /** + * Creates a new execute object using PumpStreamHandler for + * stream handling. + */ + public Execute() + { + this( new PumpStreamHandler(), null ); + } + + /** + * Creates a new execute object. + * + * @param streamHandler the stream handler used to handle the input and + * output streams of the subprocess. + */ + public Execute( ExecuteStreamHandler streamHandler ) + { + this( streamHandler, null ); + } + + /** + * Creates a new execute object. + * + * @param streamHandler the stream handler used to handle the input and + * output streams of the subprocess. + * @param watchdog a watchdog for the subprocess or null to to + * disable a timeout for the subprocess. + */ + public Execute( ExecuteStreamHandler streamHandler, ExecuteWatchdog watchdog ) + { + this.streamHandler = streamHandler; + this.watchdog = watchdog; + } + + /** + * Find the list of environment variables for this process. + * + * @return The ProcEnvironment value + */ + public static synchronized Vector getProcEnvironment() + { + if( procEnvironment != null ) + return procEnvironment; + + procEnvironment = new Vector(); + try + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Execute exe = new Execute( new PumpStreamHandler( out ) ); + exe.setCommandline( getProcEnvCommand() ); + // Make sure we do not recurse forever + exe.setNewenvironment( true ); + int retval = exe.execute(); + if( retval != 0 ) + { + // Just try to use what we got + } + + BufferedReader in = + new BufferedReader( new StringReader( out.toString() ) ); + String var = null; + String line; + String lineSep = System.getProperty( "line.separator" ); + while( ( line = in.readLine() ) != null ) + { + if( line.indexOf( '=' ) == -1 ) + { + // Chunk part of previous env var (UNIX env vars can + // contain embedded new lines). + if( var == null ) + { + var = lineSep + line; + } + else + { + var += lineSep + line; + } + } + else + { + // New env var...append the previous one if we have it. + if( var != null ) + { + procEnvironment.addElement( var ); + } + var = line; + } + } + // Since we "look ahead" before adding, there's one last env var. + procEnvironment.addElement( var ); + } + catch( java.io.IOException exc ) + { + exc.printStackTrace(); + // Just try to see how much we got + } + return procEnvironment; + } + + /** + * A utility method that runs an external command. Writes the output and + * error streams of the command to the project log. + * + * @param task The task that the command is part of. Used for logging + * @param cmdline The command to execute. + * @throws BuildException if the command does not return 0. + */ + public static void runCommand( Task task, String[] cmdline ) + throws BuildException + { + try + { + task.log( Commandline.toString( cmdline ), Project.MSG_VERBOSE ); + Execute exe = new Execute( new LogStreamHandler( task, + Project.MSG_INFO, + Project.MSG_ERR ) ); + exe.setAntRun( task.getProject() ); + exe.setCommandline( cmdline ); + int retval = exe.execute(); + if( retval != 0 ) + { + throw new BuildException( cmdline[ 0 ] + " failed with return code " + retval, task.getLocation() ); + } + } + catch( java.io.IOException exc ) + { + throw new BuildException( "Could not launch " + cmdline[ 0 ] + ": " + exc, task.getLocation() ); + } + } + + private static String[] getProcEnvCommand() + { + if( Os.isFamily( "os/2" ) ) + { + // OS/2 - use same mechanism as Windows 2000 + // Not sure + String[] cmd = {"cmd", "/c", "set"}; + return cmd; + } + else if( Os.isFamily( "windows" ) ) + { + String osname = + System.getProperty( "os.name" ).toLowerCase( Locale.US ); + // Determine if we're running under 2000/NT or 98/95 + if( osname.indexOf( "nt" ) >= 0 || osname.indexOf( "2000" ) >= 0 ) + { + // Windows 2000/NT + String[] cmd = {"cmd", "/c", "set"}; + return cmd; + } + else + { + // Windows 98/95 - need to use an auxiliary script + String[] cmd = {"command.com", "/c", "set"}; + return cmd; + } + } + else if( Os.isFamily( "unix" ) ) + { + // Generic UNIX + // Alternatively one could use: /bin/sh -c env + String[] cmd = {"/usr/bin/env"}; + return cmd; + } + else if( Os.isFamily( "netware" ) ) + { + String[] cmd = {"env"}; + return cmd; + } + else + { + // MAC OS 9 and previous + // TODO: I have no idea how to get it, someone must fix it + String[] cmd = null; + return cmd; + } + } + + /** + * Set the name of the antRun script using the project's value. + * + * @param project the current project. + * @exception BuildException Description of Exception + */ + public void setAntRun( Project project ) + throws BuildException + { + this.project = project; + } + + /** + * Sets the commandline of the subprocess to launch. + * + * @param commandline the commandline of the subprocess to launch + */ + public void setCommandline( String[] commandline ) + { + cmdl = commandline; + } + + /** + * Sets the environment variables for the subprocess to launch. + * + * @param env The new Environment value + */ + public void setEnvironment( String[] env ) + { + this.env = env; + } + + /** + * Set whether to propagate the default environment or not. + * + * @param newenv whether to propagate the process environment. + */ + public void setNewenvironment( boolean newenv ) + { + newEnvironment = newenv; + } + + /** + * Launch this execution through the VM, where possible, rather than through + * the OS's shell. In some cases and operating systems using the shell will + * allow the shell to perform additional processing such as associating an + * executable with a script, etc + * + * @param useVMLauncher The new VMLauncher value + */ + public void setVMLauncher( boolean useVMLauncher ) + { + this.useVMLauncher = useVMLauncher; + } + + /** + * Sets the working directory of the process to execute.

              + * + * This is emulated using the antRun scripts unless the OS is Windows NT in + * which case a cmd.exe is spawned, or MRJ and setting user.dir works, or + * JDK 1.3 and there is official support in java.lang.Runtime. + * + * @param wd the working directory of the process. + */ + public void setWorkingDirectory( File wd ) + { + if( wd == null || wd.getAbsolutePath().equals( antWorkingDirectory ) ) + workingDirectory = null; + else + workingDirectory = wd; + } + + /** + * Returns the commandline used to create a subprocess. + * + * @return the commandline used to create a subprocess + */ + public String[] getCommandline() + { + return cmdl; + } + + /** + * Returns the environment used to create a subprocess. + * + * @return the environment used to create a subprocess + */ + public String[] getEnvironment() + { + if( env == null || newEnvironment ) + return env; + return patchEnvironment(); + } + + /** + * query the exit value of the process. + * + * @return the exit value, 1 if the process was killed, or Project.INVALID + * if no exit value has been received + */ + public int getExitValue() + { + return exitValue; + } + + /** + * Runs a process defined by the command line and returns its exit status. + * + * @return the exit status of the subprocess or INVALID + * @exception IOException Description of Exception + */ + public int execute() + throws IOException + { + CommandLauncher launcher = vmLauncher != null ? vmLauncher : shellLauncher; + if( !useVMLauncher ) + { + launcher = shellLauncher; + } + + final Process process = launcher.exec( project, getCommandline(), getEnvironment(), workingDirectory ); + try + { + streamHandler.setProcessInputStream( process.getOutputStream() ); + streamHandler.setProcessOutputStream( process.getInputStream() ); + streamHandler.setProcessErrorStream( process.getErrorStream() ); + } + catch( IOException e ) + { + process.destroy(); + throw e; + } + streamHandler.start(); + + // add the process to the list of those to destroy if the VM exits + // + processDestroyer.add( process ); + + if( watchdog != null ) + watchdog.start( process ); + waitFor( process ); + + // remove the process to the list of those to destroy if the VM exits + // + processDestroyer.remove( process ); + + if( watchdog != null ) + watchdog.stop(); + streamHandler.stop(); + if( watchdog != null ) + watchdog.checkException(); + return getExitValue(); + } + + /** + * test for an untimely death of the process + * + * @return true iff a watchdog had to kill the process + * @since 1.5 + */ + public boolean killedProcess() + { + return watchdog != null && watchdog.killedProcess(); + } + + protected void setExitValue( int value ) + { + exitValue = value; + } + + protected void waitFor( Process process ) + { + try + { + process.waitFor(); + setExitValue( process.exitValue() ); + } + catch( InterruptedException e ) + { + } + } + + /** + * Patch the current environment with the new values from the user. + * + * @return the patched environment + */ + private String[] patchEnvironment() + { + Vector osEnv = (Vector)getProcEnvironment().clone(); + for( int i = 0; i < env.length; i++ ) + { + int pos = env[ i ].indexOf( '=' ); + // Get key including "=" + String key = env[ i ].substring( 0, pos + 1 ); + int size = osEnv.size(); + for( int j = 0; j < size; j++ ) + { + if( ( (String)osEnv.elementAt( j ) ).startsWith( key ) ) + { + osEnv.removeElementAt( j ); + break; + } + } + osEnv.addElement( env[ i ] ); + } + String[] result = new String[ osEnv.size() ]; + osEnv.copyInto( result ); + return result; + } + + /** + * A command launcher for a particular JVM/OS platform. This class is a + * general purpose command launcher which can only launch commands in the + * current working directory. + * + * @author RT + */ + private static class CommandLauncher + { + /** + * Launches the given command in a new process. + * + * @param project The project that the command is part of + * @param cmd The command to execute + * @param env The environment for the new process. If null, the + * environment of the current proccess is used. + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env ) + throws IOException + { + if( project != null ) + { + project.log( "Execute:CommandLauncher: " + + Commandline.toString( cmd ), Project.MSG_DEBUG ); + } + return Runtime.getRuntime().exec( cmd, env ); + } + + /** + * Launches the given command in a new process, in the given working + * directory. + * + * @param project The project that the command is part of + * @param cmd The command to execute + * @param env The environment for the new process. If null, the + * environment of the current proccess is used. + * @param workingDir The directory to start the command in. If null, the + * current directory is used + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + throw new IOException( "Cannot execute a process in different directory under this JVM" ); + } + } + + /** + * A command launcher that proxies another command launcher. Sub-classes + * override exec(args, env, workdir) + * + * @author RT + */ + private static class CommandLauncherProxy extends CommandLauncher + { + + private CommandLauncher _launcher; + + CommandLauncherProxy( CommandLauncher launcher ) + { + _launcher = launcher; + } + + /** + * Launches the given command in a new process. Delegates this method to + * the proxied launcher + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env ) + throws IOException + { + return _launcher.exec( project, cmd, env ); + } + } + + /** + * A command launcher for JDK/JRE 1.1 under Windows. Fixes quoting problems + * in Runtime.exec(). Can only launch commands in the current working + * directory + * + * @author RT + */ + private static class Java11CommandLauncher extends CommandLauncher + { + /** + * Launches the given command in a new process. Needs to quote arguments + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env ) + throws IOException + { + // Need to quote arguments with spaces, and to escape quote characters + String[] newcmd = new String[ cmd.length ]; + for( int i = 0; i < cmd.length; i++ ) + { + newcmd[ i ] = Commandline.quoteArgument( cmd[ i ] ); + } + if( project != null ) + { + project.log( "Execute:Java11CommandLauncher: " + + Commandline.toString( newcmd ), Project.MSG_DEBUG ); + } + return Runtime.getRuntime().exec( newcmd, env ); + } + } + + /** + * A command launcher for JDK/JRE 1.3 (and higher). Uses the built-in + * Runtime.exec() command + * + * @author RT + */ + private static class Java13CommandLauncher extends CommandLauncher + { + + private Method _execWithCWD; + + public Java13CommandLauncher() + throws NoSuchMethodException + { + // Locate method Runtime.exec(String[] cmdarray, String[] envp, File dir) + _execWithCWD = Runtime.class.getMethod( "exec", new Class[]{String[].class, String[].class, File.class} ); + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + try + { + if( project != null ) + { + project.log( "Execute:Java13CommandLauncher: " + + Commandline.toString( cmd ), Project.MSG_DEBUG ); + } + Object[] arguments = {cmd, env, workingDir}; + return (Process)_execWithCWD.invoke( Runtime.getRuntime(), arguments ); + } + catch( InvocationTargetException exc ) + { + Throwable realexc = exc.getTargetException(); + if( realexc instanceof ThreadDeath ) + { + throw (ThreadDeath)realexc; + } + else if( realexc instanceof IOException ) + { + throw (IOException)realexc; + } + else + { + throw new BuildException( "Unable to execute command", realexc ); + } + } + catch( Exception exc ) + { + // IllegalAccess, IllegalArgument, ClassCast + throw new BuildException( "Unable to execute command", exc ); + } + } + } + + /** + * A command launcher for Mac that uses a dodgy mechanism to change working + * directory before launching commands. + * + * @author RT + */ + private static class MacCommandLauncher extends CommandLauncherProxy + { + MacCommandLauncher( CommandLauncher launcher ) + { + super( launcher ); + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + + System.getProperties().put( "user.dir", workingDir.getAbsolutePath() ); + try + { + return exec( project, cmd, env ); + } + finally + { + System.getProperties().put( "user.dir", antWorkingDirectory ); + } + } + } + + /** + * A command launcher that uses an auxiliary perl script to launch commands + * in directories other than the current working directory. + * + * @author RT + */ + private static class PerlScriptCommandLauncher extends CommandLauncherProxy + { + + private String _script; + + PerlScriptCommandLauncher( String script, CommandLauncher launcher ) + { + super( launcher ); + _script = script; + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( project == null ) + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + throw new IOException( "Cannot locate antRun script: No project provided" ); + } + + // Locate the auxiliary script + String antHome = project.getProperty( "ant.home" ); + if( antHome == null ) + { + throw new IOException( "Cannot locate antRun script: Property 'ant.home' not found" ); + } + String antRun = project.resolveFile( antHome + File.separator + _script ).toString(); + + // Build the command + File commandDir = workingDir; + if( workingDir == null && project != null ) + { + commandDir = project.getBaseDir(); + } + + String[] newcmd = new String[ cmd.length + 3 ]; + newcmd[ 0 ] = "perl"; + newcmd[ 1 ] = antRun; + newcmd[ 2 ] = commandDir.getAbsolutePath(); + System.arraycopy( cmd, 0, newcmd, 3, cmd.length ); + + return exec( project, newcmd, env ); + } + } + + /** + * A command launcher that uses an auxiliary script to launch commands in + * directories other than the current working directory. + * + * @author RT + */ + private static class ScriptCommandLauncher extends CommandLauncherProxy + { + + private String _script; + + ScriptCommandLauncher( String script, CommandLauncher launcher ) + { + super( launcher ); + _script = script; + } + + /** + * Launches the given command in a new process, in the given working + * directory + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + if( project == null ) + { + if( workingDir == null ) + { + return exec( project, cmd, env ); + } + throw new IOException( "Cannot locate antRun script: No project provided" ); + } + + // Locate the auxiliary script + String antHome = project.getProperty( "ant.home" ); + if( antHome == null ) + { + throw new IOException( "Cannot locate antRun script: Property 'ant.home' not found" ); + } + String antRun = project.resolveFile( antHome + File.separator + _script ).toString(); + + // Build the command + File commandDir = workingDir; + if( workingDir == null && project != null ) + { + commandDir = project.getBaseDir(); + } + + String[] newcmd = new String[ cmd.length + 2 ]; + newcmd[ 0 ] = antRun; + newcmd[ 1 ] = commandDir.getAbsolutePath(); + System.arraycopy( cmd, 0, newcmd, 2, cmd.length ); + + return exec( project, newcmd, env ); + } + } + + /** + * A command launcher for Windows 2000/NT that uses 'cmd.exe' when launching + * commands in directories other than the current working directory. + * + * @author RT + */ + private static class WinNTCommandLauncher extends CommandLauncherProxy + { + WinNTCommandLauncher( CommandLauncher launcher ) + { + super( launcher ); + } + + /** + * Launches the given command in a new process, in the given working + * directory. + * + * @param project Description of Parameter + * @param cmd Description of Parameter + * @param env Description of Parameter + * @param workingDir Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + public Process exec( Project project, String[] cmd, String[] env, File workingDir ) + throws IOException + { + File commandDir = workingDir; + if( workingDir == null ) + { + if( project != null ) + { + commandDir = project.getBaseDir(); + } + else + { + return exec( project, cmd, env ); + } + } + + // Use cmd.exe to change to the specified directory before running + // the command + final int preCmdLength = 6; + String[] newcmd = new String[ cmd.length + preCmdLength ]; + newcmd[ 0 ] = "cmd"; + newcmd[ 1 ] = "/c"; + newcmd[ 2 ] = "cd"; + newcmd[ 3 ] = "/d"; + newcmd[ 4 ] = commandDir.getAbsolutePath(); + newcmd[ 5 ] = "&&"; + System.arraycopy( cmd, 0, newcmd, preCmdLength, cmd.length ); + + return exec( project, newcmd, env ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteJava.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteJava.java new file mode 100644 index 000000000..6fc077991 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteJava.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Path; + +/* + * @author thomas.haas@softwired-inc.com + * @author Stefan Bodewig + */ +public class ExecuteJava +{ + + private Commandline javaCommand = null; + private Path classpath = null; + private CommandlineJava.SysProperties sysProperties = null; + + public void setClasspath( Path p ) + { + classpath = p; + } + + public void setJavaCommand( Commandline javaCommand ) + { + this.javaCommand = javaCommand; + } + + /** + * All output (System.out as well as System.err) will be written to this + * Stream. + * + * @param out The new Output value + * @deprecated manage output at the task level + */ + public void setOutput( PrintStream out ) { } + + public void setSystemProperties( CommandlineJava.SysProperties s ) + { + sysProperties = s; + } + + public void execute( Project project ) + throws BuildException + { + final String classname = javaCommand.getExecutable(); + final Object[] argument = {javaCommand.getArguments()}; + + AntClassLoader loader = null; + try + { + if( sysProperties != null ) + { + sysProperties.setSystem(); + } + + final Class[] param = {Class.forName( "[Ljava.lang.String;" )}; + Class target = null; + if( classpath == null ) + { + target = Class.forName( classname ); + } + else + { + loader = new AntClassLoader( project.getCoreLoader(), project, classpath, false ); + loader.setIsolated( true ); + loader.setThreadContextLoader(); + target = loader.forceLoadClass( classname ); + AntClassLoader.initializeClass( target ); + } + final Method main = target.getMethod( "main", param ); + main.invoke( null, argument ); + } + catch( NullPointerException e ) + { + throw new BuildException( "Could not find main() method in " + classname ); + } + catch( ClassNotFoundException e ) + { + throw new BuildException( "Could not find " + classname + ". Make sure you have it in your classpath" ); + } + catch( InvocationTargetException e ) + { + Throwable t = e.getTargetException(); + if( !( t instanceof SecurityException ) ) + { + throw new BuildException( t ); + } + else + { + throw ( SecurityException )t; + } + } + catch( Exception e ) + { + throw new BuildException( e ); + } + finally + { + if( loader != null ) + { + loader.resetThreadContextLoader(); + loader.cleanup(); + } + if( sysProperties != null ) + { + sysProperties.restoreSystem(); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteOn.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteOn.java new file mode 100644 index 000000000..0a8bd9d53 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteOn.java @@ -0,0 +1,473 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Executes a given command, supplying a set of files as arguments. + * + * @author Stefan Bodewig + * @author Mariusz Nowostawski + */ +public class ExecuteOn extends ExecTask +{ + + protected Vector filesets = new Vector(); + private boolean relative = false; + private boolean parallel = false; + protected String type = "file"; + protected Commandline.Marker srcFilePos = null; + private boolean skipEmpty = false; + protected Commandline.Marker targetFilePos = null; + protected Mapper mapperElement = null; + protected FileNameMapper mapper = null; + protected File destDir = null; + + /** + * Has <srcfile> been specified before <targetfile> + */ + protected boolean srcIsFirst = true; + + /** + * Set the destination directory. + * + * @param destDir The new Dest value + */ + public void setDest( File destDir ) + { + this.destDir = destDir; + } + + + /** + * Shall the command work on all specified files in parallel? + * + * @param parallel The new Parallel value + */ + public void setParallel( boolean parallel ) + { + this.parallel = parallel; + } + + /** + * Should filenames be returned as relative path names? + * + * @param relative The new Relative value + */ + public void setRelative( boolean relative ) + { + this.relative = relative; + } + + /** + * Should empty filesets be ignored? + * + * @param skip The new SkipEmptyFilesets value + */ + public void setSkipEmptyFilesets( boolean skip ) + { + skipEmpty = skip; + } + + /** + * Shall the command work only on files, directories or both? + * + * @param type The new Type value + */ + public void setType( FileDirBoth type ) + { + this.type = type.getValue(); + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapperElement != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapperElement = new Mapper( project ); + return mapperElement; + } + + /** + * Marker that indicates where the name of the source file should be put on + * the command line. + * + * @return Description of the Returned Value + */ + public Commandline.Marker createSrcfile() + { + if( srcFilePos != null ) + { + throw new BuildException( taskType + " doesn\'t support multiple srcfile elements.", + location ); + } + srcFilePos = cmdl.createMarker(); + return srcFilePos; + } + + /** + * Marker that indicates where the name of the target file should be put on + * the command line. + * + * @return Description of the Returned Value + */ + public Commandline.Marker createTargetfile() + { + if( targetFilePos != null ) + { + throw new BuildException( taskType + " doesn\'t support multiple targetfile elements.", + location ); + } + targetFilePos = cmdl.createMarker(); + srcIsFirst = ( srcFilePos != null ); + return targetFilePos; + } + + /** + * Construct the command line for parallel execution. + * + * @param srcFiles The filenames to add to the commandline + * @param baseDirs Description of Parameter + * @return The Commandline value + */ + protected String[] getCommandline( String[] srcFiles, File[] baseDirs ) + { + Vector targets = new Vector(); + if( targetFilePos != null ) + { + Hashtable addedFiles = new Hashtable(); + for( int i = 0; i < srcFiles.length; i++ ) + { + String[] subTargets = mapper.mapFileName( srcFiles[i] ); + if( subTargets != null ) + { + for( int j = 0; j < subTargets.length; j++ ) + { + String name = null; + if( !relative ) + { + name = + ( new File( destDir, subTargets[j] ) ).getAbsolutePath(); + } + else + { + name = subTargets[j]; + } + if( !addedFiles.contains( name ) ) + { + targets.addElement( name ); + addedFiles.put( name, name ); + } + } + } + } + } + String[] targetFiles = new String[targets.size()]; + targets.copyInto( targetFiles ); + + String[] orig = cmdl.getCommandline(); + String[] result = new String[orig.length + srcFiles.length + targetFiles.length]; + + int srcIndex = orig.length; + if( srcFilePos != null ) + { + srcIndex = srcFilePos.getPosition(); + } + + if( targetFilePos != null ) + { + int targetIndex = targetFilePos.getPosition(); + + if( srcIndex < targetIndex + || ( srcIndex == targetIndex && srcIsFirst ) ) + { + + // 0 --> srcIndex + System.arraycopy( orig, 0, result, 0, srcIndex ); + + // srcIndex --> targetIndex + System.arraycopy( orig, srcIndex, result, + srcIndex + srcFiles.length, + targetIndex - srcIndex ); + + // targets are already absolute file names + System.arraycopy( targetFiles, 0, result, + targetIndex + srcFiles.length, + targetFiles.length ); + + // targetIndex --> end + System.arraycopy( orig, targetIndex, result, + targetIndex + srcFiles.length + targetFiles.length, + orig.length - targetIndex ); + } + else + { + // 0 --> targetIndex + System.arraycopy( orig, 0, result, 0, targetIndex ); + + // targets are already absolute file names + System.arraycopy( targetFiles, 0, result, + targetIndex, + targetFiles.length ); + + // targetIndex --> srcIndex + System.arraycopy( orig, targetIndex, result, + targetIndex + targetFiles.length, + srcIndex - targetIndex ); + + // srcIndex --> end + System.arraycopy( orig, srcIndex, result, + srcIndex + srcFiles.length + targetFiles.length, + orig.length - srcIndex ); + srcIndex += targetFiles.length; + } + + } + else + {// no targetFilePos + + // 0 --> srcIndex + System.arraycopy( orig, 0, result, 0, srcIndex ); + // srcIndex --> end + System.arraycopy( orig, srcIndex, result, + srcIndex + srcFiles.length, + orig.length - srcIndex ); + + } + + // fill in source file names + for( int i = 0; i < srcFiles.length; i++ ) + { + if( !relative ) + { + result[srcIndex + i] = + ( new File( baseDirs[i], srcFiles[i] ) ).getAbsolutePath(); + } + else + { + result[srcIndex + i] = srcFiles[i]; + } + } + return result; + } + + /** + * Construct the command line for serial execution. + * + * @param srcFile The filename to add to the commandline + * @param baseDir filename is relative to this dir + * @return The Commandline value + */ + protected String[] getCommandline( String srcFile, File baseDir ) + { + return getCommandline( new String[]{srcFile}, new File[]{baseDir} ); + } + + /** + * Return the list of Directories from this DirectoryScanner that should be + * included on the command line. + * + * @param baseDir Description of Parameter + * @param ds Description of Parameter + * @return The Dirs value + */ + protected String[] getDirs( File baseDir, DirectoryScanner ds ) + { + if( mapper != null ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + return sfs.restrict( ds.getIncludedDirectories(), baseDir, destDir, + mapper ); + } + else + { + return ds.getIncludedDirectories(); + } + } + + /** + * Return the list of files from this DirectoryScanner that should be + * included on the command line. + * + * @param baseDir Description of Parameter + * @param ds Description of Parameter + * @return The Files value + */ + protected String[] getFiles( File baseDir, DirectoryScanner ds ) + { + if( mapper != null ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + return sfs.restrict( ds.getIncludedFiles(), baseDir, destDir, + mapper ); + } + else + { + return ds.getIncludedFiles(); + } + } + + protected void checkConfiguration() + { + if( "execon".equals( taskName ) ) + { + log( "!! execon is deprecated. Use apply instead. !!" ); + } + + super.checkConfiguration(); + if( filesets.size() == 0 ) + { + throw new BuildException( "no filesets specified", location ); + } + + if( targetFilePos != null || mapperElement != null + || destDir != null ) + { + + if( mapperElement == null ) + { + throw new BuildException( "no mapper specified", location ); + } + if( mapperElement == null ) + { + throw new BuildException( "no dest attribute specified", + location ); + } + mapper = mapperElement.getImplementation(); + } + } + + protected void runExec( Execute exe ) + throws BuildException + { + try + { + + Vector fileNames = new Vector(); + Vector baseDirs = new Vector(); + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + File base = fs.getDir( project ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + + if( !"dir".equals( type ) ) + { + String[] s = getFiles( base, ds ); + for( int j = 0; j < s.length; j++ ) + { + fileNames.addElement( s[j] ); + baseDirs.addElement( base ); + } + } + + if( !"file".equals( type ) ) + { + String[] s = getDirs( base, ds ); + ; + for( int j = 0; j < s.length; j++ ) + { + fileNames.addElement( s[j] ); + baseDirs.addElement( base ); + } + } + + if( fileNames.size() == 0 && skipEmpty ) + { + log( "Skipping fileset for directory " + + base + ". It is empty.", Project.MSG_INFO ); + continue; + } + + if( !parallel ) + { + String[] s = new String[fileNames.size()]; + fileNames.copyInto( s ); + for( int j = 0; j < s.length; j++ ) + { + String[] command = getCommandline( s[j], base ); + log( "Executing " + Commandline.toString( command ), + Project.MSG_VERBOSE ); + exe.setCommandline( command ); + runExecute( exe ); + } + fileNames.removeAllElements(); + baseDirs.removeAllElements(); + } + } + + if( parallel && ( fileNames.size() > 0 || !skipEmpty ) ) + { + String[] s = new String[fileNames.size()]; + fileNames.copyInto( s ); + File[] b = new File[baseDirs.size()]; + baseDirs.copyInto( b ); + String[] command = getCommandline( s, b ); + log( "Executing " + Commandline.toString( command ), + Project.MSG_VERBOSE ); + exe.setCommandline( command ); + runExecute( exe ); + } + + } + catch( IOException e ) + { + throw new BuildException( "Execute failed: " + e, e, location ); + } + finally + { + // close the output file if required + logFlush(); + } + } + + /** + * Enumerated attribute with the values "file", "dir" and "both" for the + * type attribute. + * + * @author RT + */ + public static class FileDirBoth extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"file", "dir", "both"}; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java new file mode 100644 index 000000000..f9f64cca6 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteStreamHandler.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Used by Execute to handle input and output stream of + * subprocesses. + * + * @author thomas.haas@softwired-inc.com + */ +public interface ExecuteStreamHandler +{ + + /** + * Install a handler for the input stream of the subprocess. + * + * @param os output stream to write to the standard input stream of the + * subprocess + * @exception IOException Description of Exception + */ + void setProcessInputStream( OutputStream os ) + throws IOException; + + /** + * Install a handler for the error stream of the subprocess. + * + * @param is input stream to read from the error stream from the subprocess + * @exception IOException Description of Exception + */ + void setProcessErrorStream( InputStream is ) + throws IOException; + + /** + * Install a handler for the output stream of the subprocess. + * + * @param is input stream to read from the error stream from the subprocess + * @exception IOException Description of Exception + */ + void setProcessOutputStream( InputStream is ) + throws IOException; + + /** + * Start handling of the streams. + * + * @exception IOException Description of Exception + */ + void start() + throws IOException; + + /** + * Stop handling of the streams - will not be restarted. + */ + void stop(); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java new file mode 100644 index 000000000..318e622aa --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ExecuteWatchdog.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; + +/** + * Destroys a process running for too long. For example:

              + * ExecuteWatchdog watchdog = new ExecuteWatchdog(30000);
              + * Execute exec = new Execute(myloghandler, watchdog);
              + * exec.setCommandLine(mycmdline);
              + * int exitvalue = exec.execute();
              + * if (exitvalue != SUCCESS && watchdog.killedProcess()){
              + *              // it was killed on purpose by the watchdog
              + * }
              + * 
              + * + * @author thomas.haas@softwired-inc.com + * @author Stephane Bailliez + * @see Execute + */ +public class ExecuteWatchdog implements Runnable +{ + + /** + * say whether or not the watchog is currently monitoring a process + */ + private boolean watch = false; + + /** + * exception that might be thrown during the process execution + */ + private Exception caught = null; + + /** + * say whether or not the process was killed due to running overtime + */ + private boolean killedProcess = false; + + /** + * the process to execute and watch for duration + */ + private Process process; + + /** + * timeout duration. Once the process running time exceeds this it should be + * killed + */ + private int timeout; + + /** + * Creates a new watchdog with a given timeout. + * + * @param timeout the timeout for the process in milliseconds. It must be + * greather than 0. + */ + public ExecuteWatchdog( int timeout ) + { + if( timeout < 1 ) + { + throw new IllegalArgumentException( "timeout lesser than 1." ); + } + this.timeout = timeout; + } + + /** + * Indicates whether or not the watchdog is still monitoring the process. + * + * @return true if the process is still running, otherwise + * false . + */ + public boolean isWatching() + { + return watch; + } + + /** + * This method will rethrow the exception that was possibly caught during + * the run of the process. It will only remains valid once the process has + * been terminated either by 'error', timeout or manual intervention. + * Information will be discarded once a new process is ran. + * + * @throws BuildException a wrapped exception over the one that was silently + * swallowed and stored during the process run. + */ + public void checkException() + throws BuildException + { + if( caught != null ) + { + throw new BuildException( "Exception in ExecuteWatchdog.run: " + + caught.getMessage(), caught ); + } + } + + /** + * Indicates whether the last process run was killed on timeout or not. + * + * @return true if the process was killed otherwise false + * . + */ + public boolean killedProcess() + { + return killedProcess; + } + + + /** + * Watches the process and terminates it, if it runs for to long. + */ + public synchronized void run() + { + try + { + // This isn't a Task, don't have a Project object to log. + // project.log("ExecuteWatchdog: timeout = "+timeout+" msec", Project.MSG_VERBOSE); + final long until = System.currentTimeMillis() + timeout; + long now; + while( watch && until > ( now = System.currentTimeMillis() ) ) + { + try + { + wait( until - now ); + } + catch( InterruptedException e ) + {} + } + + // if we are here, either someone stopped the watchdog, + // we are on timeout and the process must be killed, or + // we are on timeout and the process has already stopped. + try + { + // We must check if the process was not stopped + // before being here + process.exitValue(); + } + catch( IllegalThreadStateException e ) + { + // the process is not terminated, if this is really + // a timeout and not a manual stop then kill it. + if( watch ) + { + killedProcess = true; + process.destroy(); + } + } + } + catch( Exception e ) + { + caught = e; + } + finally + { + cleanUp(); + } + } + + /** + * Watches the given process and terminates it, if it runs for too long. All + * information from the previous run are reset. + * + * @param process the process to monitor. It cannot be null + * @throws IllegalStateException thrown if a process is still being + * monitored. + */ + public synchronized void start( Process process ) + { + if( process == null ) + { + throw new NullPointerException( "process is null." ); + } + if( this.process != null ) + { + throw new IllegalStateException( "Already running." ); + } + this.caught = null; + this.killedProcess = false; + this.watch = true; + this.process = process; + final Thread thread = new Thread( this, "WATCHDOG" ); + thread.setDaemon( true ); + thread.start(); + } + + /** + * Stops the watcher. It will notify all threads possibly waiting on this + * object. + */ + public synchronized void stop() + { + watch = false; + notifyAll(); + } + + /** + * reset the monitor flag and the process. + */ + protected void cleanUp() + { + watch = false; + process = null; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exit.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exit.java new file mode 100644 index 000000000..60ae24009 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Exit.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; + +/** + * Just exit the active build, giving an additional message if available. + * + * @author Nico Seessle + */ +public class Exit extends Task +{ + private String ifCondition, unlessCondition; + private String message; + + public void setIf( String c ) + { + ifCondition = c; + } + + public void setMessage( String value ) + { + this.message = value; + } + + public void setUnless( String c ) + { + unlessCondition = c; + } + + /** + * Set a multiline message. + * + * @param msg The feature to be added to the Text attribute + */ + public void addText( String msg ) + { + message += project.replaceProperties( msg ); + } + + public void execute() + throws BuildException + { + if( testIfCondition() && testUnlessCondition() ) + { + if( message != null && message.length() > 0 ) + { + throw new BuildException( message ); + } + else + { + throw new BuildException( "No message" ); + } + } + } + + private boolean testIfCondition() + { + if( ifCondition == null || "".equals( ifCondition ) ) + { + return true; + } + + return project.getProperty( ifCondition ) != null; + } + + private boolean testUnlessCondition() + { + if( unlessCondition == null || "".equals( unlessCondition ) ) + { + return true; + } + return project.getProperty( unlessCondition ) == null; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Expand.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Expand.java new file mode 100644 index 000000000..38ebc9404 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Expand.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; +import org.apache.tools.ant.util.FileUtils; + +/** + * Unzip a file. + * + * @author costin@dnt.ro + * @author Stefan Bodewig + * @author Magesh Umasankar + */ +public class Expand extends MatchingTask +{// req + private boolean overwrite = true; + private Vector patternsets = new Vector(); + private Vector filesets = new Vector(); + private File dest;//req + private File source; + + /** + * Set the destination directory. File will be unzipped into the destination + * directory. + * + * @param d Path to the directory. + */ + public void setDest( File d ) + { + this.dest = d; + } + + /** + * Should we overwrite files in dest, even if they are newer than the + * corresponding entries in the archive? + * + * @param b The new Overwrite value + */ + public void setOverwrite( boolean b ) + { + overwrite = b; + } + + /** + * Set the path to zip-file. + * + * @param s Path to zip-file. + */ + public void setSrc( File s ) + { + this.source = s; + } + + /** + * Add a fileset + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Add a patternset + * + * @param set The feature to be added to the Patternset attribute + */ + public void addPatternset( PatternSet set ) + { + patternsets.addElement( set ); + } + + /** + * Do the work. + * + * @exception BuildException Thrown in unrecoverable error. + */ + public void execute() + throws BuildException + { + if( "expand".equals( taskType ) ) + { + log( "!! expand is deprecated. Use unzip instead. !!" ); + } + + if( source == null && filesets.size() == 0 ) + { + throw new BuildException( "src attribute and/or filesets must be specified" ); + } + + if( dest == null ) + { + throw new BuildException( + "Dest attribute must be specified" ); + } + + if( dest.exists() && !dest.isDirectory() ) + { + throw new BuildException( "Dest must be a directory.", location ); + } + + FileUtils fileUtils = FileUtils.newFileUtils(); + + if( source != null ) + { + if( source.isDirectory() ) + { + throw new BuildException( "Src must not be a directory." + + " Use nested filesets instead.", location ); + } + else + { + expandFile( fileUtils, source, dest ); + } + } + if( filesets.size() > 0 ) + { + for( int j = 0; j < filesets.size(); j++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( j ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File fromDir = fs.getDir( project ); + + String[] files = ds.getIncludedFiles(); + for( int i = 0; i < files.length; ++i ) + { + File file = new File( fromDir, files[i] ); + expandFile( fileUtils, file, dest ); + } + } + } + } + + /* + * This method is to be overridden by extending unarchival tasks. + */ + protected void expandFile( FileUtils fileUtils, File srcF, File dir ) + { + ZipInputStream zis = null; + try + { + // code from WarExpand + zis = new ZipInputStream( new FileInputStream( srcF ) ); + ZipEntry ze = null; + + while( ( ze = zis.getNextEntry() ) != null ) + { + extractFile( fileUtils, srcF, dir, zis, + ze.getName(), + new Date( ze.getTime() ), + ze.isDirectory() ); + } + + log( "expand complete", Project.MSG_VERBOSE ); + } + catch( IOException ioe ) + { + throw new BuildException( "Error while expanding " + srcF.getPath(), ioe ); + } + finally + { + if( zis != null ) + { + try + { + zis.close(); + } + catch( IOException e ) + {} + } + } + } + + protected void extractFile( FileUtils fileUtils, File srcF, File dir, + InputStream compressedInputStream, + String entryName, + Date entryDate, boolean isDirectory ) + throws IOException + { + + if( patternsets != null && patternsets.size() > 0 ) + { + String name = entryName; + boolean included = false; + for( int v = 0; v < patternsets.size(); v++ ) + { + PatternSet p = ( PatternSet )patternsets.elementAt( v ); + String[] incls = p.getIncludePatterns( project ); + if( incls != null ) + { + for( int w = 0; w < incls.length; w++ ) + { + boolean isIncl = DirectoryScanner.match( incls[w], name ); + if( isIncl ) + { + included = true; + break; + } + } + } + String[] excls = p.getExcludePatterns( project ); + if( excls != null ) + { + for( int w = 0; w < excls.length; w++ ) + { + boolean isExcl = DirectoryScanner.match( excls[w], name ); + if( isExcl ) + { + included = false; + break; + } + } + } + } + if( !included ) + { + //Do not process this file + return; + } + } + + File f = fileUtils.resolveFile( dir, entryName ); + try + { + if( !overwrite && f.exists() + && f.lastModified() >= entryDate.getTime() ) + { + log( "Skipping " + f + " as it is up-to-date", + Project.MSG_DEBUG ); + return; + } + + log( "expanding " + entryName + " to " + f, + Project.MSG_VERBOSE ); + // create intermediary directories - sometimes zip don't add them + File dirF = fileUtils.getParentFile( f ); + dirF.mkdirs(); + + if( isDirectory ) + { + f.mkdirs(); + } + else + { + byte[] buffer = new byte[1024]; + int length = 0; + FileOutputStream fos = null; + try + { + fos = new FileOutputStream( f ); + + while( ( length = + compressedInputStream.read( buffer ) ) >= 0 ) + { + fos.write( buffer, 0, length ); + } + + fos.close(); + fos = null; + } + finally + { + if( fos != null ) + { + try + { + fos.close(); + } + catch( IOException e ) + {} + } + } + } + + fileUtils.setFileLastModified( f, entryDate.getTime() ); + } + catch( FileNotFoundException ex ) + { + log( "Unable to expand to file " + f.getPath(), Project.MSG_WARN ); + } + + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Filter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Filter.java new file mode 100644 index 000000000..6970d0ab4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Filter.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * This task sets a token filter that is used by the file copy methods of the + * project to do token substitution, or sets mutiple tokens by reading these + * from a file. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Gero Vermaas gero@xs4all.nl + * @author Michael McCallum + */ +public class Filter extends Task +{ + private File filtersFile; + + private String token; + private String value; + + public void setFiltersfile( File filtersFile ) + { + this.filtersFile = filtersFile; + } + + public void setToken( String token ) + { + this.token = token; + } + + public void setValue( String value ) + { + this.value = value; + } + + public void execute() + throws BuildException + { + boolean isFiltersFromFile = filtersFile != null && token == null && value == null; + boolean isSingleFilter = filtersFile == null && token != null && value != null; + + if( !isFiltersFromFile && !isSingleFilter ) + { + throw new BuildException( "both token and value parameters, or only a filtersFile parameter is required", location ); + } + + if( isSingleFilter ) + { + project.getGlobalFilterSet().addFilter( token, value ); + } + + if( isFiltersFromFile ) + { + readFilters(); + } + } + + protected void readFilters() + throws BuildException + { + log( "Reading filters from " + filtersFile, Project.MSG_VERBOSE ); + project.getGlobalFilterSet().readFiltersFromFile( filtersFile ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/FixCRLF.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/FixCRLF.java new file mode 100644 index 000000000..2cd21df0a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/FixCRLF.java @@ -0,0 +1,1159 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Enumeration; +import java.util.NoSuchElementException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.util.FileUtils; + +/** + * Task to convert text source files to local OS formatting conventions, as well + * as repair text files damaged by misconfigured or misguided editors or file + * transfer programs.

              + * + * This task can take the following arguments: + *

                + *
              • srcdir + *
              • destdir + *
              • include + *
              • exclude + *
              • cr + *
              • eol + *
              • tab + *
              • eof + *
              • encoding + *
              + * Of these arguments, only sourcedir is required.

              + * + * When this task executes, it will scan the srcdir based on the include and + * exclude properties.

              + * + * This version generalises the handling of EOL characters, and allows for + * CR-only line endings (which I suspect is the standard on Macs.) Tab handling + * has also been generalised to accommodate any tabwidth from 2 to 80, + * inclusive. Importantly, it will leave untouched any literal TAB characters + * embedded within string or character constants.

              + * + * Warning: do not run on binary files. Caution: run with care + * on carefully formatted files. This may sound obvious, but if you don't + * specify asis, presume that your files are going to be modified. If "tabs" is + * "add" or "remove", whitespace characters may be added or removed as + * necessary. Similarly, for CR's - in fact "eol"="crlf" or cr="add" can result + * in cr characters being removed in one special case accommodated, i.e., CRCRLF + * is regarded as a single EOL to handle cases where other programs have + * converted CRLF into CRCRLF. + * + * @author Sam Ruby rubys@us.ibm.com + * @author Peter B. West + * @version $Revision$ $Name$ + */ + +public class FixCRLF extends MatchingTask +{ + + private final static int UNDEF = -1; + private final static int NOTJAVA = 0; + private final static int LOOKING = 1; + private final static int IN_CHAR_CONST = 2; + private final static int IN_STR_CONST = 3; + private final static int IN_SINGLE_COMMENT = 4; + private final static int IN_MULTI_COMMENT = 5; + + private final static int ASIS = 0; + private final static int CR = 1; + private final static int LF = 2; + private final static int CRLF = 3; + private final static int ADD = 1; + private final static int REMOVE = -1; + private final static int SPACES = -1; + private final static int TABS = 1; + + private final static int INBUFLEN = 8192; + private final static int LINEBUFLEN = 200; + + private final static char CTRLZ = '\u001A'; + + private int tablength = 8; + private String spaces = " "; + private StringBuffer linebuf = new StringBuffer( 1024 ); + private StringBuffer linebuf2 = new StringBuffer( 1024 ); + private boolean javafiles = false; + private File destDir = null; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + + /** + * Encoding to assume for the files + */ + private String encoding = null; + private int ctrlz; + private int eol; + private String eolstr; + + private File srcDir; + private int tabs; + + /** + * Defaults the properties based on the system type. + *

                + *
              • Unix: eol="LF" tab="asis" eof="remove" + *
              • Mac: eol="CR" tab="asis" eof="remove" + *
              • DOS: eol="CRLF" tab="asis" eof="asis" + *
              + * + */ + public FixCRLF() + { + tabs = ASIS; + if( System.getProperty( "path.separator" ).equals( ":" ) ) + { + ctrlz = REMOVE; + if( System.getProperty( "os.name" ).indexOf( "Mac" ) > -1 ) + { + eol = CR; + eolstr = "\r"; + } + else + { + eol = LF; + eolstr = "\n"; + } + } + else + { + ctrlz = ASIS; + eol = CRLF; + eolstr = "\r\n"; + } + } + + /** + * Specify how carriage return (CR) characters are to be handled + * + * @param attr The new Cr value + * @deprecated use {@link #setEol setEol} instead. + */ + public void setCr( AddAsisRemove attr ) + { + log( "DEPRECATED: The cr attribute has been deprecated,", + Project.MSG_WARN ); + log( "Please us the eol attribute instead", Project.MSG_WARN ); + String option = attr.getValue(); + CrLf c = new CrLf(); + if( option.equals( "remove" ) ) + { + c.setValue( "lf" ); + } + else if( option.equals( "asis" ) ) + { + c.setValue( "asis" ); + } + else + { + // must be "add" + c.setValue( "crlf" ); + } + setEol( c ); + } + + /** + * Set the destination where the fixed files should be placed. Default is to + * replace the original file. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Specifies the encoding Ant expects the files to be in - defaults to the + * platforms default encoding. + * + * @param encoding The new Encoding value + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Specify how DOS EOF (control-z) charaters are to be handled + * + * @param attr The new Eof value + */ + public void setEof( AddAsisRemove attr ) + { + String option = attr.getValue(); + if( option.equals( "remove" ) ) + { + ctrlz = REMOVE; + } + else if( option.equals( "asis" ) ) + { + ctrlz = ASIS; + } + else + { + // must be "add" + ctrlz = ADD; + } + } + + + /** + * Specify how EndOfLine characters are to be handled + * + * @param attr The new Eol value + */ + public void setEol( CrLf attr ) + { + String option = attr.getValue(); + if( option.equals( "asis" ) ) + { + eol = ASIS; + } + else if( option.equals( "cr" ) ) + { + eol = CR; + eolstr = "\r"; + } + else if( option.equals( "lf" ) ) + { + eol = LF; + eolstr = "\n"; + } + else + { + // Must be "crlf" + eol = CRLF; + eolstr = "\r\n"; + } + } + + /** + * Fixing Java source files? + * + * @param javafiles The new Javafiles value + */ + public void setJavafiles( boolean javafiles ) + { + this.javafiles = javafiles; + } + + /** + * Set the source dir to find the source text files. + * + * @param srcDir The new Srcdir value + */ + public void setSrcdir( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * Specify how tab characters are to be handled + * + * @param attr The new Tab value + */ + public void setTab( AddAsisRemove attr ) + { + String option = attr.getValue(); + if( option.equals( "remove" ) ) + { + tabs = SPACES; + } + else if( option.equals( "asis" ) ) + { + tabs = ASIS; + } + else + { + // must be "add" + tabs = TABS; + } + } + + /** + * Specify tab length in characters + * + * @param tlength specify the length of tab in spaces, + * @exception BuildException Description of Exception + */ + public void setTablength( int tlength ) + throws BuildException + { + if( tlength < 2 || tlength > 80 ) + { + throw new BuildException( "tablength must be between 2 and 80", + location ); + } + tablength = tlength; + StringBuffer sp = new StringBuffer(); + for( int i = 0; i < tablength; i++ ) + { + sp.append( ' ' ); + } + spaces = sp.toString(); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir and destdir + + if( srcDir == null ) + { + throw new BuildException( "srcdir attribute must be set!" ); + } + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir does not exist!" ); + } + if( !srcDir.isDirectory() ) + { + throw new BuildException( "srcdir is not a directory!" ); + } + if( destDir != null ) + { + if( !destDir.exists() ) + { + throw new BuildException( "destdir does not exist!" ); + } + if( !destDir.isDirectory() ) + { + throw new BuildException( "destdir is not a directory!" ); + } + } + + // log options used + log( "options:" + + " eol=" + + ( eol == ASIS ? "asis" : eol == CR ? "cr" : eol == LF ? "lf" : "crlf" ) + + " tab=" + ( tabs == TABS ? "add" : tabs == ASIS ? "asis" : "remove" ) + + " eof=" + ( ctrlz == ADD ? "add" : ctrlz == ASIS ? "asis" : "remove" ) + + " tablength=" + tablength + + " encoding=" + ( encoding == null ? "default" : encoding ), + Project.MSG_VERBOSE ); + + DirectoryScanner ds = super.getDirectoryScanner( srcDir ); + String[] files = ds.getIncludedFiles(); + + for( int i = 0; i < files.length; i++ ) + { + processFile( files[i] ); + } + } + + /** + * Creates a Reader reading from a given file an taking the user defined + * encoding into account. + * + * @param f Description of Parameter + * @return The Reader value + * @exception IOException Description of Exception + */ + private Reader getReader( File f ) + throws IOException + { + return ( encoding == null ) ? new FileReader( f ) + : new InputStreamReader( new FileInputStream( f ), encoding ); + } + + + /** + * Scan a BufferLine forward from the 'next' pointer for the end of a + * character constant. Set 'lookahead' pointer to the character following + * the terminating quote. + * + * @param bufline Description of Parameter + * @param terminator Description of Parameter + * @exception BuildException Description of Exception + */ + private void endOfCharConst( OneLiner.BufferLine bufline, char terminator ) + throws BuildException + { + int ptr = bufline.getNext(); + int eol = bufline.length(); + char c; + ptr++;// skip past initial quote + while( ptr < eol ) + { + if( ( c = bufline.getChar( ptr++ ) ) == '\\' ) + { + ptr++; + } + else + { + if( c == terminator ) + { + bufline.setLookahead( ptr ); + return; + } + } + }// end of while (ptr < eol) + // Must have fallen through to the end of the line + throw new BuildException( "endOfCharConst: unterminated char constant" ); + } + + /** + * Scan a BufferLine for the next state changing token: the beginning of a + * single or multi-line comment, a character or a string constant. As a + * side-effect, sets the buffer state to the next state, and sets field + * lookahead to the first character of the state-changing token, or to the + * next eol character. + * + * @param bufline Description of Parameter + * @exception BuildException Description of Exception + */ + private void nextStateChange( OneLiner.BufferLine bufline ) + throws BuildException + { + int eol = bufline.length(); + int ptr = bufline.getNext(); + + // Look for next single or double quote, double slash or slash star + while( ptr < eol ) + { + switch ( bufline.getChar( ptr++ ) ) + { + case '\'': + bufline.setState( IN_CHAR_CONST ); + bufline.setLookahead( --ptr ); + return; + case '\"': + bufline.setState( IN_STR_CONST ); + bufline.setLookahead( --ptr ); + return; + case '/': + if( ptr < eol ) + { + if( bufline.getChar( ptr ) == '*' ) + { + bufline.setState( IN_MULTI_COMMENT ); + bufline.setLookahead( --ptr ); + return; + } + else if( bufline.getChar( ptr ) == '/' ) + { + bufline.setState( IN_SINGLE_COMMENT ); + bufline.setLookahead( --ptr ); + return; + } + } + break; + }// end of switch (bufline.getChar(ptr++)) + + }// end of while (ptr < eol) + // Eol is the next token + bufline.setLookahead( ptr ); + } + + + /** + * Process a BufferLine string which is not part of of a string constant. + * The start position of the string is given by the 'next' field. Sets the + * 'next' and 'column' fields in the BufferLine. + * + * @param bufline Description of Parameter + * @param end Description of Parameter + * @param outWriter Description of Parameter + */ + private void notInConstant( OneLiner.BufferLine bufline, int end, + BufferedWriter outWriter ) + { + // N.B. both column and string index are zero-based + // Process a string not part of a constant; + // i.e. convert tabs<->spaces as required + // This is NOT called for ASIS tab handling + int nextTab; + int nextStop; + int tabspaces; + String line = bufline.substring( bufline.getNext(), end ); + int place = 0;// Zero-based + int col = bufline.getColumn();// Zero-based + + // process sequences of white space + // first convert all tabs to spaces + linebuf.setLength( 0 ); + while( ( nextTab = line.indexOf( ( int )'\t', place ) ) >= 0 ) + { + linebuf.append( line.substring( place, nextTab ) );// copy to the TAB + col += nextTab - place; + tabspaces = tablength - ( col % tablength ); + linebuf.append( spaces.substring( 0, tabspaces ) ); + col += tabspaces; + place = nextTab + 1; + }// end of while + linebuf.append( line.substring( place, line.length() ) ); + // if converting to spaces, all finished + String linestring = new String( linebuf.toString() ); + if( tabs == REMOVE ) + { + try + { + outWriter.write( linestring ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + } + else + {// tabs == ADD + int tabCol; + linebuf2.setLength( 0 ); + place = 0; + col = bufline.getColumn(); + int placediff = col - 0; + // for the length of the string, cycle through the tab stop + // positions, checking for a space preceded by at least one + // other space at the tab stop. if so replace the longest possible + // preceding sequence of spaces with a tab. + nextStop = col + ( tablength - col % tablength ); + if( nextStop - col < 2 ) + { + linebuf2.append( linestring.substring( + place, nextStop - placediff ) ); + place = nextStop - placediff; + nextStop += tablength; + } + + for( ; nextStop - placediff <= linestring.length() + ; nextStop += tablength ) + { + for( tabCol = nextStop; + --tabCol - placediff >= place + && linestring.charAt( tabCol - placediff ) == ' ' + ; ) + { + ;// Loop for the side-effects + } + // tabCol is column index of the last non-space character + // before the next tab stop + if( nextStop - tabCol > 2 ) + { + linebuf2.append( linestring.substring( + place, ++tabCol - placediff ) ); + linebuf2.append( '\t' ); + } + else + { + linebuf2.append( linestring.substring( + place, nextStop - placediff ) ); + }// end of else + + place = nextStop - placediff; + }// end of for (nextStop ... ) + + // pick up that last bit, if any + linebuf2.append( linestring.substring( place, linestring.length() ) ); + + try + { + outWriter.write( linebuf2.toString() ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + + }// end of else tabs == ADD + + // Set column position as modified by this method + bufline.setColumn( bufline.getColumn() + linestring.length() ); + bufline.setNext( end ); + + } + + + private void processFile( String file ) + throws BuildException + { + File srcFile = new File( srcDir, file ); + File destD = destDir == null ? srcDir : destDir; + File tmpFile = null; + BufferedWriter outWriter; + OneLiner.BufferLine line; + + // read the contents of the file + OneLiner lines = new OneLiner( srcFile ); + + try + { + // Set up the output Writer + try + { + tmpFile = fileUtils.createTempFile( "fixcrlf", "", destD ); + Writer writer = ( encoding == null ) ? new FileWriter( tmpFile ) + : new OutputStreamWriter( new FileOutputStream( tmpFile ), encoding ); + outWriter = new BufferedWriter( writer ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + while( lines.hasMoreElements() ) + { + // In-line states + int endComment; + + try + { + line = ( OneLiner.BufferLine )lines.nextElement(); + } + catch( NoSuchElementException e ) + { + throw new BuildException( e ); + } + + String lineString = line.getLineString(); + int linelen = line.length(); + + // Note - all of the following processing NOT done for + // tabs ASIS + + if( tabs == ASIS ) + { + // Just copy the body of the line across + try + { + outWriter.write( lineString ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + + } + else + {// (tabs != ASIS) + int ptr; + + while( ( ptr = line.getNext() ) < linelen ) + { + + switch ( lines.getState() ) + { + + case NOTJAVA: + notInConstant( line, line.length(), outWriter ); + break; + case IN_MULTI_COMMENT: + if( ( endComment = + lineString.indexOf( "*/", line.getNext() ) + ) >= 0 ) + { + // End of multiLineComment on this line + endComment += 2;// Include the end token + lines.setState( LOOKING ); + } + else + { + endComment = linelen; + } + + notInConstant( line, endComment, outWriter ); + break; + case IN_SINGLE_COMMENT: + notInConstant( line, line.length(), outWriter ); + lines.setState( LOOKING ); + break; + case IN_CHAR_CONST: + case IN_STR_CONST: + // Got here from LOOKING by finding an opening "\'" + // next points to that quote character. + // Find the end of the constant. Watch out for + // backslashes. Literal tabs are left unchanged, and + // the column is adjusted accordingly. + + int begin = line.getNext(); + char terminator = ( lines.getState() == IN_STR_CONST + ? '\"' + : '\'' ); + endOfCharConst( line, terminator ); + while( line.getNext() < line.getLookahead() ) + { + if( line.getNextCharInc() == '\t' ) + { + line.setColumn( + line.getColumn() + + tablength - + line.getColumn() % tablength ); + } + else + { + line.incColumn(); + } + } + + // Now output the substring + try + { + outWriter.write( line.substring( begin, line.getNext() ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + lines.setState( LOOKING ); + + break; + + case LOOKING: + nextStateChange( line ); + notInConstant( line, line.getLookahead(), outWriter ); + break; + }// end of switch (state) + + }// end of while (line.getNext() < linelen) + + }// end of else (tabs != ASIS) + + try + { + outWriter.write( eolstr ); + } + catch( IOException e ) + { + throw new BuildException( e ); + }// end of try-catch + + }// end of while (lines.hasNext()) + + try + { + // Handle CTRLZ + if( ctrlz == ASIS ) + { + outWriter.write( lines.getEofStr() ); + } + else if( ctrlz == ADD ) + { + outWriter.write( CTRLZ ); + } + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + try + { + outWriter.close(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + File destFile = new File( destD, file ); + + try + { + lines.close(); + lines = null; + } + catch( IOException e ) + { + throw new BuildException( "Unable to close source file " + srcFile ); + } + + if( destFile.exists() ) + { + // Compare the destination with the temp file + log( "destFile exists", Project.MSG_DEBUG ); + if( !fileUtils.contentEquals( destFile, tmpFile ) ) + { + log( destFile + " is being written", Project.MSG_DEBUG ); + if( !destFile.delete() ) + { + throw new BuildException( "Unable to delete " + + destFile ); + } + if( !tmpFile.renameTo( destFile ) ) + { + throw new BuildException( + "Failed to transform " + srcFile + + " to " + destFile + + ". Couldn't rename temporary file: " + + tmpFile ); + } + + } + else + {// destination is equal to temp file + log( destFile + + " is not written, as the contents are identical", + Project.MSG_DEBUG ); + if( !tmpFile.delete() ) + { + throw new BuildException( "Unable to delete " + + tmpFile ); + } + } + } + else + {// destFile does not exist - write the temp file + log( "destFile does not exist", Project.MSG_DEBUG ); + if( !tmpFile.renameTo( destFile ) ) + { + throw new BuildException( + "Failed to transform " + srcFile + + " to " + destFile + + ". Couldn't rename temporary file: " + + tmpFile ); + } + } + + tmpFile = null; + + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + try + { + if( lines != null ) + { + lines.close(); + } + } + catch( IOException io ) + { + log( "Error closing " + srcFile, Project.MSG_ERR ); + }// end of catch + + if( tmpFile != null ) + { + tmpFile.delete(); + } + }// end of finally + } + + /** + * Enumerated attribute with the values "asis", "add" and "remove". + * + * @author RT + */ + public static class AddAsisRemove extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"add", "asis", "remove"}; + } + } + + /** + * Enumerated attribute with the values "asis", "cr", "lf" and "crlf". + * + * @author RT + */ + public static class CrLf extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"asis", "cr", "lf", "crlf"}; + } + } + + + class OneLiner implements Enumeration + { + + private int state = javafiles ? LOOKING : NOTJAVA; + + private StringBuffer eolStr = new StringBuffer( LINEBUFLEN ); + private StringBuffer eofStr = new StringBuffer(); + private StringBuffer line = new StringBuffer(); + private boolean reachedEof = false; + + private BufferedReader reader; + + public OneLiner( File srcFile ) + throws BuildException + { + try + { + reader = new BufferedReader + ( getReader( srcFile ), INBUFLEN ); + nextLine(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + public void setState( int state ) + { + this.state = state; + } + + public String getEofStr() + { + return eofStr.toString(); + } + + public int getState() + { + return state; + } + + public void close() + throws IOException + { + if( reader != null ) + { + reader.close(); + } + } + + public boolean hasMoreElements() + { + return !reachedEof; + } + + public Object nextElement() + throws NoSuchElementException + { + if( !hasMoreElements() ) + { + throw new NoSuchElementException( "OneLiner" ); + } + BufferLine tmpLine = + new BufferLine( line.toString(), eolStr.toString() ); + nextLine(); + return tmpLine; + } + + protected void nextLine() + throws BuildException + { + int ch = -1; + int eolcount = 0; + + eolStr.setLength( 0 ); + line.setLength( 0 ); + + try + { + ch = reader.read(); + while( ch != -1 && ch != '\r' && ch != '\n' ) + { + line.append( ( char )ch ); + ch = reader.read(); + } + + if( ch == -1 && line.length() == 0 ) + { + // Eof has been reached + reachedEof = true; + return; + } + + switch ( ( char )ch ) + { + case '\r': + // Check for \r, \r\n and \r\r\n + // Regard \r\r not followed by \n as two lines + ++eolcount; + eolStr.append( '\r' ); + switch ( ( char )( ch = reader.read() ) ) + { + case '\r': + if( ( char )( ch = reader.read() ) == '\n' ) + { + eolcount += 2; + eolStr.append( "\r\n" ); + } + break; + case '\n': + ++eolcount; + eolStr.append( '\n' ); + break; + }// end of switch ((char)(ch = reader.read())) + break; + case '\n': + ++eolcount; + eolStr.append( '\n' ); + break; + }// end of switch ((char) ch) + + // if at eolcount == 0 and trailing characters of string + // are CTRL-Zs, set eofStr + if( eolcount == 0 ) + { + int i = line.length(); + while( --i >= 0 && line.charAt( i ) == CTRLZ ) + { + // keep searching for the first ^Z + } + if( i < line.length() - 1 ) + { + // Trailing characters are ^Zs + // Construct new line and eofStr + eofStr.append( line.toString().substring( i + 1 ) ); + if( i < 0 ) + { + line.setLength( 0 ); + reachedEof = true; + } + else + { + line.setLength( i + 1 ); + } + } + + }// end of if (eolcount == 0) + + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + class BufferLine + { + private int next = 0; + private int column = 0; + private int lookahead = UNDEF; + private String eolStr; + private String line; + + public BufferLine( String line, String eolStr ) + throws BuildException + { + next = 0; + column = 0; + this.line = line; + this.eolStr = eolStr; + } + + public void setColumn( int col ) + { + column = col; + } + + public void setLookahead( int lookahead ) + { + this.lookahead = lookahead; + } + + public void setNext( int next ) + { + this.next = next; + } + + public void setState( int state ) + { + OneLiner.this.setState( state ); + } + + public char getChar( int i ) + { + return line.charAt( i ); + } + + public int getColumn() + { + return column; + } + + public String getEol() + { + return eolStr; + } + + public int getEolLength() + { + return eolStr.length(); + } + + public String getLineString() + { + return line; + } + + public int getLookahead() + { + return lookahead; + } + + public int getNext() + { + return next; + } + + public char getNextChar() + { + return getChar( next ); + } + + public char getNextCharInc() + { + return getChar( next++ ); + } + + public int getState() + { + return OneLiner.this.getState(); + } + + public int incColumn() + { + return column++; + } + + public int length() + { + return line.length(); + } + + public String substring( int begin ) + { + return line.substring( begin ); + } + + public String substring( int begin, int end ) + { + return line.substring( begin, end ); + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GUnzip.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GUnzip.java new file mode 100644 index 000000000..a4001fba5 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GUnzip.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; +import org.apache.tools.ant.BuildException; + +/** + * Expands a file that has been compressed with the GZIP algorithm. Normally + * used to compress non-compressed archives such as TAR files. + * + * @author Stefan Bodewig + * @author Magesh Umasankar + */ + +public class GUnzip extends Unpack +{ + + private final static String DEFAULT_EXTENSION = ".gz"; + + protected String getDefaultExtension() + { + return DEFAULT_EXTENSION; + } + + protected void extract() + { + if( source.lastModified() > dest.lastModified() ) + { + log( "Expanding " + source.getAbsolutePath() + " to " + + dest.getAbsolutePath() ); + + FileOutputStream out = null; + GZIPInputStream zIn = null; + FileInputStream fis = null; + try + { + out = new FileOutputStream( dest ); + fis = new FileInputStream( source ); + zIn = new GZIPInputStream( fis ); + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + out.write( buffer, 0, count ); + count = zIn.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } + catch( IOException ioe ) + { + String msg = "Problem expanding gzip " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( fis != null ) + { + try + { + fis.close(); + } + catch( IOException ioex ) + {} + } + if( out != null ) + { + try + { + out.close(); + } + catch( IOException ioex ) + {} + } + if( zIn != null ) + { + try + { + zIn.close(); + } + catch( IOException ioex ) + {} + } + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GZip.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GZip.java new file mode 100644 index 000000000..df35b0daf --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GZip.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.GZIPOutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Pack; + +/** + * Compresses a file with the GZIP algorithm. Normally used to compress + * non-compressed archives such as TAR files. + * + * @author James Davidson duncan@x180.com + * @author Jon S. Stevens jon@clearink.com + * @author Magesh Umasankar + */ + +public class GZip extends Pack +{ + protected void pack() + { + GZIPOutputStream zOut = null; + try + { + zOut = new GZIPOutputStream( new FileOutputStream( zipFile ) ); + zipFile( source, zOut ); + } + catch( IOException ioe ) + { + String msg = "Problem creating gzip " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( zOut != null ) + { + try + { + // close up + zOut.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GenerateKey.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GenerateKey.java new file mode 100644 index 000000000..06a0425ea --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/GenerateKey.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; + +/** + * Generates a key. + * + * @author Peter Donald + */ +public class GenerateKey extends Task +{ + + /** + * The alias of signer. + */ + protected String alias; + protected String dname; + protected DistinguishedName expandedDname; + protected String keyalg; + protected String keypass; + protected int keysize; + + /** + * The name of keystore file. + */ + protected String keystore; + + protected String sigalg; + protected String storepass; + protected String storetype; + protected int validity; + protected boolean verbose; + + public void setAlias( final String alias ) + { + this.alias = alias; + } + + public void setDname( final String dname ) + { + if( null != expandedDname ) + { + throw new BuildException( "It is not possible to specify dname both " + + "as attribute and element." ); + } + this.dname = dname; + } + + public void setKeyalg( final String keyalg ) + { + this.keyalg = keyalg; + } + + public void setKeypass( final String keypass ) + { + this.keypass = keypass; + } + + public void setKeysize( final String keysize ) + throws BuildException + { + try + { + this.keysize = Integer.parseInt( keysize ); + } + catch( final NumberFormatException nfe ) + { + throw new BuildException( "KeySize attribute should be a integer" ); + } + } + + public void setKeystore( final String keystore ) + { + this.keystore = keystore; + } + + public void setSigalg( final String sigalg ) + { + this.sigalg = sigalg; + } + + public void setStorepass( final String storepass ) + { + this.storepass = storepass; + } + + public void setStoretype( final String storetype ) + { + this.storetype = storetype; + } + + public void setValidity( final String validity ) + throws BuildException + { + try + { + this.validity = Integer.parseInt( validity ); + } + catch( final NumberFormatException nfe ) + { + throw new BuildException( "Validity attribute should be a integer" ); + } + } + + public void setVerbose( final boolean verbose ) + { + this.verbose = verbose; + } + + public DistinguishedName createDname() + throws BuildException + { + if( null != expandedDname ) + { + throw new BuildException( "DName sub-element can only be specified once." ); + } + if( null != dname ) + { + throw new BuildException( "It is not possible to specify dname both " + + "as attribute and element." ); + } + expandedDname = new DistinguishedName(); + return expandedDname; + } + + public void execute() + throws BuildException + { + if( project.getJavaVersion().equals( Project.JAVA_1_1 ) ) + { + throw new BuildException( "The genkey task is only available on JDK" + + " versions 1.2 or greater" ); + } + + if( null == alias ) + { + throw new BuildException( "alias attribute must be set" ); + } + + if( null == storepass ) + { + throw new BuildException( "storepass attribute must be set" ); + } + + if( null == dname && null == expandedDname ) + { + throw new BuildException( "dname must be set" ); + } + + final StringBuffer sb = new StringBuffer(); + + sb.append( "keytool -genkey " ); + + if( verbose ) + { + sb.append( "-v " ); + } + + sb.append( "-alias \"" ); + sb.append( alias ); + sb.append( "\" " ); + + if( null != dname ) + { + sb.append( "-dname \"" ); + sb.append( dname ); + sb.append( "\" " ); + } + + if( null != expandedDname ) + { + sb.append( "-dname \"" ); + sb.append( expandedDname ); + sb.append( "\" " ); + } + + if( null != keystore ) + { + sb.append( "-keystore \"" ); + sb.append( keystore ); + sb.append( "\" " ); + } + + if( null != storepass ) + { + sb.append( "-storepass \"" ); + sb.append( storepass ); + sb.append( "\" " ); + } + + if( null != storetype ) + { + sb.append( "-storetype \"" ); + sb.append( storetype ); + sb.append( "\" " ); + } + + sb.append( "-keypass \"" ); + if( null != keypass ) + { + sb.append( keypass ); + } + else + { + sb.append( storepass ); + } + sb.append( "\" " ); + + if( null != sigalg ) + { + sb.append( "-sigalg \"" ); + sb.append( sigalg ); + sb.append( "\" " ); + } + + if( null != keyalg ) + { + sb.append( "-keyalg \"" ); + sb.append( keyalg ); + sb.append( "\" " ); + } + + if( 0 < keysize ) + { + sb.append( "-keysize \"" ); + sb.append( keysize ); + sb.append( "\" " ); + } + + if( 0 < validity ) + { + sb.append( "-validity \"" ); + sb.append( validity ); + sb.append( "\" " ); + } + + log( "Generating Key for " + alias ); + final ExecTask cmd = ( ExecTask )project.createTask( "exec" ); + cmd.setCommand( new Commandline( sb.toString() ) ); + cmd.setFailonerror( true ); + cmd.setTaskName( getTaskName() ); + cmd.execute(); + } + + public static class DistinguishedName + { + + private Vector params = new Vector(); + private String name; + private String path; + + public Enumeration getParams() + { + return params.elements(); + } + + public Object createParam() + { + DnameParam param = new DnameParam(); + params.addElement( param ); + + return param; + } + + public String encode( final String string ) + { + int end = string.indexOf( ',' ); + + if( -1 == end ) + return string; + + final StringBuffer sb = new StringBuffer(); + + int start = 0; + + while( -1 != end ) + { + sb.append( string.substring( start, end ) ); + sb.append( "\\," ); + start = end + 1; + end = string.indexOf( ',', start ); + } + + sb.append( string.substring( start ) ); + + return sb.toString(); + } + + public String toString() + { + final int size = params.size(); + final StringBuffer sb = new StringBuffer(); + boolean firstPass = true; + + for( int i = 0; i < size; i++ ) + { + if( !firstPass ) + { + sb.append( " ," ); + } + firstPass = false; + + final DnameParam param = ( DnameParam )params.elementAt( i ); + sb.append( encode( param.getName() ) ); + sb.append( '=' ); + sb.append( encode( param.getValue() ) ); + } + + return sb.toString(); + } + } + + public static class DnameParam + { + private String name; + private String value; + + public void setName( String name ) + { + this.name = name; + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getName() + { + return name; + } + + public String getValue() + { + return value; + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Get.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Get.java new file mode 100644 index 000000000..6c1eb4451 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Get.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.util.Date; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Get a particular file from a URL source. Options include verbose reporting, + * timestamp based fetches and controlling actions on failures. NB: access + * through a firewall only works if the whole Java runtime is correctly + * configured. + * + * @author costin@dnt.ro + * @author gg@grtmail.com (Added Java 1.1 style HTTP basic auth) + */ +public class Get extends Task +{// required + private boolean verbose = false; + private boolean useTimestamp = false;//off by default + private boolean ignoreErrors = false; + private String uname = null; + private String pword = null;// required + private File dest; + private URL source; + + /** + * Where to copy the source file. + * + * @param dest Path to file. + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Don't stop if get fails if set to "true". + * + * @param v if "true" then don't report download errors up to ant + */ + public void setIgnoreErrors( boolean v ) + { + ignoreErrors = v; + } + + /** + * password for the basic auth. + * + * @param p password for authentication + */ + public void setPassword( String p ) + { + this.pword = p; + } + + /** + * Set the URL. + * + * @param u URL for the file. + */ + public void setSrc( URL u ) + { + this.source = u; + } + + /** + * Use timestamps, if set to "true".

              + * + * In this situation, the if-modified-since header is set so that the file + * is only fetched if it is newer than the local file (or there is no local + * file) This flag is only valid on HTTP connections, it is ignored in other + * cases. When the flag is set, the local copy of the downloaded file will + * also have its timestamp set to the remote file time.
              + * Note that remote files of date 1/1/1970 (GMT) are treated as 'no + * timestamp', and web servers often serve files with a timestamp in the + * future by replacing their timestamp with that of the current time. Also, + * inter-computer clock differences can cause no end of grief. + * + * @param v "true" to enable file time fetching + */ + public void setUseTimestamp( boolean v ) + { + if( project.getJavaVersion() != Project.JAVA_1_1 ) + { + useTimestamp = v; + } + } + + + /** + * Username for basic auth. + * + * @param u username for authentication + */ + public void setUsername( String u ) + { + this.uname = u; + } + + /** + * Be verbose, if set to "true". + * + * @param v if "true" then be verbose + */ + public void setVerbose( boolean v ) + { + verbose = v; + } + + + /** + * Does the work. + * + * @exception BuildException Thrown in unrecoverable error. + */ + public void execute() + throws BuildException + { + if( source == null ) + { + throw new BuildException( "src attribute is required", location ); + } + + if( dest == null ) + { + throw new BuildException( "dest attribute is required", location ); + } + + if( dest.exists() && dest.isDirectory() ) + { + throw new BuildException( "The specified destination is a directory", + location ); + } + + if( dest.exists() && !dest.canWrite() ) + { + throw new BuildException( "Can't write to " + dest.getAbsolutePath(), + location ); + } + + try + { + + log( "Getting: " + source ); + + //set the timestamp to the file date. + long timestamp = 0; + + boolean hasTimestamp = false; + if( useTimestamp && dest.exists() ) + { + timestamp = dest.lastModified(); + if( verbose ) + { + Date t = new Date( timestamp ); + log( "local file date : " + t.toString() ); + } + + hasTimestamp = true; + } + + //set up the URL connection + URLConnection connection = source.openConnection(); + //modify the headers + //NB: things like user authentication could go in here too. + if( useTimestamp && hasTimestamp ) + { + connection.setIfModifiedSince( timestamp ); + } + // prepare Java 1.1 style credentials + if( uname != null || pword != null ) + { + String up = uname + ":" + pword; + String encoding; + // check to see if sun's Base64 encoder is available. + try + { + sun.misc.BASE64Encoder encoder = + ( sun.misc.BASE64Encoder )Class.forName( "sun.misc.BASE64Encoder" ).newInstance(); + encoding = encoder.encode( up.getBytes() ); + + } + catch( Exception ex ) + {// sun's base64 encoder isn't available + Base64Converter encoder = new Base64Converter(); + encoding = encoder.encode( up.getBytes() ); + } + connection.setRequestProperty( "Authorization", "Basic " + encoding ); + } + + //connect to the remote site (may take some time) + connection.connect(); + //next test for a 304 result (HTTP only) + if( connection instanceof HttpURLConnection ) + { + HttpURLConnection httpConnection = ( HttpURLConnection )connection; + if( httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED ) + { + //not modified so no file download. just return instead + //and trace out something so the user doesn't think that the + //download happened when it didnt + log( "Not modified - so not downloaded" ); + return; + } + // test for 401 result (HTTP only) + if( httpConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED ) + { + log( "Not authorized - check " + dest + " for details" ); + return; + } + + } + + //REVISIT: at this point even non HTTP connections may support the if-modified-since + //behaviour -we just check the date of the content and skip the write if it is not + //newer. Some protocols (FTP) dont include dates, of course. + + FileOutputStream fos = new FileOutputStream( dest ); + + InputStream is = null; + for( int i = 0; i < 3; i++ ) + { + try + { + is = connection.getInputStream(); + break; + } + catch( IOException ex ) + { + log( "Error opening connection " + ex ); + } + } + if( is == null ) + { + log( "Can't get " + source + " to " + dest ); + if( ignoreErrors ) + return; + throw new BuildException( "Can't get " + source + " to " + dest, + location ); + } + + byte[] buffer = new byte[100 * 1024]; + int length; + + while( ( length = is.read( buffer ) ) >= 0 ) + { + fos.write( buffer, 0, length ); + if( verbose ) + System.out.print( "." ); + } + if( verbose ) + System.out.println(); + fos.close(); + is.close(); + + //if (and only if) the use file time option is set, then the + //saved file now has its timestamp set to that of the downloaded file + if( useTimestamp ) + { + long remoteTimestamp = connection.getLastModified(); + if( verbose ) + { + Date t = new Date( remoteTimestamp ); + log( "last modified = " + t.toString() + + ( ( remoteTimestamp == 0 ) ? " - using current time instead" : "" ) ); + } + if( remoteTimestamp != 0 ) + touchFile( dest, remoteTimestamp ); + } + } + catch( IOException ioe ) + { + log( "Error getting " + source + " to " + dest ); + if( ignoreErrors ) + return; + throw new BuildException( ioe); + } + } + + /** + * set the timestamp of a named file to a specified time. + * + * @param file Description of Parameter + * @param timemillis Description of Parameter + * @return true if it succeeded. False means that this is a java1.1 system + * and that file times can not be set + * @exception BuildException Thrown in unrecoverable error. Likely this + * comes from file access failures. + */ + protected boolean touchFile( File file, long timemillis ) + throws BuildException + { + + if( project.getJavaVersion() != Project.JAVA_1_1 ) + { + Touch touch = ( Touch )project.createTask( "touch" ); + touch.setOwningTarget( target ); + touch.setTaskName( getTaskName() ); + touch.setLocation( getLocation() ); + touch.setFile( file ); + touch.setMillis( timemillis ); + touch.touch(); + return true; + } + else + { + return false; + } + } + + /** + * BASE 64 encoding of a String or an array of bytes. Based on RFC 1421. + * + * @author Unknown + * @author Gautam Guliani + */ + + class Base64Converter + { + + public final char[] alphabet = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0 to 7 + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 8 to 15 + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 16 to 23 + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 24 to 31 + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 32 to 39 + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 40 to 47 + 'w', 'x', 'y', 'z', '0', '1', '2', '3', // 48 to 55 + '4', '5', '6', '7', '8', '9', '+', '/'};// 56 to 63 + + + public String encode( String s ) + { + return encode( s.getBytes() ); + } + + public String encode( byte[] octetString ) + { + int bits24; + int bits6; + + char[] out + = new char[( ( octetString.length - 1 ) / 3 + 1 ) * 4]; + + int outIndex = 0; + int i = 0; + + while( ( i + 3 ) <= octetString.length ) + { + // store the octets + bits24 = ( octetString[i++] & 0xFF ) << 16; + bits24 |= ( octetString[i++] & 0xFF ) << 8; + + bits6 = ( bits24 & 0x00FC0000 ) >> 18; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0003F000 ) >> 12; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x00000FC0 ) >> 6; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0000003F ); + out[outIndex++] = alphabet[bits6]; + } + + if( octetString.length - i == 2 ) + { + // store the octets + bits24 = ( octetString[i] & 0xFF ) << 16; + bits24 |= ( octetString[i + 1] & 0xFF ) << 8; + bits6 = ( bits24 & 0x00FC0000 ) >> 18; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0003F000 ) >> 12; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x00000FC0 ) >> 6; + out[outIndex++] = alphabet[bits6]; + + // padding + out[outIndex++] = '='; + } + else if( octetString.length - i == 1 ) + { + // store the octets + bits24 = ( octetString[i] & 0xFF ) << 16; + bits6 = ( bits24 & 0x00FC0000 ) >> 18; + out[outIndex++] = alphabet[bits6]; + bits6 = ( bits24 & 0x0003F000 ) >> 12; + out[outIndex++] = alphabet[bits6]; + + // padding + out[outIndex++] = '='; + out[outIndex++] = '='; + } + + return new String( out ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Input.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Input.java new file mode 100644 index 000000000..caf080870 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Input.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.*; + + +/** + * Ant task to read input line from console. + * + * @author Ulrich Schmidt + */ +public class Input extends Task +{ + private String validargs = null; + private String message = ""; + private String addproperty = null; + private String input = null; + + /** + * No arg constructor. + */ + public Input() { } + + /** + * Defines the name of a property to be created from input. Behaviour is + * according to property task which means that existing properties cannot be + * overriden. + * + * @param addproperty Name for the property to be created from input + */ + public void setAddproperty( String addproperty ) + { + this.addproperty = addproperty; + } + + /** + * Sets the Message which gets displayed to the user during the build run. + * + * @param message The message to be displayed. + */ + public void setMessage( String message ) + { + this.message = message; + } + + /** + * Sets surrogate input to allow automated testing. + * + * @param testinput The new Testinput value + */ + public void setTestinput( String testinput ) + { + this.input = testinput; + } + + /** + * Defines valid input parameters as comma separated String. If set, input + * task will reject any input not defined as accepted and requires the user + * to reenter it. Validargs are case sensitive. If you want 'a' and 'A' to + * be accepted you need to define both values as accepted arguments. + * + * @param validargs A comma separated String defining valid input args. + */ + public void setValidargs( String validargs ) + { + this.validargs = validargs; + } + + // copied n' pasted from org.apache.tools.ant.taskdefs.Exit + /** + * Set a multiline message. + * + * @param msg The feature to be added to the Text attribute + */ + public void addText( String msg ) + { + message += project.replaceProperties( msg ); + } + + /** + * Actual test method executed by jakarta-ant. + * + * @exception BuildException + */ + public void execute() + throws BuildException + { + Vector accept = null; + if( validargs != null ) + { + accept = new Vector(); + StringTokenizer stok = new StringTokenizer( validargs, ",", false ); + while( stok.hasMoreTokens() ) + { + accept.addElement( stok.nextToken() ); + } + } + log( message, Project.MSG_WARN ); + if( input == null ) + { + try + { + BufferedReader in = new BufferedReader( new InputStreamReader( System.in ) ); + input = in.readLine(); + if( accept != null ) + { + while( !accept.contains( input ) ) + { + log( message, Project.MSG_WARN ); + input = in.readLine(); + } + } + } + catch( IOException e ) + { + throw new BuildException( "Failed to read input from Console.", e ); + } + } + // not quite the original intention of this task but for the sake + // of testing ;-) + else + { + if( accept != null && ( !accept.contains( input ) ) ) + { + throw new BuildException( "Invalid input please reenter." ); + } + } + // adopted from org.apache.tools.ant.taskdefs.Property + if( addproperty != null ) + { + if( project.getProperty( addproperty ) == null ) + { + project.setProperty( addproperty, input ); + } + else + { + log( "Override ignored for " + addproperty, Project.MSG_VERBOSE ); + } + } + } +} + + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jar.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jar.java new file mode 100644 index 000000000..1b2d8a976 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jar.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.*; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.FileScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.zip.ZipOutputStream; + +/** + * Creates a JAR archive. + * + * @author James Davidson duncan@x180.com + */ +public class Jar extends Zip +{ + /** + * The index file name. + */ + private final static String INDEX_NAME = "META-INF/INDEX.LIST"; + + /** + * true if a manifest has been specified in the task + */ + private boolean buildFileManifest = false; + + /** + * jar index is JDK 1.3+ only + */ + private boolean index = false; + private Manifest execManifest; + private Manifest manifest; + + private File manifestFile; + + /** + * constructor + */ + public Jar() + { + super(); + archiveType = "jar"; + emptyBehavior = "create"; + setEncoding( "UTF8" ); + } + + /** + * Set whether or not to create an index list for classes to speed up + * classloading. + * + * @param flag The new Index value + */ + public void setIndex( boolean flag ) + { + index = flag; + } + + /** + * @param jarFile The new Jarfile value + * @deprecated use setFile(File) instead. + */ + public void setJarfile( File jarFile ) + { + log( "DEPRECATED - The jarfile attribute is deprecated. Use file attribute instead." ); + setFile( jarFile ); + } + + public void setManifest( File manifestFile ) + { + if( !manifestFile.exists() ) + { + throw new BuildException( "Manifest file: " + manifestFile + " does not exist.", + getLocation() ); + } + + this.manifestFile = manifestFile; + + Reader r = null; + try + { + r = new FileReader( manifestFile ); + Manifest newManifest = new Manifest( r ); + if( manifest == null ) + { + manifest = Manifest.getDefaultManifest(); + } + manifest.merge( newManifest ); + } + catch( ManifestException e ) + { + log( "Manifest is invalid: " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( "Invalid Manifest: " + manifestFile, e, getLocation() ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read manifest file: " + manifestFile, e ); + } + finally + { + if( r != null ) + { + try + { + r.close(); + } + catch( IOException e ) + { + // do nothing + } + } + } + } + + public void setWhenempty( WhenEmpty we ) + { + log( "JARs are never empty, they contain at least a manifest file", + Project.MSG_WARN ); + } + + public void addConfiguredManifest( Manifest newManifest ) + throws ManifestException + { + if( manifest == null ) + { + manifest = Manifest.getDefaultManifest(); + } + manifest.merge( newManifest ); + buildFileManifest = true; + } + + public void addMetainf( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "META-INF/" ); + super.addFileset( fs ); + } + + /** + * Check whether the archive is up-to-date; + * + * @param scanners list of prepared scanners containing files to archive + * @param zipFile intended archive file (may or may not exist) + * @return true if nothing need be done (may have done something already); + * false if archive creation should proceed + * @exception BuildException if it likes + */ + protected boolean isUpToDate( FileScanner[] scanners, File zipFile ) + throws BuildException + { + // need to handle manifest as a special check + if( buildFileManifest || manifestFile == null ) + { + java.util.zip.ZipFile theZipFile = null; + try + { + theZipFile = new java.util.zip.ZipFile( zipFile ); + java.util.zip.ZipEntry entry = theZipFile.getEntry( "META-INF/MANIFEST.MF" ); + if( entry == null ) + { + log( "Updating jar since the current jar has no manifest", Project.MSG_VERBOSE ); + return false; + } + Manifest currentManifest = new Manifest( new InputStreamReader( theZipFile.getInputStream( entry ) ) ); + if( manifest == null ) + { + manifest = Manifest.getDefaultManifest(); + } + if( !currentManifest.equals( manifest ) ) + { + log( "Updating jar since jar manifest has changed", Project.MSG_VERBOSE ); + return false; + } + } + catch( Exception e ) + { + // any problems and we will rebuild + log( "Updating jar since cannot read current jar manifest: " + e.getClass().getName() + e.getMessage(), + Project.MSG_VERBOSE ); + return false; + } + finally + { + if( theZipFile != null ) + { + try + { + theZipFile.close(); + } + catch( IOException e ) + { + //ignore + } + } + } + } + else if( manifestFile.lastModified() > zipFile.lastModified() ) + { + return false; + } + return super.isUpToDate( scanners, zipFile ); + } + + /** + * Make sure we don't think we already have a MANIFEST next time this task + * gets executed. + */ + protected void cleanUp() + { + super.cleanUp(); + } + + protected boolean createEmptyZip( File zipFile ) + { + // Jar files always contain a manifest and can never be empty + return false; + } + + protected void finalizeZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + if( index ) + { + createIndexList( zOut ); + } + } + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + try + { + execManifest = Manifest.getDefaultManifest(); + + if( manifest != null ) + { + execManifest.merge( manifest ); + } + for( Enumeration e = execManifest.getWarnings(); e.hasMoreElements(); ) + { + log( "Manifest warning: " + ( String )e.nextElement(), Project.MSG_WARN ); + } + + zipDir( null, zOut, "META-INF/" ); + // time to write the manifest + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintWriter writer = new PrintWriter( baos ); + execManifest.write( writer ); + writer.flush(); + + ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() ); + super.zipFile( bais, zOut, "META-INF/MANIFEST.MF", System.currentTimeMillis() ); + super.initZipOutputStream( zOut ); + } + catch( ManifestException e ) + { + log( "Manifest is invalid: " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( "Invalid Manifest", e, getLocation() ); + } + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + // If the file being added is META-INF/MANIFEST.MF, we warn if it's not the + // one specified in the "manifest" attribute - or if it's being added twice, + // meaning the same file is specified by the "manifeset" attribute and in + // a element. + if( vPath.equalsIgnoreCase( "META-INF/MANIFEST.MF" ) ) + { + log( "Warning: selected " + archiveType + " files include a META-INF/MANIFEST.MF which will be ignored " + + "(please use manifest attribute to " + archiveType + " task)", Project.MSG_WARN ); + } + else + { + super.zipFile( file, zOut, vPath ); + } + + } + + protected void zipFile( InputStream is, ZipOutputStream zOut, String vPath, long lastModified ) + throws IOException + { + // If the file being added is META-INF/MANIFEST.MF, we merge it with the + // current manifest + if( vPath.equalsIgnoreCase( "META-INF/MANIFEST.MF" ) ) + { + try + { + zipManifestEntry( is ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read manifest file: ", e ); + } + } + else + { + super.zipFile( is, zOut, vPath, lastModified ); + } + } + + /** + * Create the index list to speed up classloading. This is a JDK 1.3+ + * specific feature and is enabled by default. {@link + * http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#JAR%20Index} + * + * @param zOut the zip stream representing the jar being built. + * @throws IOException thrown if there is an error while creating the index + * and adding it to the zip stream. + */ + private void createIndexList( ZipOutputStream zOut ) + throws IOException + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + // encoding must be UTF8 as specified in the specs. + PrintWriter writer = new PrintWriter( new OutputStreamWriter( baos, "UTF8" ) ); + + // version-info blankline + writer.println( "JarIndex-Version: 1.0" ); + writer.println(); + + // header newline + writer.println( zipFile.getName() ); + + // JarIndex is sorting the directories by ascending order. + // it's painful to do in JDK 1.1 and it has no value but cosmetic + // since it will be read into a hashtable by the classloader. + Enumeration enum = addedDirs.keys(); + while( enum.hasMoreElements() ) + { + String dir = ( String )enum.nextElement(); + + // try to be smart, not to be fooled by a weird directory name + // @fixme do we need to check for directories starting by ./ ? + dir = dir.replace( '\\', '/' ); + int pos = dir.lastIndexOf( '/' ); + if( pos != -1 ) + { + dir = dir.substring( 0, pos ); + } + + // looks like nothing from META-INF should be added + // and the check is not case insensitive. + // see sun.misc.JarIndex + if( dir.startsWith( "META-INF" ) ) + { + continue; + } + // name newline + writer.println( dir ); + } + + writer.flush(); + ByteArrayInputStream bais = new ByteArrayInputStream( baos.toByteArray() ); + super.zipFile( bais, zOut, INDEX_NAME, System.currentTimeMillis() ); + } + + + + /** + * Handle situation when we encounter a manifest file If we haven't been + * given one, we use this one. If we have, we merge the manifest in, + * provided it is a new file and not the old one from the JAR we are + * updating + * + * @param is Description of Parameter + * @exception IOException Description of Exception + */ + private void zipManifestEntry( InputStream is ) + throws IOException + { + try + { + if( execManifest == null ) + { + execManifest = new Manifest( new InputStreamReader( is ) ); + } + else if( isAddingNewFiles() ) + { + execManifest.merge( new Manifest( new InputStreamReader( is ) ) ); + } + } + catch( ManifestException e ) + { + log( "Manifest is invalid: " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( "Invalid Manifest", e, getLocation() ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Java.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Java.java new file mode 100644 index 000000000..060a331c6 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Java.java @@ -0,0 +1,456 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ExitException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * This task acts as a loader for java applications but allows to use the same + * JVM for the called application thus resulting in much faster operation. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Stefan Bodewig + */ +public class Java extends Task +{ + + private CommandlineJava cmdl = new CommandlineJava(); + private boolean fork = false; + private File dir = null; + private PrintStream outStream = null; + private boolean failOnError = false; + private File out; + + /** + * Set the command line arguments for the class. + * + * @param s The new Args value + */ + public void setArgs( String s ) + { + log( "The args attribute is deprecated. " + + "Please use nested arg elements.", + Project.MSG_WARN ); + cmdl.createArgument().setLine( s ); + } + + /** + * Set the class name. + * + * @param s The new Classname value + * @exception BuildException Description of Exception + */ + public void setClassname( String s ) + throws BuildException + { + if( cmdl.getJar() != null ) + { + throw new BuildException( "Cannot use 'jar' and 'classname' attributes in same command" ); + } + cmdl.setClassname( s ); + } + + /** + * Set the classpath to be used for this compilation. + * + * @param s The new Classpath value + */ + public void setClasspath( Path s ) + { + createClasspath().append( s ); + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * The working directory of the process + * + * @param d The new Dir value + */ + public void setDir( File d ) + { + this.dir = d; + } + + /** + * Throw a BuildException if process returns non 0. + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Set the forking flag. + * + * @param s The new Fork value + */ + public void setFork( boolean s ) + { + this.fork = s; + } + + public void setJVMVersion( String value ) + { + cmdl.setVmversion( value ); + } + + /** + * set the jar name... + * + * @param jarfile The new Jar value + * @exception BuildException Description of Exception + */ + public void setJar( File jarfile ) + throws BuildException + { + if( cmdl.getClassname() != null ) + { + throw new BuildException( "Cannot use 'jar' and 'classname' attributes in same command." ); + } + cmdl.setJar( jarfile.getAbsolutePath() ); + } + + /** + * Set the command used to start the VM (only if fork==false). + * + * @param s The new Jvm value + */ + public void setJvm( String s ) + { + cmdl.setVm( s ); + } + + /** + * Set the command line arguments for the JVM. + * + * @param s The new Jvmargs value + */ + public void setJvmargs( String s ) + { + log( "The jvmargs attribute is deprecated. " + + "Please use nested jvmarg elements.", + Project.MSG_WARN ); + cmdl.createVmArgument().setLine( s ); + } + + /** + * -mx or -Xmx depending on VM version + * + * @param max The new Maxmemory value + */ + public void setMaxmemory( String max ) + { + cmdl.setMaxmemory( max ); + } + + /** + * File the output of the process is redirected to. + * + * @param out The new Output value + */ + public void setOutput( File out ) + { + this.out = out; + } + + /** + * Add a nested sysproperty element. + * + * @param sysp The feature to be added to the Sysproperty attribute + */ + public void addSysproperty( Environment.Variable sysp ) + { + cmdl.addSysproperty( sysp ); + } + + /** + * Clear out the arguments to this java task. + */ + public void clearArgs() + { + cmdl.clearJavaArgs(); + } + + /** + * Creates a nested arg element. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createArg() + { + return cmdl.createArgument(); + } + + /** + * Creates a nested classpath element + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + return cmdl.createClasspath( project ).createPath(); + } + + /** + * Creates a nested jvmarg element. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createJvmarg() + { + return cmdl.createVmArgument(); + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + int err = -1; + if( ( err = executeJava() ) != 0 ) + { + if( failOnError ) + { + throw new BuildException( "Java returned: " + err, location ); + } + else + { + log( "Java Result: " + err, Project.MSG_ERR ); + } + } + } + + /** + * Do the execution and return a return code. + * + * @return the return code from the execute java class if it was executed in + * a separate VM (fork = "yes"). + * @exception BuildException Description of Exception + */ + public int executeJava() + throws BuildException + { + String classname = cmdl.getClassname(); + if( classname == null && cmdl.getJar() == null ) + { + throw new BuildException( "Classname must not be null." ); + } + if( !fork && cmdl.getJar() != null ) + { + throw new BuildException( "Cannot execute a jar in non-forked mode. Please set fork='true'. " ); + } + + if( fork ) + { + log( "Forking " + cmdl.toString(), Project.MSG_VERBOSE ); + + return run( cmdl.getCommandline() ); + } + else + { + if( cmdl.getVmCommand().size() > 1 ) + { + log( "JVM args ignored when same JVM is used.", Project.MSG_WARN ); + } + if( dir != null ) + { + log( "Working directory ignored when same JVM is used.", Project.MSG_WARN ); + } + + log( "Running in same VM " + cmdl.getJavaCommand().toString(), + Project.MSG_VERBOSE ); + try + { + run( cmdl ); + return 0; + } + catch( ExitException ex ) + { + return ex.getStatus(); + } + } + } + + protected void handleErrorOutput( String line ) + { + if( outStream != null ) + { + outStream.println( line ); + } + else + { + super.handleErrorOutput( line ); + } + } + + protected void handleOutput( String line ) + { + if( outStream != null ) + { + outStream.println( line ); + } + else + { + super.handleOutput( line ); + } + } + + /** + * Executes the given classname with the given arguments as it was a command + * line application. + * + * @param classname Description of Parameter + * @param args Description of Parameter + * @exception BuildException Description of Exception + */ + protected void run( String classname, Vector args ) + throws BuildException + { + CommandlineJava cmdj = new CommandlineJava(); + cmdj.setClassname( classname ); + for( int i = 0; i < args.size(); i++ ) + { + cmdj.createArgument().setValue( ( String )args.elementAt( i ) ); + } + run( cmdj ); + } + + /** + * Executes the given classname with the given arguments as it was a command + * line application. + * + * @param command Description of Parameter + * @exception BuildException Description of Exception + */ + private void run( CommandlineJava command ) + throws BuildException + { + ExecuteJava exe = new ExecuteJava(); + exe.setJavaCommand( command.getJavaCommand() ); + exe.setClasspath( command.getClasspath() ); + exe.setSystemProperties( command.getSystemProperties() ); + if( out != null ) + { + try + { + outStream = new PrintStream( new FileOutputStream( out ) ); + exe.execute( project ); + } + catch( IOException io ) + { + throw new BuildException( io ); + } + finally + { + if( outStream != null ) + { + outStream.close(); + } + } + } + else + { + exe.execute( project ); + } + } + + /** + * Executes the given classname with the given arguments in a separate VM. + * + * @param command Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int run( String[] command ) + throws BuildException + { + FileOutputStream fos = null; + try + { + Execute exe = null; + if( out == null ) + { + exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ), + null ); + } + else + { + fos = new FileOutputStream( out ); + exe = new Execute( new PumpStreamHandler( fos ), null ); + } + + exe.setAntRun( project ); + + if( dir == null ) + { + dir = project.getBaseDir(); + } + else if( !dir.exists() || !dir.isDirectory() ) + { + throw new BuildException( dir.getAbsolutePath() + " is not a valid directory", + location ); + } + + exe.setWorkingDirectory( dir ); + + exe.setCommandline( command ); + try + { + return exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + catch( IOException io ) + { + throw new BuildException( io ); + } + finally + { + if( fos != null ) + { + try + { + fos.close(); + } + catch( IOException io ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javac.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javac.java new file mode 100644 index 000000000..7793bc4f2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javac.java @@ -0,0 +1,941 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.compilers.CompilerAdapter; +import org.apache.tools.ant.taskdefs.compilers.CompilerAdapterFactory; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.GlobPatternMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Task to compile Java source files. This task can take the following + * arguments: + *

                + *
              • sourcedir + *
              • destdir + *
              • deprecation + *
              • classpath + *
              • bootclasspath + *
              • extdirs + *
              • optimize + *
              • debug + *
              • encoding + *
              • target + *
              • depend + *
              • vebose + *
              • failonerror + *
              • includeantruntime + *
              • includejavaruntime + *
              • source + *
              + * Of these arguments, the sourcedir and destdir are required.

              + * + * When this task executes, it will recursively scan the sourcedir and destdir + * looking for Java source files to compile. This task makes its compile + * decision based on timestamp. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ + +public class Javac extends MatchingTask +{ + + private final static String FAIL_MSG + = "Compile failed, messages should have been provided."; + private boolean debug = false; + private boolean optimize = false; + private boolean deprecation = false; + private boolean depend = false; + private boolean verbose = false; + private boolean includeAntRuntime = true; + private boolean includeJavaRuntime = false; + private String fork = "false"; + private String forkedExecutable = null; + private boolean nowarn = false; + private Vector implementationSpecificArgs = new Vector(); + + protected boolean failOnError = true; + protected File[] compileList = new File[0]; + private Path bootclasspath; + private Path compileClasspath; + private String debugLevel; + private File destDir; + private String encoding; + private Path extdirs; + private String memoryInitialSize; + private String memoryMaximumSize; + + private String source; + + private Path src; + private String target; + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new BootClasspathRef value + */ + public void setBootClasspathRef( Reference r ) + { + createBootclasspath().setRefid( r ); + } + + /** + * Sets the bootclasspath that will be used to compile the classes against. + * + * @param bootclasspath The new Bootclasspath value + */ + public void setBootclasspath( Path bootclasspath ) + { + if( this.bootclasspath == null ) + { + this.bootclasspath = bootclasspath; + } + else + { + this.bootclasspath.append( bootclasspath ); + } + } + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( compileClasspath == null ) + { + compileClasspath = classpath; + } + else + { + compileClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Set the debug flag. + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Set the value of debugLevel. + * + * @param v Value to assign to debugLevel. + */ + public void setDebugLevel( String v ) + { + this.debugLevel = v; + } + + /** + * Set the depend flag. + * + * @param depend The new Depend value + */ + public void setDepend( boolean depend ) + { + this.depend = depend; + } + + /** + * Set the deprecation flag. + * + * @param deprecation The new Deprecation value + */ + public void setDeprecation( boolean deprecation ) + { + this.deprecation = deprecation; + } + + /** + * Set the destination directory into which the Java source files should be + * compiled. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Set the Java source file encoding name. + * + * @param encoding The new Encoding value + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Sets the extension directories that will be used during the compilation. + * + * @param extdirs The new Extdirs value + */ + public void setExtdirs( Path extdirs ) + { + if( this.extdirs == null ) + { + this.extdirs = extdirs; + } + else + { + this.extdirs.append( extdirs ); + } + } + + /** + * Throw a BuildException if compilation fails + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Sets whether to fork the javac compiler. + * + * @param f "true|false|on|off|yes|no" or the name of the javac executable. + */ + public void setFork( String f ) + { + if( f.equalsIgnoreCase( "on" ) + || f.equalsIgnoreCase( "true" ) + || f.equalsIgnoreCase( "yes" ) ) + { + fork = "true"; + forkedExecutable = getSystemJavac(); + } + else if( f.equalsIgnoreCase( "off" ) + || f.equalsIgnoreCase( "false" ) + || f.equalsIgnoreCase( "no" ) ) + { + fork = "false"; + forkedExecutable = null; + } + else + { + fork = "true"; + forkedExecutable = f; + } + } + + /** + * Include ant's own classpath in this task's classpath? + * + * @param include The new Includeantruntime value + */ + public void setIncludeantruntime( boolean include ) + { + includeAntRuntime = include; + } + + /** + * Sets whether or not to include the java runtime libraries to this task's + * classpath. + * + * @param include The new Includejavaruntime value + */ + public void setIncludejavaruntime( boolean include ) + { + includeJavaRuntime = include; + } + + /** + * Set the memoryInitialSize flag. + * + * @param memoryInitialSize The new MemoryInitialSize value + */ + public void setMemoryInitialSize( String memoryInitialSize ) + { + this.memoryInitialSize = memoryInitialSize; + } + + /** + * Set the memoryMaximumSize flag. + * + * @param memoryMaximumSize The new MemoryMaximumSize value + */ + public void setMemoryMaximumSize( String memoryMaximumSize ) + { + this.memoryMaximumSize = memoryMaximumSize; + } + + /** + * Sets whether the -nowarn option should be used. + * + * @param flag The new Nowarn value + */ + public void setNowarn( boolean flag ) + { + this.nowarn = flag; + } + + /** + * Set the optimize flag. + * + * @param optimize The new Optimize value + */ + public void setOptimize( boolean optimize ) + { + this.optimize = optimize; + } + + /** + * Proceed if compilation fails + * + * @param proceed The new Proceed value + */ + public void setProceed( boolean proceed ) + { + failOnError = !proceed; + } + + /** + * Set the value of source. + * + * @param v Value to assign to source. + */ + public void setSource( String v ) + { + this.source = v; + } + + /** + * Set the source dirs to find the source Java files. + * + * @param srcDir The new Srcdir value + */ + public void setSrcdir( Path srcDir ) + { + if( src == null ) + { + src = srcDir; + } + else + { + src.append( srcDir ); + } + } + + /** + * Sets the target VM that the classes will be compiled for. Valid strings + * are "1.1", "1.2", and "1.3". + * + * @param target The new Target value + */ + public void setTarget( String target ) + { + this.target = target; + } + + /** + * Set the verbose flag. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + this.verbose = verbose; + } + + /** + * Gets the bootclasspath that will be used to compile the classes against. + * + * @return The Bootclasspath value + */ + public Path getBootclasspath() + { + return bootclasspath; + } + + /** + * Gets the classpath to be used for this compilation. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return compileClasspath; + } + + /** + * Get the additional implementation specific command line arguments. + * + * @return array of command line arguments, guaranteed to be non-null. + */ + public String[] getCurrentCompilerArgs() + { + Vector args = new Vector(); + for( Enumeration enum = implementationSpecificArgs.elements(); + enum.hasMoreElements(); + ) + { + String[] curr = + ( ( ImplementationSpecificArgument )enum.nextElement() ).getParts(); + for( int i = 0; i < curr.length; i++ ) + { + args.addElement( curr[i] ); + } + } + String[] res = new String[args.size()]; + args.copyInto( res ); + return res; + } + + /** + * Gets the debug flag. + * + * @return The Debug value + */ + public boolean getDebug() + { + return debug; + } + + /** + * Get the value of debugLevel. + * + * @return value of debugLevel. + */ + public String getDebugLevel() + { + return debugLevel; + } + + /** + * Gets the depend flag. + * + * @return The Depend value + */ + public boolean getDepend() + { + return depend; + } + + /** + * Gets the deprecation flag. + * + * @return The Deprecation value + */ + public boolean getDeprecation() + { + return deprecation; + } + + /** + * Gets the destination directory into which the java source files should be + * compiled. + * + * @return The Destdir value + */ + public File getDestdir() + { + return destDir; + } + + /** + * Gets the java source file encoding name. + * + * @return The Encoding value + */ + public String getEncoding() + { + return encoding; + } + + /** + * Gets the extension directories that will be used during the compilation. + * + * @return The Extdirs value + */ + public Path getExtdirs() + { + return extdirs; + } + + /** + * Gets the failonerror flag. + * + * @return The Failonerror value + */ + public boolean getFailonerror() + { + return failOnError; + } + + /** + * Gets the list of files to be compiled. + * + * @return The FileList value + */ + public File[] getFileList() + { + return compileList; + } + + /** + * Gets whether or not the ant classpath is to be included in the task's + * classpath. + * + * @return The Includeantruntime value + */ + public boolean getIncludeantruntime() + { + return includeAntRuntime; + } + + /** + * Gets whether or not the java runtime should be included in this task's + * classpath. + * + * @return The Includejavaruntime value + */ + public boolean getIncludejavaruntime() + { + return includeJavaRuntime; + } + + /** + * The name of the javac executable to use in fork-mode. + * + * @return The JavacExecutable value + */ + public String getJavacExecutable() + { + if( forkedExecutable == null && isForkedJavac() ) + { + forkedExecutable = getSystemJavac(); + } + else if( forkedExecutable != null && !isForkedJavac() ) + { + forkedExecutable = null; + } + return forkedExecutable; + } + + /** + * Gets the memoryInitialSize flag. + * + * @return The MemoryInitialSize value + */ + public String getMemoryInitialSize() + { + return memoryInitialSize; + } + + /** + * Gets the memoryMaximumSize flag. + * + * @return The MemoryMaximumSize value + */ + public String getMemoryMaximumSize() + { + return memoryMaximumSize; + } + + /** + * Should the -nowarn option be used. + * + * @return The Nowarn value + */ + public boolean getNowarn() + { + return nowarn; + } + + /** + * Gets the optimize flag. + * + * @return The Optimize value + */ + public boolean getOptimize() + { + return optimize; + } + + /** + * Get the value of source. + * + * @return value of source. + */ + public String getSource() + { + return source; + } + + /** + * Gets the source dirs to find the source java files. + * + * @return The Srcdir value + */ + public Path getSrcdir() + { + return src; + } + + /** + * Gets the target VM that the classes will be compiled for. + * + * @return The Target value + */ + public String getTarget() + { + return target; + } + + /** + * Gets the verbose flag. + * + * @return The Verbose value + */ + public boolean getVerbose() + { + return verbose; + } + + /** + * Is this a forked invocation of JDK's javac? + * + * @return The ForkedJavac value + */ + public boolean isForkedJavac() + { + return !"false".equals( fork ) || + "extJavac".equals( project.getProperty( "build.compiler" ) ); + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createBootclasspath() + { + if( bootclasspath == null ) + { + bootclasspath = new Path( project ); + } + return bootclasspath.createPath(); + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + return compileClasspath.createPath(); + } + + /** + * Adds an implementation specific command line argument. + * + * @return Description of the Returned Value + */ + public ImplementationSpecificArgument createCompilerArg() + { + ImplementationSpecificArgument arg = + new ImplementationSpecificArgument(); + implementationSpecificArgs.addElement( arg ); + return arg; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createExtdirs() + { + if( extdirs == null ) + { + extdirs = new Path( project ); + } + return extdirs.createPath(); + } + + /** + * Create a nested src element for multiple source path support. + * + * @return a nested src element. + */ + public Path createSrc() + { + if( src == null ) + { + src = new Path( project ); + } + return src.createPath(); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir + + if( src == null ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + String[] list = src.list(); + if( list.length == 0 ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + + if( destDir != null && !destDir.isDirectory() ) + { + throw new BuildException( "destination directory \"" + destDir + "\" does not exist or is not a directory", location ); + } + + // scan source directories and dest directory to build up + // compile lists + resetFileLists(); + for( int i = 0; i < list.length; i++ ) + { + File srcDir = ( File )project.resolveFile( list[i] ); + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir \"" + srcDir.getPath() + "\" does not exist!", location ); + } + + DirectoryScanner ds = this.getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + + scanDir( srcDir, destDir != null ? destDir : srcDir, files ); + } + + // compile the source files + + String compiler = determineCompiler(); + + if( compileList.length > 0 ) + { + + CompilerAdapter adapter = CompilerAdapterFactory.getCompiler( + compiler, this ); + log( "Compiling " + compileList.length + + " source file" + + ( compileList.length == 1 ? "" : "s" ) + + ( destDir != null ? " to " + destDir : "" ) ); + + // now we need to populate the compiler adapter + adapter.setJavac( this ); + + // finally, lets execute the compiler!! + if( !adapter.execute() ) + { + if( failOnError ) + { + throw new BuildException( FAIL_MSG, location ); + } + else + { + log( FAIL_MSG, Project.MSG_ERR ); + } + } + } + } + + protected String getSystemJavac() + { + // This is the most common extension case - exe for windows and OS/2, + // nothing for *nix. + String extension = Os.isFamily( "dos" ) ? ".exe" : ""; + + // Look for java in the java.home/../bin directory. Unfortunately + // on Windows java.home doesn't always refer to the correct location, + // so we need to fall back to assuming java is somewhere on the + // PATH. + java.io.File jExecutable = + new java.io.File( System.getProperty( "java.home" ) + + "/../bin/javac" + extension ); + + if( jExecutable.exists() && !Os.isFamily( "netware" ) ) + { + return jExecutable.getAbsolutePath(); + } + else + { + return "javac"; + } + } + + protected boolean isJdkCompiler( String compiler ) + { + return "modern".equals( compiler ) || + "classic".equals( compiler ) || + "javac1.1".equals( compiler ) || + "javac1.2".equals( compiler ) || + "javac1.3".equals( compiler ) || + "javac1.4".equals( compiler ); + } + + /** + * Recreate src + * + * @return a nested src element. + */ + protected Path recreateSrc() + { + src = null; + return createSrc(); + } + + /** + * Clear the list of files to be compiled and copied.. + */ + protected void resetFileLists() + { + compileList = new File[0]; + } + + /** + * Scans the directory looking for source files to be compiled. The results + * are returned in the class variable compileList + * + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param files Description of Parameter + */ + protected void scanDir( File srcDir, File destDir, String files[] ) + { + GlobPatternMapper m = new GlobPatternMapper(); + m.setFrom( "*.java" ); + m.setTo( "*.class" ); + SourceFileScanner sfs = new SourceFileScanner( this ); + File[] newFiles = sfs.restrictAsFiles( files, srcDir, destDir, m ); + + if( newFiles.length > 0 ) + { + File[] newCompileList = new File[compileList.length + + newFiles.length]; + System.arraycopy( compileList, 0, newCompileList, 0, + compileList.length ); + System.arraycopy( newFiles, 0, newCompileList, + compileList.length, newFiles.length ); + compileList = newCompileList; + } + } + + private String determineCompiler() + { + String compiler = project.getProperty( "build.compiler" ); + + if( !"false".equals( fork ) ) + { + if( compiler != null ) + { + if( isJdkCompiler( compiler ) ) + { + log( "Since fork is true, ignoring build.compiler setting.", + Project.MSG_WARN ); + compiler = "extJavac"; + } + else + { + log( "Since build.compiler setting isn't classic or modern, ignoring fork setting.", Project.MSG_WARN ); + } + } + else + { + compiler = "extJavac"; + } + } + + if( compiler == null ) + { + if( Project.getJavaVersion() != Project.JAVA_1_1 && + Project.getJavaVersion() != Project.JAVA_1_2 ) + { + compiler = "modern"; + } + else + { + compiler = "classic"; + } + } + return compiler; + } + + /** + * Adds an "implementation" attribute to Commandline$Attribute used to + * filter command line attributes based on the current implementation. + * + * @author RT + */ + public class ImplementationSpecificArgument + extends Commandline.Argument + { + + private String impl; + + public void setImplementation( String impl ) + { + this.impl = impl; + } + + public String[] getParts() + { + if( impl == null || impl.equals( determineCompiler() ) ) + { + return super.getParts(); + } + else + { + return new String[0]; + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JavacOutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JavacOutputStream.java new file mode 100644 index 000000000..4d959fbf8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JavacOutputStream.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.tools.ant.Task; + +/** + * Serves as an output stream to Javac. This let's us print messages out to the + * log and detect whether or not Javac had an error while compiling. + * + * @author James Duncan Davidson (duncan@x180.com) + * @deprecated use returnvalue of compile to detect compilation failure. + */ + +class JavacOutputStream extends OutputStream +{ + private boolean errorFlag = false; + private StringBuffer line; + + private Task task; + + /** + * Constructs a new JavacOutputStream with the given task as the output + * source for messages. + * + * @param task Description of Parameter + */ + + JavacOutputStream( Task task ) + { + this.task = task; + line = new StringBuffer(); + } + + /** + * Write a character to the output stream. This method looks to make sure + * that there isn't an error being reported and will flush each line of + * input out to the project's log stream. + * + * @param c Description of Parameter + * @exception IOException Description of Exception + */ + + public void write( int c ) + throws IOException + { + char cc = ( char )c; + if( cc == '\r' || cc == '\n' ) + { + // line feed + if( line.length() > 0 ) + { + processLine(); + } + } + else + { + line.append( cc ); + } + } + + /** + * Returns the error status of the compile. If no errors occured, this + * method will return false, else this method will return true. + * + * @return The ErrorFlag value + */ + + boolean getErrorFlag() + { + return errorFlag; + } + + /** + * Processes a line of input and determines if an error occured. + */ + + private void processLine() + { + String s = line.toString(); + if( s.indexOf( "error" ) > -1 ) + { + errorFlag = true; + } + task.log( s ); + line = new StringBuffer(); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javadoc.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javadoc.java new file mode 100644 index 000000000..e95245de7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Javadoc.java @@ -0,0 +1,1473 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileWriter; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileUtils; + +/** + * This task makes it easy to generate Javadoc documentation for a collection of + * source code.

              + * + * Current known limitations are:

              + * + * + *

                + *
              • patterns must be of the form "xxx.*", every other pattern doesn't + * work. + *
              • the java comment-stripper reader is horribly slow + *
              • there is no control on arguments sanity since they are left to the + * javadoc implementation. + *
              • argument J in javadoc1 is not supported (what is that for anyway?) + * + *
              + *

              + * + * If no doclet is set, then the version and author + * are by default "yes".

              + * + * Note: This task is run on another VM because the Javadoc code calls System.exit() + * which would break Ant functionality. + * + * @author Jon S. Stevens jon@clearink.com + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Patrick Chanezon + * chanezon@netscape.com + * @author Ernst de Haan ernst@jollem.com + * @author Stefan Bodewig + */ + +public class Javadoc extends Task +{ + private static boolean javadoc1 = + ( Project.getJavaVersion() == Project.JAVA_1_1 ); + + private Commandline cmd = new Commandline(); + + private boolean foundJavaFile = false; + private boolean failOnError = false; + private Path sourcePath = null; + private File destDir = null; + private Vector sourceFiles = new Vector(); + private Vector packageNames = new Vector( 5 ); + private Vector excludePackageNames = new Vector( 1 ); + private boolean author = true; + private boolean version = true; + private DocletInfo doclet = null; + private Path classpath = null; + private Path bootclasspath = null; + private String group = null; + private Vector compileList = new Vector( 10 ); + private String packageList = null; + private Vector links = new Vector( 2 ); + private Vector groups = new Vector( 2 ); + private boolean useDefaultExcludes = true; + private Html doctitle = null; + private Html header = null; + private Html footer = null; + private Html bottom = null; + private boolean useExternalFile = false; + private File tmpList = null; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + + public void setAccess( AccessType at ) + { + cmd.createArgument().setValue( "-" + at.getValue() ); + } + + public void setAdditionalparam( String add ) + { + cmd.createArgument().setLine( add ); + } + + public void setAuthor( boolean src ) + { + author = src; + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new BootClasspathRef value + */ + public void setBootClasspathRef( Reference r ) + { + createBootclasspath().setRefid( r ); + } + + public void setBootclasspath( Path src ) + { + if( bootclasspath == null ) + { + bootclasspath = src; + } + else + { + bootclasspath.append( src ); + } + } + + public void setBottom( String src ) + { + Html h = new Html(); + h.addText( src ); + addBottom( h ); + } + + public void setCharset( String src ) + { + this.add12ArgIfNotEmpty( "-charset", src ); + } + + public void setClasspath( Path src ) + { + if( classpath == null ) + { + classpath = src; + } + else + { + classpath.append( src ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + public void setDestdir( File dir ) + { + destDir = dir; + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + public void setDocencoding( String enc ) + { + cmd.createArgument().setValue( "-docencoding" ); + cmd.createArgument().setValue( enc ); + } + + public void setDoclet( String src ) + { + if( doclet == null ) + { + doclet = new DocletInfo(); + } + doclet.setName( src ); + } + + public void setDocletPath( Path src ) + { + if( doclet == null ) + { + doclet = new DocletInfo(); + } + doclet.setPath( src ); + } + + public void setDocletPathRef( Reference r ) + { + if( doclet == null ) + { + doclet = new DocletInfo(); + } + doclet.createPath().setRefid( r ); + } + + public void setDoctitle( String src ) + { + Html h = new Html(); + h.addText( src ); + addDoctitle( h ); + } + + public void setEncoding( String enc ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( enc ); + } + + public void setExcludePackageNames( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String p = tok.nextToken(); + PackageName pn = new PackageName(); + pn.setName( p ); + addExcludePackage( pn ); + } + } + + public void setExtdirs( String src ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-extdirs" ); + cmd.createArgument().setValue( src ); + } + } + + /** + * Should the build process fail if javadoc fails (as indicated by a non + * zero return code)?

              + * + * Default is false.

              + * + * @param b The new Failonerror value + */ + public void setFailonerror( boolean b ) + { + failOnError = b; + } + + public void setFooter( String src ) + { + Html h = new Html(); + h.addText( src ); + addFooter( h ); + } + + public void setGroup( String src ) + { + group = src; + } + + public void setHeader( String src ) + { + Html h = new Html(); + h.addText( src ); + addHeader( h ); + } + + public void setHelpfile( File f ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-helpfile" ); + cmd.createArgument().setFile( f ); + } + } + + public void setLink( String src ) + { + if( !javadoc1 ) + { + createLink().setHref( src ); + } + } + + public void setLinkoffline( String src ) + { + if( !javadoc1 ) + { + LinkArgument le = createLink(); + le.setOffline( true ); + String linkOfflineError = "The linkoffline attribute must include a URL and " + + "a package-list file location separated by a space"; + if( src.trim().length() == 0 ) + { + throw new BuildException( linkOfflineError ); + } + StringTokenizer tok = new StringTokenizer( src, " ", false ); + le.setHref( tok.nextToken() ); + + if( !tok.hasMoreTokens() ) + { + throw new BuildException( linkOfflineError ); + } + le.setPackagelistLoc( project.resolveFile( tok.nextToken() ) ); + } + } + + public void setLocale( String src ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-locale" ); + cmd.createArgument().setValue( src ); + } + } + + public void setMaxmemory( String max ) + { + if( javadoc1 ) + { + cmd.createArgument().setValue( "-J-mx" + max ); + } + else + { + cmd.createArgument().setValue( "-J-Xmx" + max ); + } + } + + public void setNodeprecated( boolean b ) + { + addArgIf( b, "-nodeprecated" ); + } + + public void setNodeprecatedlist( boolean b ) + { + add12ArgIf( b, "-nodeprecatedlist" ); + } + + public void setNohelp( boolean b ) + { + add12ArgIf( b, "-nohelp" ); + } + + public void setNoindex( boolean b ) + { + addArgIf( b, "-noindex" ); + } + + public void setNonavbar( boolean b ) + { + add12ArgIf( b, "-nonavbar" ); + } + + public void setNotree( boolean b ) + { + addArgIf( b, "-notree" ); + } + + public void setOld( boolean b ) + { + add12ArgIf( b, "-1.1" ); + } + + public void setOverview( File f ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-overview" ); + cmd.createArgument().setFile( f ); + } + } + + public void setPackage( boolean b ) + { + addArgIf( b, "-package" ); + } + + public void setPackageList( String src ) + { + packageList = src; + } + + public void setPackagenames( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String p = tok.nextToken(); + PackageName pn = new PackageName(); + pn.setName( p ); + addPackage( pn ); + } + } + + public void setPrivate( boolean b ) + { + addArgIf( b, "-private" ); + } + + public void setProtected( boolean b ) + { + addArgIf( b, "-protected" ); + } + + public void setPublic( boolean b ) + { + addArgIf( b, "-public" ); + } + + public void setSerialwarn( boolean b ) + { + add12ArgIf( b, "-serialwarn" ); + } + + public void setSourcefiles( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String f = tok.nextToken(); + SourceFile sf = new SourceFile(); + sf.setFile( project.resolveFile( f ) ); + addSource( sf ); + } + } + + public void setSourcepath( Path src ) + { + if( sourcePath == null ) + { + sourcePath = src; + } + else + { + sourcePath.append( src ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new SourcepathRef value + */ + public void setSourcepathRef( Reference r ) + { + createSourcepath().setRefid( r ); + } + + public void setSplitindex( boolean b ) + { + add12ArgIf( b, "-splitindex" ); + } + + public void setStylesheetfile( File f ) + { + if( !javadoc1 ) + { + cmd.createArgument().setValue( "-stylesheetfile" ); + cmd.createArgument().setFile( f ); + } + } + + public void setUse( boolean b ) + { + add12ArgIf( b, "-use" ); + } + + /** + * Work around command line length limit by using an external file for the + * sourcefiles. + * + * @param b The new UseExternalFile value + */ + public void setUseExternalFile( boolean b ) + { + if( !javadoc1 ) + { + useExternalFile = b; + } + } + + public void setVerbose( boolean b ) + { + add12ArgIf( b, "-verbose" ); + } + + public void setVersion( boolean src ) + { + version = src; + } + + public void setWindowtitle( String src ) + { + add12ArgIfNotEmpty( "-windowtitle", src ); + } + + public void addBottom( Html text ) + { + if( !javadoc1 ) + { + bottom = text; + } + } + + public void addDoctitle( Html text ) + { + if( !javadoc1 ) + { + doctitle = text; + } + } + + public void addExcludePackage( PackageName pn ) + { + excludePackageNames.addElement( pn ); + } + + public void addFooter( Html text ) + { + if( !javadoc1 ) + { + footer = text; + } + } + + public void addHeader( Html text ) + { + if( !javadoc1 ) + { + header = text; + } + } + + public void addPackage( PackageName pn ) + { + packageNames.addElement( pn ); + } + + public void addSource( SourceFile sf ) + { + sourceFiles.addElement( sf ); + } + + public Path createBootclasspath() + { + if( bootclasspath == null ) + { + bootclasspath = new Path( project ); + } + return bootclasspath.createPath(); + } + + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + public DocletInfo createDoclet() + { + doclet = new DocletInfo(); + return doclet; + } + + public GroupArgument createGroup() + { + GroupArgument ga = new GroupArgument(); + groups.addElement( ga ); + return ga; + } + + public LinkArgument createLink() + { + LinkArgument la = new LinkArgument(); + links.addElement( la ); + return la; + } + + public Path createSourcepath() + { + if( sourcePath == null ) + { + sourcePath = new Path( project ); + } + return sourcePath.createPath(); + } + + public void execute() + throws BuildException + { + if( "javadoc2".equals( taskType ) ) + { + log( "!! javadoc2 is deprecated. Use javadoc instead. !!" ); + } + + if( sourcePath == null ) + { + String msg = "sourcePath attribute must be set!"; + throw new BuildException( msg ); + } + + log( "Generating Javadoc", Project.MSG_INFO ); + + if( doctitle != null ) + { + cmd.createArgument().setValue( "-doctitle" ); + cmd.createArgument().setValue( expand( doctitle.getText() ) ); + } + if( header != null ) + { + cmd.createArgument().setValue( "-header" ); + cmd.createArgument().setValue( expand( header.getText() ) ); + } + if( footer != null ) + { + cmd.createArgument().setValue( "-footer" ); + cmd.createArgument().setValue( expand( footer.getText() ) ); + } + if( bottom != null ) + { + cmd.createArgument().setValue( "-bottom" ); + cmd.createArgument().setValue( expand( bottom.getText() ) ); + } + + Commandline toExecute = ( Commandline )cmd.clone(); + toExecute.setExecutable( getJavadocExecutableName() ); + +// ------------------------------------------------ general javadoc arguments + if( classpath == null ) + classpath = Path.systemClasspath; + else + classpath = classpath.concatSystemClasspath( "ignore" ); + + if( !javadoc1 ) + { + toExecute.createArgument().setValue( "-classpath" ); + toExecute.createArgument().setPath( classpath ); + toExecute.createArgument().setValue( "-sourcepath" ); + toExecute.createArgument().setPath( sourcePath ); + } + else + { + toExecute.createArgument().setValue( "-classpath" ); + toExecute.createArgument().setValue( sourcePath.toString() + + System.getProperty( "path.separator" ) + classpath.toString() ); + } + + if( version && doclet == null ) + toExecute.createArgument().setValue( "-version" ); + if( author && doclet == null ) + toExecute.createArgument().setValue( "-author" ); + + if( javadoc1 || doclet == null ) + { + if( destDir == null ) + { + String msg = "destDir attribute must be set!"; + throw new BuildException( msg ); + } + } + +// --------------------------------- javadoc2 arguments for default doclet + +// XXX: how do we handle a custom doclet? + + if( !javadoc1 ) + { + if( doclet != null ) + { + if( doclet.getName() == null ) + { + throw new BuildException( "The doclet name must be specified.", location ); + } + else + { + toExecute.createArgument().setValue( "-doclet" ); + toExecute.createArgument().setValue( doclet.getName() ); + if( doclet.getPath() != null ) + { + toExecute.createArgument().setValue( "-docletpath" ); + toExecute.createArgument().setPath( doclet.getPath() ); + } + for( Enumeration e = doclet.getParams(); e.hasMoreElements(); ) + { + DocletParam param = ( DocletParam )e.nextElement(); + if( param.getName() == null ) + { + throw new BuildException( "Doclet parameters must have a name" ); + } + + toExecute.createArgument().setValue( param.getName() ); + if( param.getValue() != null ) + { + toExecute.createArgument().setValue( param.getValue() ); + } + } + } + } + if( bootclasspath != null ) + { + toExecute.createArgument().setValue( "-bootclasspath" ); + toExecute.createArgument().setPath( bootclasspath ); + } + + // add the links arguments + if( links.size() != 0 ) + { + for( Enumeration e = links.elements(); e.hasMoreElements(); ) + { + LinkArgument la = ( LinkArgument )e.nextElement(); + + if( la.getHref() == null ) + { + throw new BuildException( "Links must provide the URL to the external class documentation." ); + } + + if( la.isLinkOffline() ) + { + File packageListLocation = la.getPackagelistLoc(); + if( packageListLocation == null ) + { + throw new BuildException( "The package list location for link " + la.getHref() + + " must be provided because the link is offline" ); + } + File packageList = new File( packageListLocation, "package-list" ); + if( packageList.exists() ) + { + toExecute.createArgument().setValue( "-linkoffline" ); + toExecute.createArgument().setValue( la.getHref() ); + toExecute.createArgument().setValue( packageListLocation.getAbsolutePath() ); + } + else + { + log( "Warning: No package list was found at " + packageListLocation, + Project.MSG_VERBOSE ); + } + } + else + { + toExecute.createArgument().setValue( "-link" ); + toExecute.createArgument().setValue( la.getHref() ); + } + } + } + + // add the single group arguments + // Javadoc 1.2 rules: + // Multiple -group args allowed. + // Each arg includes 3 strings: -group [name] [packagelist]. + // Elements in [packagelist] are colon-delimited. + // An element in [packagelist] may end with the * wildcard. + + // Ant javadoc task rules for group attribute: + // Args are comma-delimited. + // Each arg is 2 space-delimited strings. + // E.g., group="XSLT_Packages org.apache.xalan.xslt*,XPath_Packages org.apache.xalan.xpath*" + if( group != null ) + { + StringTokenizer tok = new StringTokenizer( group, ",", false ); + while( tok.hasMoreTokens() ) + { + String grp = tok.nextToken().trim(); + int space = grp.indexOf( " " ); + if( space > 0 ) + { + String name = grp.substring( 0, space ); + String pkgList = grp.substring( space + 1 ); + toExecute.createArgument().setValue( "-group" ); + toExecute.createArgument().setValue( name ); + toExecute.createArgument().setValue( pkgList ); + } + } + } + + // add the group arguments + if( groups.size() != 0 ) + { + for( Enumeration e = groups.elements(); e.hasMoreElements(); ) + { + GroupArgument ga = ( GroupArgument )e.nextElement(); + String title = ga.getTitle(); + String packages = ga.getPackages(); + if( title == null || packages == null ) + { + throw new BuildException( "The title and packages must be specified for group elements." ); + } + toExecute.createArgument().setValue( "-group" ); + toExecute.createArgument().setValue( expand( title ) ); + toExecute.createArgument().setValue( packages ); + } + } + + } + + tmpList = null; + if( packageNames.size() > 0 ) + { + Vector packages = new Vector(); + Enumeration enum = packageNames.elements(); + while( enum.hasMoreElements() ) + { + PackageName pn = ( PackageName )enum.nextElement(); + String name = pn.getName().trim(); + if( name.endsWith( ".*" ) ) + { + packages.addElement( name ); + } + else + { + toExecute.createArgument().setValue( name ); + } + } + + Vector excludePackages = new Vector(); + if( excludePackageNames.size() > 0 ) + { + enum = excludePackageNames.elements(); + while( enum.hasMoreElements() ) + { + PackageName pn = ( PackageName )enum.nextElement(); + excludePackages.addElement( pn.getName().trim() ); + } + } + if( packages.size() > 0 ) + { + evaluatePackages( toExecute, sourcePath, packages, excludePackages ); + } + } + + if( sourceFiles.size() > 0 ) + { + PrintWriter srcListWriter = null; + try + { + + /** + * Write sourcefiles to a temporary file if requested. + */ + if( useExternalFile ) + { + if( tmpList == null ) + { + tmpList = fileUtils.createTempFile( "javadoc", "", null ); + toExecute.createArgument().setValue( "@" + tmpList.getAbsolutePath() ); + } + srcListWriter = new PrintWriter( new FileWriter( tmpList.getAbsolutePath(), + true ) ); + } + + Enumeration enum = sourceFiles.elements(); + while( enum.hasMoreElements() ) + { + SourceFile sf = ( SourceFile )enum.nextElement(); + String sourceFileName = sf.getFile().getAbsolutePath(); + if( useExternalFile ) + { + srcListWriter.println( sourceFileName ); + } + else + { + toExecute.createArgument().setValue( sourceFileName ); + } + } + + } + catch( IOException e ) + { + throw new BuildException( "Error creating temporary file", + e, location ); + } + finally + { + if( srcListWriter != null ) + { + srcListWriter.close(); + } + } + } + + if( packageList != null ) + { + toExecute.createArgument().setValue( "@" + packageList ); + } + log( "Javadoc args: " + toExecute, Project.MSG_VERBOSE ); + + log( "Javadoc execution", Project.MSG_INFO ); + + JavadocOutputStream out = new JavadocOutputStream( Project.MSG_INFO ); + JavadocOutputStream err = new JavadocOutputStream( Project.MSG_WARN ); + Execute exe = new Execute( new PumpStreamHandler( out, err ) ); + exe.setAntRun( project ); + + /* + * No reason to change the working directory as all filenames and + * path components have been resolved already. + * + * Avoid problems with command line length in some environments. + */ + exe.setWorkingDirectory( null ); + try + { + exe.setCommandline( toExecute.getCommandline() ); + int ret = exe.execute(); + if( ret != 0 && failOnError ) + { + throw new BuildException( "Javadoc returned " + ret, location ); + } + } + catch( IOException e ) + { + throw new BuildException( "Javadoc failed: " + e, e, location ); + } + finally + { + + if( tmpList != null ) + { + tmpList.delete(); + tmpList = null; + } + + out.logFlush(); + err.logFlush(); + try + { + out.close(); + err.close(); + } + catch( IOException e ) + {} + } + } + + /** + * Convenience method to expand properties. + * + * @param content Description of Parameter + * @return Description of the Returned Value + */ + protected String expand( String content ) + { + return project.replaceProperties( content ); + } + + private String getJavadocExecutableName() + { + // This is the most common extension case - exe for windows and OS/2, + // nothing for *nix. + String extension = Os.isFamily( "dos" ) ? ".exe" : ""; + + // Look for javadoc in the java.home/../bin directory. Unfortunately + // on Windows java.home doesn't always refer to the correct location, + // so we need to fall back to assuming javadoc is somewhere on the + // PATH. + File jdocExecutable = new File( System.getProperty( "java.home" ) + + "/../bin/javadoc" + extension ); + + if( jdocExecutable.exists() && !Os.isFamily( "netware" ) ) + { + return jdocExecutable.getAbsolutePath(); + } + else + { + if( !Os.isFamily( "netware" ) ) + { + log( "Unable to locate " + jdocExecutable.getAbsolutePath() + + ". Using \"javadoc\" instead.", Project.MSG_VERBOSE ); + } + return "javadoc"; + } + } + + private void add11ArgIf( boolean b, String arg ) + { + if( javadoc1 && b ) + { + cmd.createArgument().setValue( arg ); + } + } + + private void add12ArgIf( boolean b, String arg ) + { + if( !javadoc1 && b ) + { + cmd.createArgument().setValue( arg ); + } + } + + private void add12ArgIfNotEmpty( String key, String value ) + { + if( !javadoc1 ) + { + if( value != null && value.length() != 0 ) + { + cmd.createArgument().setValue( key ); + cmd.createArgument().setValue( value ); + } + else + { + project.log( this, + "Warning: Leaving out empty argument '" + key + "'", + Project.MSG_WARN ); + } + } + } + + + private void addArgIf( boolean b, String arg ) + { + if( b ) + { + cmd.createArgument().setValue( arg ); + } + } + + /** + * Given a source path, a list of package patterns, fill the given list with + * the packages found in that path subdirs matching one of the given + * patterns. + * + * @param toExecute Description of Parameter + * @param sourcePath Description of Parameter + * @param packages Description of Parameter + * @param excludePackages Description of Parameter + */ + private void evaluatePackages( Commandline toExecute, Path sourcePath, + Vector packages, Vector excludePackages ) + { + log( "Source path = " + sourcePath.toString(), Project.MSG_VERBOSE ); + StringBuffer msg = new StringBuffer( "Packages = " ); + for( int i = 0; i < packages.size(); i++ ) + { + if( i > 0 ) + { + msg.append( "," ); + } + msg.append( packages.elementAt( i ) ); + } + log( msg.toString(), Project.MSG_VERBOSE ); + + msg.setLength( 0 ); + msg.append( "Exclude Packages = " ); + for( int i = 0; i < excludePackages.size(); i++ ) + { + if( i > 0 ) + { + msg.append( "," ); + } + msg.append( excludePackages.elementAt( i ) ); + } + log( msg.toString(), Project.MSG_VERBOSE ); + + Vector addedPackages = new Vector(); + + String[] list = sourcePath.list(); + if( list == null ) + list = new String[0]; + + FileSet fs = new FileSet(); + fs.setDefaultexcludes( useDefaultExcludes ); + + Enumeration e = packages.elements(); + while( e.hasMoreElements() ) + { + String pkg = ( String )e.nextElement(); + pkg = pkg.replace( '.', '/' ); + if( pkg.endsWith( "*" ) ) + { + pkg += "*"; + } + + fs.createInclude().setName( pkg ); + }// while + + e = excludePackages.elements(); + while( e.hasMoreElements() ) + { + String pkg = ( String )e.nextElement(); + pkg = pkg.replace( '.', '/' ); + if( pkg.endsWith( "*" ) ) + { + pkg += "*"; + } + + fs.createExclude().setName( pkg ); + } + + PrintWriter packageListWriter = null; + try + { + if( useExternalFile ) + { + tmpList = fileUtils.createTempFile( "javadoc", "", null ); + toExecute.createArgument().setValue( "@" + tmpList.getAbsolutePath() ); + packageListWriter = new PrintWriter( new FileWriter( tmpList ) ); + } + + for( int j = 0; j < list.length; j++ ) + { + File source = project.resolveFile( list[j] ); + fs.setDir( source ); + + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] packageDirs = ds.getIncludedDirectories(); + + for( int i = 0; i < packageDirs.length; i++ ) + { + File pd = new File( source, packageDirs[i] ); + String[] files = pd.list( + new FilenameFilter() + { + public boolean accept( File dir1, String name ) + { + if( name.endsWith( ".java" ) ) + { + return true; + } + return false;// ignore dirs + } + } ); + + if( files.length > 0 ) + { + String pkgDir = packageDirs[i].replace( '/', '.' ).replace( '\\', '.' ); + if( !addedPackages.contains( pkgDir ) ) + { + if( useExternalFile ) + { + packageListWriter.println( pkgDir ); + } + else + { + toExecute.createArgument().setValue( pkgDir ); + } + addedPackages.addElement( pkgDir ); + } + } + } + } + } + catch( IOException ioex ) + { + throw new BuildException( "Error creating temporary file", + ioex, location ); + } + finally + { + if( packageListWriter != null ) + { + packageListWriter.close(); + } + } + } + + public static class AccessType extends EnumeratedAttribute + { + public String[] getValues() + { + // Protected first so if any GUI tool offers a default + // based on enum #0, it will be right. + return new String[]{"protected", "public", "package", "private"}; + } + } + + public static class Html + { + private StringBuffer text = new StringBuffer(); + + public String getText() + { + return text.toString(); + } + + public void addText( String t ) + { + text.append( t ); + } + } + + public static class PackageName + { + private String name; + + public void setName( String name ) + { + this.name = name; + } + + public String getName() + { + return name; + } + + public String toString() + { + return getName(); + } + } + + public static class SourceFile + { + private File file; + + public void setFile( File file ) + { + this.file = file; + } + + public File getFile() + { + return file; + } + } + + public class DocletInfo + { + + private Vector params = new Vector(); + private String name; + private Path path; + + public void setName( String name ) + { + this.name = name; + } + + public void setPath( Path path ) + { + if( this.path == null ) + { + this.path = path; + } + else + { + this.path.append( path ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new PathRef value + */ + public void setPathRef( Reference r ) + { + createPath().setRefid( r ); + } + + public String getName() + { + return name; + } + + public Enumeration getParams() + { + return params.elements(); + } + + public Path getPath() + { + return path; + } + + public DocletParam createParam() + { + DocletParam param = new DocletParam(); + params.addElement( param ); + + return param; + } + + public Path createPath() + { + if( path == null ) + { + path = new Path( getProject() ); + } + return path.createPath(); + } + } + + public class DocletParam + { + private String name; + private String value; + + public void setName( String name ) + { + this.name = name; + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getName() + { + return name; + } + + public String getValue() + { + return value; + } + } + + public class GroupArgument + { + private Vector packages = new Vector( 3 ); + private Html title; + + public GroupArgument() { } + + public void setPackages( String src ) + { + StringTokenizer tok = new StringTokenizer( src, "," ); + while( tok.hasMoreTokens() ) + { + String p = tok.nextToken(); + PackageName pn = new PackageName(); + pn.setName( p ); + addPackage( pn ); + } + } + + public void setTitle( String src ) + { + Html h = new Html(); + h.addText( src ); + addTitle( h ); + } + + public String getPackages() + { + StringBuffer p = new StringBuffer(); + for( int i = 0; i < packages.size(); i++ ) + { + if( i > 0 ) + { + p.append( ":" ); + } + p.append( packages.elementAt( i ).toString() ); + } + return p.toString(); + } + + public String getTitle() + { + return title != null ? title.getText() : null; + } + + public void addPackage( PackageName pn ) + { + packages.addElement( pn ); + } + + public void addTitle( Html text ) + { + title = text; + } + } + + public class LinkArgument + { + private boolean offline = false; + private String href; + private File packagelistLoc; + + public LinkArgument() { } + + public void setHref( String hr ) + { + href = hr; + } + + public void setOffline( boolean offline ) + { + this.offline = offline; + } + + public void setPackagelistLoc( File src ) + { + packagelistLoc = src; + } + + public String getHref() + { + return href; + } + + public File getPackagelistLoc() + { + return packagelistLoc; + } + + public boolean isLinkOffline() + { + return offline; + } + } + + private class JavadocOutputStream extends LogOutputStream + { + + // + // Override the logging of output in order to filter out Generating + // messages. Generating messages are set to a priority of VERBOSE + // unless they appear after what could be an informational message. + // + private String queuedLine = null; + + JavadocOutputStream( int level ) + { + super( Javadoc.this, level ); + } + + + protected void logFlush() + { + if( queuedLine != null ) + { + super.processLine( queuedLine, Project.MSG_VERBOSE ); + queuedLine = null; + } + } + + protected void processLine( String line, int messageLevel ) + { + if( messageLevel == Project.MSG_INFO && line.startsWith( "Generating " ) ) + { + if( queuedLine != null ) + { + super.processLine( queuedLine, Project.MSG_VERBOSE ); + } + queuedLine = line; + } + else + { + if( queuedLine != null ) + { + if( line.startsWith( "Building " ) ) + super.processLine( queuedLine, Project.MSG_VERBOSE ); + else + super.processLine( queuedLine, Project.MSG_INFO ); + queuedLine = null; + } + super.processLine( line, messageLevel ); + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jikes.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jikes.java new file mode 100644 index 000000000..4e8700635 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Jikes.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Random; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Encapsulates a Jikes compiler, by directly executing an external process. + * + * @author skanthak@muehlheim.de + * @deprecated merged into the class Javac. + */ +public class Jikes +{ + protected String command; + protected JikesOutputParser jop; + protected Project project; + + /** + * Constructs a new Jikes obect. + * + * @param jop - Parser to send jike's output to + * @param command - name of jikes executeable + * @param project Description of Parameter + */ + protected Jikes( JikesOutputParser jop, String command, Project project ) + { + super(); + this.jop = jop; + this.command = command; + this.project = project; + } + + /** + * Do the compile with the specified arguments. + * + * @param args - arguments to pass to process on command line + */ + protected void compile( String[] args ) + { + String[] commandArray = null; + File tmpFile = null; + + try + { + String myos = System.getProperty( "os.name" ); + + // Windows has a 32k limit on total arg size, so + // create a temporary file to store all the arguments + + // There have been reports that 300 files could be compiled + // so 250 is a conservative approach + if( myos.toLowerCase().indexOf( "windows" ) >= 0 + && args.length > 250 ) + { + PrintWriter out = null; + try + { + tmpFile = new File( "jikes" + ( new Random( System.currentTimeMillis() ) ).nextLong() ); + out = new PrintWriter( new FileWriter( tmpFile ) ); + for( int i = 0; i < args.length; i++ ) + { + out.println( args[i] ); + } + out.flush(); + commandArray = new String[]{command, + "@" + tmpFile.getAbsolutePath()}; + } + catch( IOException e ) + { + throw new BuildException( "Error creating temporary file", e ); + } + finally + { + if( out != null ) + { + try + { + out.close(); + } + catch( Throwable t ) + {} + } + } + } + else + { + commandArray = new String[args.length + 1]; + commandArray[0] = command; + System.arraycopy( args, 0, commandArray, 1, args.length ); + } + + // We assume, that everything jikes writes goes to + // standard output, not to standard error. The option + // -Xstdout that is given to Jikes in Javac.doJikesCompile() + // should guarantee this. At least I hope so. :) + try + { + Execute exe = new Execute( jop ); + exe.setAntRun( project ); + exe.setWorkingDirectory( project.getBaseDir() ); + exe.setCommandline( commandArray ); + exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( "Error running Jikes compiler", e ); + } + } + finally + { + if( tmpFile != null ) + { + tmpFile.delete(); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JikesOutputParser.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JikesOutputParser.java new file mode 100644 index 000000000..3376ff97f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/JikesOutputParser.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Parses output from jikes and passes errors and warnings into the right + * logging channels of Project. TODO: Parsing could be much better + * + * @author skanthak@muehlheim.de + * @deprecated use Jikes' exit value to detect compilation failure. + */ +public class JikesOutputParser implements ExecuteStreamHandler +{ + protected boolean errorFlag = false; + protected boolean error = false; + + protected BufferedReader br; + protected boolean emacsMode;// no errors so far + protected int errors, warnings; + protected Task task; + + /** + * Construct a new Parser object + * + * @param task - task in whichs context we are called + * @param emacsMode Description of Parameter + */ + protected JikesOutputParser( Task task, boolean emacsMode ) + { + super(); + this.task = task; + this.emacsMode = emacsMode; + } + + /** + * Ignore. + * + * @param is The new ProcessErrorStream value + */ + public void setProcessErrorStream( InputStream is ) { } + + /** + * Ignore. + * + * @param os The new ProcessInputStream value + */ + public void setProcessInputStream( OutputStream os ) { } + + /** + * Set the inputstream + * + * @param is The new ProcessOutputStream value + * @exception IOException Description of Exception + */ + public void setProcessOutputStream( InputStream is ) + throws IOException + { + br = new BufferedReader( new InputStreamReader( is ) ); + } + + /** + * Invokes parseOutput. + * + * @exception IOException Description of Exception + */ + public void start() + throws IOException + { + parseOutput( br ); + } + + /** + * Ignore. + */ + public void stop() { } + + /** + * Indicate if there were errors during the compile + * + * @return if errors ocured + */ + protected boolean getErrorFlag() + { + return errorFlag; + } + + /** + * Parse the output of a jikes compiler + * + * @param reader - Reader used to read jikes's output + * @exception IOException Description of Exception + */ + protected void parseOutput( BufferedReader reader ) + throws IOException + { + if( emacsMode ) + parseEmacsOutput( reader ); + else + parseStandardOutput( reader ); + } + + private void setError( boolean err ) + { + error = err; + if( error ) + errorFlag = true; + } + + private void log( String line ) + { + if( !emacsMode ) + { + task.log( "", ( error ? Project.MSG_ERR : Project.MSG_WARN ) ); + } + task.log( line, ( error ? Project.MSG_ERR : Project.MSG_WARN ) ); + } + + private void parseEmacsOutput( BufferedReader reader ) + throws IOException + { + // This may change, if we add advanced parsing capabilities. + parseStandardOutput( reader ); + } + + private void parseStandardOutput( BufferedReader reader ) + throws IOException + { + String line; + String lower; + // We assume, that every output, jike does, stands for an error/warning + // XXX + // Is this correct? + + // TODO: + // A warning line, that shows code, which contains a variable + // error will cause some trouble. The parser should definitely + // be much better. + + while( ( line = reader.readLine() ) != null ) + { + lower = line.toLowerCase(); + if( line.trim().equals( "" ) ) + continue; + if( lower.indexOf( "error" ) != -1 ) + setError( true ); + else if( lower.indexOf( "warning" ) != -1 ) + setError( false ); + else + { + // If we don't know the type of the line + // and we are in emacs mode, it will be + // an error, because in this mode, jikes won't + // always print "error", but sometimes other + // keywords like "Syntax". We should look for + // all those keywords. + if( emacsMode ) + setError( true ); + } + log( line ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/KeySubst.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/KeySubst.java new file mode 100644 index 000000000..22fbf44f3 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/KeySubst.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Hashtable; +import java.util.StringTokenizer; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Keyword substitution. Input file is written to output file. Do not make input + * file same as output file. Keywords in input files look like this: + * + * @author Jon S. Stevens jon@clearink.com + * @foo@. See the docs for the setKeys method to understand how to do the + * substitutions. + * @deprecated KeySubst is deprecated. Use Filter + CopyDir instead. + */ +public class KeySubst extends Task +{ + private File source = null; + private File dest = null; + private String sep = "*"; + private Hashtable replacements = new Hashtable(); + + + public static void main( String[] args ) + { + try + { + Hashtable hash = new Hashtable(); + hash.put( "VERSION", "1.0.3" ); + hash.put( "b", "ffff" ); + System.out.println( KeySubst.replace( "$f ${VERSION} f ${b} jj $", hash ) ); + } + catch( Exception e ) + { + e.printStackTrace(); + } + } + + /** + * Does replacement on text using the hashtable of keys. + * + * @param origString Description of Parameter + * @param keys Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @returns the string with the replacements in it. + */ + public static String replace( String origString, Hashtable keys ) + throws BuildException + { + StringBuffer finalString = new StringBuffer(); + int index = 0; + int i = 0; + String key = null; + while( ( index = origString.indexOf( "${", i ) ) > -1 ) + { + key = origString.substring( index + 2, origString.indexOf( "}", index + 3 ) ); + finalString.append( origString.substring( i, index ) ); + if( keys.containsKey( key ) ) + { + finalString.append( keys.get( key ) ); + } + else + { + finalString.append( "${" ); + finalString.append( key ); + finalString.append( "}" ); + } + i = index + 3 + key.length(); + } + finalString.append( origString.substring( i ) ); + return finalString.toString(); + } + + /** + * Set the destination file. + * + * @param dest The new Dest value + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Format string is like this:

              + * + * name=value*name2=value

              + * + * Names are case sensitive.

              + * + * Use the setSep() method to change the * to something else if you need to + * use * as a name or value. + * + * @param keys The new Keys value + */ + public void setKeys( String keys ) + { + if( keys != null && keys.length() > 0 ) + { + StringTokenizer tok = + new StringTokenizer( keys, this.sep, false ); + while( tok.hasMoreTokens() ) + { + String token = tok.nextToken().trim(); + StringTokenizer itok = + new StringTokenizer( token, "=", false ); + + String name = itok.nextToken(); + String value = itok.nextToken(); +// log ( "Name: " + name ); +// log ( "Value: " + value ); + replacements.put( name, value ); + } + } + } + + /** + * Sets the seperator between name=value arguments in setKeys(). By default + * it is "*". + * + * @param sep The new Sep value + */ + public void setSep( String sep ) + { + this.sep = sep; + } + + /** + * Set the source file. + * + * @param s The new Src value + */ + public void setSrc( File s ) + { + this.source = s; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + log( "!! KeySubst is deprecated. Use Filter + CopyDir instead. !!" ); + log( "Performing Substitions" ); + if( source == null || dest == null ) + { + log( "Source and destinations must not be null" ); + return; + } + BufferedReader br = null; + BufferedWriter bw = null; + try + { + br = new BufferedReader( new FileReader( source ) ); + dest.delete(); + bw = new BufferedWriter( new FileWriter( dest ) ); + + String line = null; + String newline = null; + int length; + line = br.readLine(); + while( line != null ) + { + if( line.length() == 0 ) + { + bw.newLine(); + } + else + { + newline = KeySubst.replace( line, replacements ); + bw.write( newline ); + bw.newLine(); + } + line = br.readLine(); + } + bw.flush(); + bw.close(); + br.close(); + } + catch( IOException ioe ) + { + ioe.printStackTrace(); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogOutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogOutputStream.java new file mode 100644 index 000000000..f715beaaa --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogOutputStream.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + + +/** + * Logs each line written to this stream to the log system of ant. Tries to be + * smart about line separators.
              + * TODO: This class can be split to implement other line based processing of + * data written to the stream. + * + * @author thomas.haas@softwired-inc.com + */ +public class LogOutputStream extends OutputStream +{ + + private ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + private boolean skip = false; + private int level = Project.MSG_INFO; + + private Task task; + + /** + * Creates a new instance of this class. + * + * @param task the task for whom to log + * @param level loglevel used to log data written to this stream. + */ + public LogOutputStream( Task task, int level ) + { + this.task = task; + this.level = level; + } + + public int getMessageLevel() + { + return level; + } + + + /** + * Writes all remaining + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + if( buffer.size() > 0 ) + processBuffer(); + super.close(); + } + + + /** + * Write the data to the buffer and flush the buffer, if a line separator is + * detected. + * + * @param cc data to log (byte). + * @exception IOException Description of Exception + */ + public void write( int cc ) + throws IOException + { + final byte c = ( byte )cc; + if( ( c == '\n' ) || ( c == '\r' ) ) + { + if( !skip ) + processBuffer(); + } + else + buffer.write( cc ); + skip = ( c == '\r' ); + } + + + /** + * Converts the buffer to a string and sends it to processLine + */ + protected void processBuffer() + { + processLine( buffer.toString() ); + buffer.reset(); + } + + /** + * Logs a line to the log system of ant. + * + * @param line the line to log. + */ + protected void processLine( String line ) + { + processLine( line, level ); + } + + /** + * Logs a line to the log system of ant. + * + * @param line the line to log. + * @param level Description of Parameter + */ + protected void processLine( String line, int level ) + { + task.log( line, level ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogStreamHandler.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogStreamHandler.java new file mode 100644 index 000000000..b7e6c849b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/LogStreamHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Logs standard output and error of a subprocess to the log system of ant. + * + * @author thomas.haas@softwired-inc.com + */ +public class LogStreamHandler extends PumpStreamHandler +{ + + /** + * Creates a new instance of this class. + * + * @param task the task for whom to log + * @param outlevel the loglevel used to log standard output + * @param errlevel the loglevel used to log standard error + */ + public LogStreamHandler( Task task, int outlevel, int errlevel ) + { + super( new LogOutputStream( task, outlevel ), + new LogOutputStream( task, errlevel ) ); + } + + public void stop() + { + super.stop(); + try + { + getErr().close(); + getOut().close(); + } + catch( IOException e ) + { + // plain impossible + throw new BuildException( e ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Manifest.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Manifest.java new file mode 100644 index 000000000..242b0fa5e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Manifest.java @@ -0,0 +1,950 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Class to manage Manifest information + * + * @author Conor MacNeill + * @author Stefan Bodewig + */ +public class Manifest extends Task +{ + /** + * The standard manifest version header + */ + public final static String ATTRIBUTE_MANIFEST_VERSION = "Manifest-Version"; + + /** + * The standard Signature Version header + */ + public final static String ATTRIBUTE_SIGNATURE_VERSION = "Signature-Version"; + + /** + * The Name Attribute is the first in a named section + */ + public final static String ATTRIBUTE_NAME = "Name"; + + /** + * The From Header is disallowed in a Manifest + */ + public final static String ATTRIBUTE_FROM = "From"; + + /** + * The Class-Path Header is special - it can be duplicated + */ + public final static String ATTRIBUTE_CLASSPATH = "class-path"; + + /** + * Default Manifest version if one is not specified + */ + public final static String DEFAULT_MANIFEST_VERSION = "1.0"; + + /** + * The max length of a line in a Manifest + */ + public final static int MAX_LINE_LENGTH = 70; + + /** + * The version of this manifest + */ + private String manifestVersion = DEFAULT_MANIFEST_VERSION; + + /** + * The main section of this manifest + */ + private Section mainSection = new Section(); + + /** + * The named sections of this manifest + */ + private Hashtable sections = new Hashtable(); + + private File manifestFile; + + private Mode mode; + + /** + * Construct an empty manifest + */ + public Manifest() + { + mode = new Mode(); + mode.setValue( "replace" ); + manifestVersion = null; + } + + /** + * Read a manifest file from the given reader + * + * @param r Description of Parameter + * @exception ManifestException Description of Exception + * @exception IOException Description of Exception + * @throws ManifestException if the manifest is not valid according to the + * JAR spec + * @throws IOException if the manifest cannot be read from the reader. + */ + public Manifest( Reader r ) + throws ManifestException, IOException + { + BufferedReader reader = new BufferedReader( r ); + // This should be the manifest version + String nextSectionName = mainSection.read( reader ); + String readManifestVersion = mainSection.getAttributeValue( ATTRIBUTE_MANIFEST_VERSION ); + if( readManifestVersion != null ) + { + manifestVersion = readManifestVersion; + mainSection.removeAttribute( ATTRIBUTE_MANIFEST_VERSION ); + } + + String line = null; + while( ( line = reader.readLine() ) != null ) + { + if( line.length() == 0 ) + { + continue; + } + + Section section = new Section(); + if( nextSectionName == null ) + { + Attribute sectionName = new Attribute( line ); + if( !sectionName.getName().equalsIgnoreCase( ATTRIBUTE_NAME ) ) + { + throw new ManifestException( "Manifest sections should start with a \"" + ATTRIBUTE_NAME + + "\" attribute and not \"" + sectionName.getName() + "\"" ); + } + nextSectionName = sectionName.getValue(); + } + else + { + // we have already started reading this section + // this line is the first attribute. set it and then let the normal + // read handle the rest + Attribute firstAttribute = new Attribute( line ); + section.addAttributeAndCheck( firstAttribute ); + } + + section.setName( nextSectionName ); + nextSectionName = section.read( reader ); + addConfiguredSection( section ); + } + } + + /** + * Construct a manifest from Ant's default manifest file. + * + * @return The DefaultManifest value + * @exception BuildException Description of Exception + */ + public static Manifest getDefaultManifest() + throws BuildException + { + try + { + String s = "/org/apache/tools/ant/defaultManifest.mf"; + InputStream in = Manifest.class.getResourceAsStream( s ); + if( in == null ) + { + throw new BuildException( "Could not find default manifest: " + s ); + } + try + { + return new Manifest( new InputStreamReader( in, "ASCII" ) ); + } + catch( UnsupportedEncodingException e ) + { + return new Manifest( new InputStreamReader( in ) ); + } + } + catch( ManifestException e ) + { + throw new BuildException( "Default manifest is invalid !!" ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read default manifest", e ); + } + } + + /** + * The name of the manifest file to write (if used as a task). + * + * @param f The new File value + */ + public void setFile( File f ) + { + manifestFile = f; + } + + /** + * Shall we update or replace an existing manifest? + * + * @param m The new Mode value + */ + public void setMode( Mode m ) + { + mode = m; + } + + /** + * Get the warnings for this manifest. + * + * @return an enumeration of warning strings + */ + public Enumeration getWarnings() + { + Vector warnings = new Vector(); + + for( Enumeration e2 = mainSection.getWarnings(); e2.hasMoreElements(); ) + { + warnings.addElement( e2.nextElement() ); + } + + // create a vector and add in the warnings for all the sections + for( Enumeration e = sections.elements(); e.hasMoreElements(); ) + { + Section section = ( Section )e.nextElement(); + for( Enumeration e2 = section.getWarnings(); e2.hasMoreElements(); ) + { + warnings.addElement( e2.nextElement() ); + } + } + + return warnings.elements(); + } + + public void addConfiguredAttribute( Attribute attribute ) + throws ManifestException + { + mainSection.addConfiguredAttribute( attribute ); + } + + public void addConfiguredSection( Section section ) + throws ManifestException + { + if( section.getName() == null ) + { + throw new BuildException( "Sections must have a name" ); + } + sections.put( section.getName().toLowerCase(), section ); + } + + public boolean equals( Object rhs ) + { + if( !( rhs instanceof Manifest ) ) + { + return false; + } + + Manifest rhsManifest = ( Manifest )rhs; + if( manifestVersion == null ) + { + if( rhsManifest.manifestVersion != null ) + { + return false; + } + } + else if( !manifestVersion.equals( rhsManifest.manifestVersion ) ) + { + return false; + } + if( sections.size() != rhsManifest.sections.size() ) + { + return false; + } + + if( !mainSection.equals( rhsManifest.mainSection ) ) + { + return false; + } + + for( Enumeration e = sections.elements(); e.hasMoreElements(); ) + { + Section section = ( Section )e.nextElement(); + Section rhsSection = ( Section )rhsManifest.sections.get( section.getName().toLowerCase() ); + if( !section.equals( rhsSection ) ) + { + return false; + } + } + + return true; + } + + /** + * Create or update the Manifest when used as a task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( manifestFile == null ) + { + throw new BuildException( "the file attribute is required" ); + } + + Manifest toWrite = getDefaultManifest(); + + if( mode.getValue().equals( "update" ) && manifestFile.exists() ) + { + FileReader f = null; + try + { + f = new FileReader( manifestFile ); + toWrite.merge( new Manifest( f ) ); + } + catch( ManifestException m ) + { + throw new BuildException( "Existing manifest " + manifestFile + + " is invalid", m, location ); + } + catch( IOException e ) + { + throw new BuildException( "Failed to read " + manifestFile, + e, location ); + } + finally + { + if( f != null ) + { + try + { + f.close(); + } + catch( IOException e ) + {} + } + } + } + + try + { + toWrite.merge( this ); + } + catch( ManifestException m ) + { + throw new BuildException( "Manifest is invalid", m, location ); + } + + PrintWriter w = null; + try + { + w = new PrintWriter( new FileWriter( manifestFile ) ); + toWrite.write( w ); + } + catch( IOException e ) + { + throw new BuildException( "Failed to write " + manifestFile, + e, location ); + } + finally + { + if( w != null ) + { + w.close(); + } + } + } + + /** + * Merge the contents of the given manifest into this manifest + * + * @param other the Manifest to be merged with this one. + * @throws ManifestException if there is a problem merging the manfest + * according to the Manifest spec. + */ + public void merge( Manifest other ) + throws ManifestException + { + if( other.manifestVersion != null ) + { + manifestVersion = other.manifestVersion; + } + mainSection.merge( other.mainSection ); + for( Enumeration e = other.sections.keys(); e.hasMoreElements(); ) + { + String sectionName = ( String )e.nextElement(); + Section ourSection = ( Section )sections.get( sectionName ); + Section otherSection = ( Section )other.sections.get( sectionName ); + if( ourSection == null ) + { + sections.put( sectionName.toLowerCase(), otherSection ); + } + else + { + ourSection.merge( otherSection ); + } + } + + } + + /** + * Convert the manifest to its string representation + * + * @return a multiline string with the Manifest as it appears in a Manifest + * file. + */ + public String toString() + { + StringWriter sw = new StringWriter(); + try + { + write( new PrintWriter( sw ) ); + } + catch( IOException e ) + { + return null; + } + return sw.toString(); + } + + /** + * Write the manifest out to a print writer. + * + * @param writer the Writer to which the manifest is written + * @throws IOException if the manifest cannot be written + */ + public void write( PrintWriter writer ) + throws IOException + { + writer.println( ATTRIBUTE_MANIFEST_VERSION + ": " + manifestVersion ); + String signatureVersion = mainSection.getAttributeValue( ATTRIBUTE_SIGNATURE_VERSION ); + if( signatureVersion != null ) + { + writer.println( ATTRIBUTE_SIGNATURE_VERSION + ": " + signatureVersion ); + mainSection.removeAttribute( ATTRIBUTE_SIGNATURE_VERSION ); + } + mainSection.write( writer ); + if( signatureVersion != null ) + { + try + { + mainSection.addConfiguredAttribute( new Attribute( ATTRIBUTE_SIGNATURE_VERSION, signatureVersion ) ); + } + catch( ManifestException e ) + { + // shouldn't happen - ignore + } + } + + for( Enumeration e = sections.elements(); e.hasMoreElements(); ) + { + Section section = ( Section )e.nextElement(); + section.write( writer ); + } + } + + /** + * Class to hold manifest attributes + * + * @author RT + */ + public static class Attribute + { + /** + * The attribute's name + */ + private String name = null; + + /** + * The attribute's value + */ + private String value = null; + + /** + * Construct an empty attribute + */ + public Attribute() { } + + /** + * Construct an attribute by parsing a line from the Manifest + * + * @param line the line containing the attribute name and value + * @exception ManifestException Description of Exception + * @throws ManifestException if the line is not valid + */ + public Attribute( String line ) + throws ManifestException + { + parse( line ); + } + + /** + * Construct a manifest by specifying its name and value + * + * @param name the attribute's name + * @param value the Attribute's value + */ + public Attribute( String name, String value ) + { + this.name = name; + this.value = value; + } + + /** + * Set the Attribute's name + * + * @param name the attribute's name + */ + public void setName( String name ) + { + this.name = name; + } + + /** + * Set the Attribute's value + * + * @param value the attribute's value + */ + public void setValue( String value ) + { + this.value = value; + } + + /** + * Get the Attribute's name + * + * @return the attribute's name. + */ + public String getName() + { + return name; + } + + /** + * Get the Attribute's value + * + * @return the attribute's value. + */ + public String getValue() + { + return value; + } + + /** + * Add a continuation line from the Manifest file When lines are too + * long in a manifest, they are continued on the next line by starting + * with a space. This method adds the continuation data to the attribute + * value by skipping the first character. + * + * @param line The feature to be added to the Continuation attribute + */ + public void addContinuation( String line ) + { + value += line.substring( 1 ); + } + + public boolean equals( Object rhs ) + { + if( !( rhs instanceof Attribute ) ) + { + return false; + } + + Attribute rhsAttribute = ( Attribute )rhs; + return ( name != null && rhsAttribute.name != null && + name.toLowerCase().equals( rhsAttribute.name.toLowerCase() ) && + value != null && value.equals( rhsAttribute.value ) ); + } + + /** + * Parse a line into name and value pairs + * + * @param line the line to be parsed + * @throws ManifestException if the line does not contain a colon + * separating the name and value + */ + public void parse( String line ) + throws ManifestException + { + int index = line.indexOf( ": " ); + if( index == -1 ) + { + throw new ManifestException( "Manifest line \"" + line + "\" is not valid as it does not " + + "contain a name and a value separated by ': ' " ); + } + name = line.substring( 0, index ); + value = line.substring( index + 2 ); + } + + /** + * Write the attribute out to a print writer. + * + * @param writer the Writer to which the attribute is written + * @throws IOException if the attribte value cannot be written + */ + public void write( PrintWriter writer ) + throws IOException + { + String line = name + ": " + value; + while( line.getBytes().length > MAX_LINE_LENGTH ) + { + // try to find a MAX_LINE_LENGTH byte section + int breakIndex = MAX_LINE_LENGTH; + String section = line.substring( 0, breakIndex ); + while( section.getBytes().length > MAX_LINE_LENGTH && breakIndex > 0 ) + { + breakIndex--; + section = line.substring( 0, breakIndex ); + } + if( breakIndex == 0 ) + { + throw new IOException( "Unable to write manifest line " + name + ": " + value ); + } + writer.println( section ); + line = " " + line.substring( breakIndex ); + } + writer.println( line ); + } + } + + /** + * Helper class for Manifest's mode attribute. + * + * @author RT + */ + public static class Mode extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"update", "replace"}; + } + } + + /** + * Class to represent an individual section in the Manifest. A section + * consists of a set of attribute values, separated from other sections by a + * blank line. + * + * @author RT + */ + public static class Section + { + private Vector warnings = new Vector(); + + /** + * The section's name if any. The main section in a manifest is unnamed. + */ + private String name = null; + + /** + * The section's attributes. + */ + private Hashtable attributes = new Hashtable(); + + /** + * Set the Section's name + * + * @param name the section's name + */ + public void setName( String name ) + { + this.name = name; + } + + /** + * Get the value of the attribute with the name given. + * + * @param attributeName the name of the attribute to be returned. + * @return the attribute's value or null if the attribute does not exist + * in the section + */ + public String getAttributeValue( String attributeName ) + { + Object attribute = attributes.get( attributeName.toLowerCase() ); + if( attribute == null ) + { + return null; + } + if( attribute instanceof Attribute ) + { + return ( ( Attribute )attribute ).getValue(); + } + else + { + String value = ""; + for( Enumeration e = ( ( Vector )attribute ).elements(); e.hasMoreElements(); ) + { + Attribute classpathAttribute = ( Attribute )e.nextElement(); + value += classpathAttribute.getValue() + " "; + } + return value.trim(); + } + } + + /** + * Get the Section's name + * + * @return the section's name. + */ + public String getName() + { + return name; + } + + public Enumeration getWarnings() + { + return warnings.elements(); + } + + /** + * Add an attribute to the section + * + * @param attribute the attribute to be added. + * @return the value of the attribute if it is a name attribute - null + * other wise + * @throws ManifestException if the attribute already exists in this + * section. + */ + public String addAttributeAndCheck( Attribute attribute ) + throws ManifestException + { + if( attribute.getName() == null || attribute.getValue() == null ) + { + throw new BuildException( "Attributes must have name and value" ); + } + if( attribute.getName().equalsIgnoreCase( ATTRIBUTE_NAME ) ) + { + warnings.addElement( "\"" + ATTRIBUTE_NAME + "\" attributes should not occur in the " + + "main section and must be the first element in all " + + "other sections: \"" + attribute.getName() + ": " + attribute.getValue() + "\"" ); + return attribute.getValue(); + } + + if( attribute.getName().toLowerCase().startsWith( ATTRIBUTE_FROM.toLowerCase() ) ) + { + warnings.addElement( "Manifest attributes should not start with \"" + + ATTRIBUTE_FROM + "\" in \"" + attribute.getName() + ": " + attribute.getValue() + "\"" ); + } + else + { + // classpath attributes go into a vector + String attributeName = attribute.getName().toLowerCase(); + if( attributeName.equals( ATTRIBUTE_CLASSPATH ) ) + { + Vector classpathAttrs = ( Vector )attributes.get( attributeName ); + if( classpathAttrs == null ) + { + classpathAttrs = new Vector(); + attributes.put( attributeName, classpathAttrs ); + } + classpathAttrs.addElement( attribute ); + } + else if( attributes.containsKey( attributeName ) ) + { + throw new ManifestException( "The attribute \"" + attribute.getName() + "\" may not " + + "occur more than once in the same section" ); + } + else + { + attributes.put( attributeName, attribute ); + } + } + return null; + } + + public void addConfiguredAttribute( Attribute attribute ) + throws ManifestException + { + String check = addAttributeAndCheck( attribute ); + if( check != null ) + { + throw new BuildException( "Specify the section name using the \"name\" attribute of the

              element rather " + + "than using a \"Name\" manifest attribute" ); + } + } + + public boolean equals( Object rhs ) + { + if( !( rhs instanceof Section ) ) + { + return false; + } + + Section rhsSection = ( Section )rhs; + if( attributes.size() != rhsSection.attributes.size() ) + { + return false; + } + + for( Enumeration e = attributes.elements(); e.hasMoreElements(); ) + { + Attribute attribute = ( Attribute )e.nextElement(); + Attribute rshAttribute = ( Attribute )rhsSection.attributes.get( attribute.getName().toLowerCase() ); + if( !attribute.equals( rshAttribute ) ) + { + return false; + } + } + + return true; + } + + /** + * Merge in another section + * + * @param section the section to be merged with this one. + * @throws ManifestException if the sections cannot be merged. + */ + public void merge( Section section ) + throws ManifestException + { + if( name == null && section.getName() != null || + name != null && !( name.equalsIgnoreCase( section.getName() ) ) ) + { + throw new ManifestException( "Unable to merge sections with different names" ); + } + + for( Enumeration e = section.attributes.keys(); e.hasMoreElements(); ) + { + String attributeName = ( String )e.nextElement(); + if( attributeName.equals( ATTRIBUTE_CLASSPATH ) && + attributes.containsKey( attributeName ) ) + { + // classpath entries are vetors which are merged + Vector classpathAttrs = ( Vector )section.attributes.get( attributeName ); + Vector ourClasspathAttrs = ( Vector )attributes.get( attributeName ); + for( Enumeration e2 = classpathAttrs.elements(); e2.hasMoreElements(); ) + { + ourClasspathAttrs.addElement( e2.nextElement() ); + } + } + else + { + // the merge file always wins + attributes.put( attributeName, section.attributes.get( attributeName ) ); + } + } + + // add in the warnings + for( Enumeration e = section.warnings.elements(); e.hasMoreElements(); ) + { + warnings.addElement( e.nextElement() ); + } + } + + /** + * Read a section through a reader + * + * @param reader the reader from which the section is read + * @return the name of the next section if it has been read as part of + * this section - This only happens if the Manifest is malformed. + * @throws ManifestException if the section is not valid according to + * the JAR spec + * @throws IOException if the section cannot be read from the reader. + */ + public String read( BufferedReader reader ) + throws ManifestException, IOException + { + Attribute attribute = null; + while( true ) + { + String line = reader.readLine(); + if( line == null || line.length() == 0 ) + { + return null; + } + if( line.charAt( 0 ) == ' ' ) + { + // continuation line + if( attribute == null ) + { + if( name != null ) + { + // a continuation on the first line is a continuation of the name - concatenate + // this line and the name + name += line.substring( 1 ); + } + else + { + throw new ManifestException( "Can't start an attribute with a continuation line " + line ); + } + } + else + { + attribute.addContinuation( line ); + } + } + else + { + attribute = new Attribute( line ); + String nameReadAhead = addAttributeAndCheck( attribute ); + if( nameReadAhead != null ) + { + return nameReadAhead; + } + } + } + } + + /** + * Remove tge given attribute from the section + * + * @param attributeName the name of the attribute to be removed. + */ + public void removeAttribute( String attributeName ) + { + attributes.remove( attributeName.toLowerCase() ); + } + + /** + * Write the section out to a print writer. + * + * @param writer the Writer to which the section is written + * @throws IOException if the section cannot be written + */ + public void write( PrintWriter writer ) + throws IOException + { + if( name != null ) + { + Attribute nameAttr = new Attribute( ATTRIBUTE_NAME, name ); + nameAttr.write( writer ); + } + for( Enumeration e = attributes.elements(); e.hasMoreElements(); ) + { + Object object = e.nextElement(); + if( object instanceof Attribute ) + { + Attribute attribute = ( Attribute )object; + attribute.write( writer ); + } + else + { + Vector attrList = ( Vector )object; + for( Enumeration e2 = attrList.elements(); e2.hasMoreElements(); ) + { + Attribute attribute = ( Attribute )e2.nextElement(); + attribute.write( writer ); + } + } + } + writer.println(); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ManifestException.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ManifestException.java new file mode 100644 index 000000000..813da555e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ManifestException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + + + +/** + * Exception thrown indicating problems in a JAR Manifest + * + * @author Conor MacNeill + */ +public class ManifestException extends Exception +{ + + /** + * Constructs an exception with the given descriptive message. + * + * @param msg Description of or information about the exception. + */ + public ManifestException( String msg ) + { + super( msg ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/MatchingTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/MatchingTask.java new file mode 100644 index 000000000..a03cb9be7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/MatchingTask.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.PatternSet; + +/** + * This is an abstract task that should be used by all those tasks that require + * to include or exclude files based on pattern matching. + * + * @author Arnout J. Kuiper ajkuiper@wxs.nl + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Sam Ruby rubys@us.ibm.com + * @author Jon S. Stevens jon@clearink.com + * @author Stefan Bodewig + */ + +public abstract class MatchingTask extends Task +{ + + protected boolean useDefaultExcludes = true; + protected FileSet fileset = new FileSet(); + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + fileset.setExcludes( excludes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param excludesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setExcludesfile( File excludesfile ) + { + fileset.setExcludesfile( excludesfile ); + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space. + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + fileset.setIncludes( includes ); + } + + /** + * Sets the name of the file containing the includes patterns. + * + * @param includesfile A string containing the filename to fetch the include + * patterns from. + */ + public void setIncludesfile( File includesfile ) + { + fileset.setIncludesfile( includesfile ); + } + + /** + * List of filenames and directory names to not include. They should be + * either , or " " (space) separated. The ignored files will be logged. + * + * @param ignoreString the string containing the files to ignore. + */ + public void XsetIgnore( String ignoreString ) + { + log( "The ignore attribute is deprecated." + + "Please use the excludes attribute.", + Project.MSG_WARN ); + if( ignoreString != null && ignoreString.length() > 0 ) + { + Vector tmpExcludes = new Vector(); + StringTokenizer tok = new StringTokenizer( ignoreString, ", ", false ); + while( tok.hasMoreTokens() ) + { + createExclude().setName( "**/" + tok.nextToken().trim() + "/**" ); + } + } + } + + /** + * Set this to be the items in the base directory that you want to be + * included. You can also specify "*" for the items (ie: items="*") and it + * will include all the items in the base directory. + * + * @param itemString the string containing the files to include. + */ + public void XsetItems( String itemString ) + { + log( "The items attribute is deprecated. " + + "Please use the includes attribute.", + Project.MSG_WARN ); + if( itemString == null || itemString.equals( "*" ) + || itemString.equals( "." ) ) + { + createInclude().setName( "**" ); + } + else + { + StringTokenizer tok = new StringTokenizer( itemString, ", " ); + while( tok.hasMoreTokens() ) + { + String pattern = tok.nextToken().trim(); + if( pattern.length() > 0 ) + { + createInclude().setName( pattern + "/**" ); + } + } + } + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + return fileset.createExclude(); + } + + /** + * add a name entry on the include files list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExcludesFile() + { + return fileset.createExcludesFile(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + return fileset.createInclude(); + } + + /** + * add a name entry on the include files list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createIncludesFile() + { + return fileset.createIncludesFile(); + } + + /** + * add a set of patterns + * + * @return Description of the Returned Value + */ + public PatternSet createPatternSet() + { + return fileset.createPatternSet(); + } + + /** + * Returns the directory scanner needed to access the files to process. + * + * @param baseDir Description of Parameter + * @return The DirectoryScanner value + */ + protected DirectoryScanner getDirectoryScanner( File baseDir ) + { + fileset.setDir( baseDir ); + fileset.setDefaultexcludes( useDefaultExcludes ); + return fileset.getDirectoryScanner( project ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Mkdir.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Mkdir.java new file mode 100644 index 000000000..c323430a4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Mkdir.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +/** + * Creates a given directory. + * + * @author duncan@x180.com + */ + +public class Mkdir extends Task +{ + + private File dir; + + public void setDir( File dir ) + { + this.dir = dir; + } + + public void execute() + throws BuildException + { + if( dir == null ) + { + throw new BuildException( "dir attribute is required", location ); + } + + if( dir.isFile() ) + { + throw new BuildException( "Unable to create directory as a file already exists with that name: " + dir.getAbsolutePath() ); + } + + if( !dir.exists() ) + { + boolean result = dir.mkdirs(); + if( result == false ) + { + String msg = "Directory " + dir.getAbsolutePath() + " creation was not " + + "successful for an unknown reason"; + throw new BuildException( msg, location ); + } + log( "Created dir: " + dir.getAbsolutePath() ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Move.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Move.java new file mode 100644 index 000000000..75811008d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Move.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; + +/** + * Moves a file or directory to a new file or directory. By default, the + * destination is overwriten when existing. When overwrite is turned off, then + * files are only moved if the source file is newer than the destination file, + * or when the destination file does not exist.

              + * + * Source files and directories are only deleted when the file or directory has + * been copied to the destination successfully. Filtering also works.

              + * + * This implementation is based on Arnout Kuiper's initial design document, the + * following mailing list discussions, and the copyfile/copydir tasks.

              + * + * @author Glenn McAllister glennm@ca.ibm.com + * + * @author Magesh Umasankar + */ +public class Move extends Copy +{ + + public Move() + { + super(); + forceOverwrite = true; + } + + /** + * Go and delete the directory tree. + * + * @param d Description of Parameter + */ + protected void deleteDir( File d ) + { + String[] list = d.list(); + if( list == null ) + return;// on an io error list() can return null + + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( d, s ); + if( f.isDirectory() ) + { + deleteDir( f ); + } + else + { + throw new BuildException( "UNEXPECTED ERROR - The file " + f.getAbsolutePath() + " should not exist!" ); + } + } + log( "Deleting directory " + d.getAbsolutePath(), verbosity ); + if( !d.delete() ) + { + throw new BuildException( "Unable to delete directory " + d.getAbsolutePath() ); + } + } + +//************************************************************************ +// protected and private methods +//************************************************************************ + + protected void doFileOperations() + { + //Attempt complete directory renames, if any, first. + if( completeDirMap.size() > 0 ) + { + Enumeration e = completeDirMap.keys(); + while( e.hasMoreElements() ) + { + File fromDir = ( File )e.nextElement(); + File toDir = ( File )completeDirMap.get( fromDir ); + try + { + log( "Attempting to rename dir: " + fromDir + + " to " + toDir, verbosity ); + renameFile( fromDir, toDir, filtering, forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Failed to rename dir " + fromDir + + " to " + toDir + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + if( fileCopyMap.size() > 0 ) + {// files to move + log( "Moving " + fileCopyMap.size() + " files to " + + destDir.getAbsolutePath() ); + + Enumeration e = fileCopyMap.keys(); + while( e.hasMoreElements() ) + { + String fromFile = ( String )e.nextElement(); + String toFile = ( String )fileCopyMap.get( fromFile ); + + if( fromFile.equals( toFile ) ) + { + log( "Skipping self-move of " + fromFile, verbosity ); + continue; + } + + boolean moved = false; + File f = new File( fromFile ); + + if( f.exists() ) + {//Is this file still available to be moved? + File d = new File( toFile ); + + try + { + log( "Attempting to rename: " + fromFile + + " to " + toFile, verbosity ); + moved = renameFile( f, d, filtering, forceOverwrite ); + } + catch( IOException ioe ) + { + String msg = "Failed to rename " + fromFile + + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + + if( !moved ) + { + try + { + log( "Moving " + fromFile + " to " + toFile, verbosity ); + + FilterSetCollection executionFilters = new FilterSetCollection(); + if( filtering ) + { + executionFilters.addFilterSet( project.getGlobalFilterSet() ); + } + for( Enumeration filterEnum = getFilterSets().elements(); filterEnum.hasMoreElements(); ) + { + executionFilters.addFilterSet( ( FilterSet )filterEnum.nextElement() ); + } + getFileUtils().copyFile( f, d, executionFilters, + forceOverwrite ); + + f = new File( fromFile ); + if( !f.delete() ) + { + throw new BuildException( "Unable to delete file " + + f.getAbsolutePath() ); + } + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + } + } + + if( includeEmpty ) + { + Enumeration e = dirCopyMap.elements(); + int count = 0; + while( e.hasMoreElements() ) + { + File d = new File( ( String )e.nextElement() ); + if( !d.exists() ) + { + if( !d.mkdirs() ) + { + log( "Unable to create directory " + d.getAbsolutePath(), Project.MSG_ERR ); + } + else + { + count++; + } + } + } + + if( count > 0 ) + { + log( "Moved " + count + " empty directories to " + destDir.getAbsolutePath() ); + } + } + + if( filesets.size() > 0 ) + { + Enumeration e = filesets.elements(); + while( e.hasMoreElements() ) + { + FileSet fs = ( FileSet )e.nextElement(); + File dir = fs.getDir( project ); + + if( okToDelete( dir ) ) + { + deleteDir( dir ); + } + } + } + } + + /** + * Its only ok to delete a directory tree if there are no files in it. + * + * @param d Description of Parameter + * @return Description of the Returned Value + */ + protected boolean okToDelete( File d ) + { + String[] list = d.list(); + if( list == null ) + return false;// maybe io error? + + for( int i = 0; i < list.length; i++ ) + { + String s = list[i]; + File f = new File( d, s ); + if( f.isDirectory() ) + { + if( !okToDelete( f ) ) + return false; + } + else + { + return false;// found a file + } + } + + return true; + } + + /** + * Attempts to rename a file from a source to a destination. If overwrite is + * set to true, this method overwrites existing file even if the destination + * file is newer. Otherwise, the source file is renamed only if the + * destination file is older than it. Method then checks if token filtering + * is used. If it is, this method returns false assuming it is the + * responsibility to the copyFile method. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filtering Description of Parameter + * @param overwrite Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @throws IOException + */ + protected boolean renameFile( File sourceFile, File destFile, + boolean filtering, boolean overwrite ) + throws IOException, BuildException + { + + boolean renamed = true; + if( !filtering ) + { + // ensure that parent dir of dest file exists! + // not using getParentFile method to stay 1.1 compat + String parentPath = destFile.getParent(); + if( parentPath != null ) + { + File parent = new File( parentPath ); + if( !parent.exists() ) + { + parent.mkdirs(); + } + } + + if( destFile.exists() ) + { + if( !destFile.delete() ) + { + throw new BuildException( "Unable to remove existing file " + + destFile ); + } + } + renamed = sourceFile.renameTo( destFile ); + } + else + { + renamed = false; + } + return renamed; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Pack.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Pack.java new file mode 100644 index 000000000..3a9d0701d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Pack.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Abstract Base class for pack tasks. + * + * @author Magesh Umasankar + */ + +public abstract class Pack extends Task +{ + protected File source; + + protected File zipFile; + + public void setSrc( File src ) + { + source = src; + } + + public void setZipfile( File zipFile ) + { + this.zipFile = zipFile; + } + + public void execute() + throws BuildException + { + validate(); + log( "Building: " + zipFile.getAbsolutePath() ); + pack(); + } + + protected abstract void pack(); + + protected void zipFile( File file, OutputStream zOut ) + throws IOException + { + FileInputStream fIn = new FileInputStream( file ); + try + { + zipFile( fIn, zOut ); + } + finally + { + fIn.close(); + } + } + + private void validate() + { + if( zipFile == null ) + { + throw new BuildException( "zipfile attribute is required", location ); + } + + if( source == null ) + { + throw new BuildException( "src attribute is required", location ); + } + + if( source.isDirectory() ) + { + throw new BuildException( "Src attribute must not " + + "represent a directory!", location ); + } + } + + private void zipFile( InputStream in, OutputStream zOut ) + throws IOException + { + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + zOut.write( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Parallel.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Parallel.java new file mode 100644 index 000000000..1203369ac --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Parallel.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.TaskContainer; + + +/** + * Implements a multi threaded task execution.

              + * + * + * + * @author Thomas Christen chr@active.ch + * @author Conor MacNeill + */ +public class Parallel extends Task + implements TaskContainer +{ + + /** + * Collection holding the nested tasks + */ + private Vector nestedTasks = new Vector(); + + + /** + * Add a nested task to execute parallel (asynchron).

              + * + * + * + * @param nestedTask Nested task to be executed in parallel + * @exception BuildException Description of Exception + */ + public void addTask( Task nestedTask ) + throws BuildException + { + nestedTasks.addElement( nestedTask ); + } + + /** + * Block execution until the specified time or for a specified amount of + * milliseconds and if defined, execute the wait status. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + TaskThread[] threads = new TaskThread[nestedTasks.size()]; + int threadNumber = 0; + for( Enumeration e = nestedTasks.elements(); e.hasMoreElements(); threadNumber++ ) + { + Task nestedTask = ( Task )e.nextElement(); + threads[threadNumber] = new TaskThread( threadNumber, nestedTask ); + } + + // now start all threads + for( int i = 0; i < threads.length; ++i ) + { + threads[i].start(); + } + + // now join to all the threads + for( int i = 0; i < threads.length; ++i ) + { + try + { + threads[i].join(); + } + catch( InterruptedException ie ) + { + // who would interrupt me at a time like this? + } + } + + // now did any of the threads throw an exception + StringBuffer exceptionMessage = new StringBuffer(); + String lSep = System.getProperty( "line.separator" ); + int numExceptions = 0; + Throwable firstException = null; + Location firstLocation = Location.UNKNOWN_LOCATION; + ; + for( int i = 0; i < threads.length; ++i ) + { + Throwable t = threads[i].getException(); + if( t != null ) + { + numExceptions++; + if( firstException == null ) + { + firstException = t; + } + if( t instanceof BuildException && + firstLocation == Location.UNKNOWN_LOCATION ) + { + firstLocation = ( ( BuildException )t ).getLocation(); + } + exceptionMessage.append( lSep ); + exceptionMessage.append( t.getMessage() ); + } + } + + if( numExceptions == 1 ) + { + if( firstException instanceof BuildException ) + { + throw ( BuildException )firstException; + } + else + { + throw new BuildException( firstException ); + } + } + else if( numExceptions > 1 ) + { + throw new BuildException( exceptionMessage.toString(), firstLocation ); + } + } + + class TaskThread extends Thread + { + private Throwable exception; + private Task task; + private int taskNumber; + + /** + * Construct a new TaskThread

              + * + * + * + * @param task the Task to be executed in a seperate thread + * @param taskNumber Description of Parameter + */ + TaskThread( int taskNumber, Task task ) + { + this.task = task; + this.taskNumber = taskNumber; + } + + public Throwable getException() + { + return exception; + } + + /** + * Executes the task within a thread and takes care about Exceptions + * raised within the task. + */ + public void run() + { + try + { + task.perform(); + } + catch( Throwable t ) + { + exception = t; + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Patch.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Patch.java new file mode 100644 index 000000000..8c7657b12 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Patch.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; + +/** + * Task as a layer on top of patch. Patch applies a diff file to an original. + * + * @author Stefan Bodewig + */ +public class Patch extends Task +{ + private boolean havePatchfile = false; + private Commandline cmd = new Commandline(); + + private File originalFile; + + /** + * Shall patch write backups. + * + * @param backups The new Backups value + */ + public void setBackups( boolean backups ) + { + if( backups ) + { + cmd.createArgument().setValue( "-b" ); + } + } + + /** + * Ignore whitespace differences. + * + * @param ignore The new Ignorewhitespace value + */ + public void setIgnorewhitespace( boolean ignore ) + { + if( ignore ) + { + cmd.createArgument().setValue( "-l" ); + } + } + + /** + * The file to patch. + * + * @param file The new Originalfile value + */ + public void setOriginalfile( File file ) + { + originalFile = file; + } + + /** + * The file containing the diff output. + * + * @param file The new Patchfile value + */ + public void setPatchfile( File file ) + { + if( !file.exists() ) + { + throw new BuildException( "patchfile " + file + " doesn\'t exist", + location ); + } + cmd.createArgument().setValue( "-i" ); + cmd.createArgument().setFile( file ); + havePatchfile = true; + } + + /** + * Work silently unless an error occurs. + * + * @param q The new Quiet value + */ + public void setQuiet( boolean q ) + { + if( q ) + { + cmd.createArgument().setValue( "-s" ); + } + } + + /** + * Assume patch was created with old and new files swapped. + * + * @param r The new Reverse value + */ + public void setReverse( boolean r ) + { + if( r ) + { + cmd.createArgument().setValue( "-R" ); + } + } + + /** + * Strip the smallest prefix containing num leading slashes from + * filenames.

              + * + * patch's -p option. + * + * @param num The new Strip value + * @exception BuildException Description of Exception + */ + public void setStrip( int num ) + throws BuildException + { + if( num < 0 ) + { + throw new BuildException( "strip has to be >= 0", location ); + } + cmd.createArgument().setValue( "-p" + num ); + } + + public void execute() + throws BuildException + { + if( !havePatchfile ) + { + throw new BuildException( "patchfile argument is required", + location ); + } + + Commandline toExecute = ( Commandline )cmd.clone(); + toExecute.setExecutable( "patch" ); + + if( originalFile != null ) + { + toExecute.createArgument().setFile( originalFile ); + } + + Execute exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ), + null ); + exe.setCommandline( toExecute.getCommandline() ); + try + { + exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + +}// Patch diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PathConvert.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PathConvert.java new file mode 100644 index 000000000..e508b580a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PathConvert.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * This task converts path and classpath information to a specific target OS + * format. The resulting formatted path is placed into a specified property.

              + * + * LIMITATION: Currently this implementation groups all machines into one of two + * types: Unix or Windows. Unix is defined as NOT windows. + * + * @author Larry Streepy + * streepy@healthlanguage.com + */ +public class PathConvert extends Task +{ + + // Members + private Path path = null;// Path to be converted + private Reference refid = null;// Reference to path/fileset to convert + private String targetOS = null;// The target OS type + private boolean targetWindows = false;// Set when targetOS is set + private boolean onWindows = false;// Set if we're running on windows + private String property = null;// The property to receive the results + private Vector prefixMap = new Vector();// Path prefix map + private String pathSep = null;// User override on path sep char + private String dirSep = null; + + /** + * Override the default directory separator string for the target os + * + * @param sep The new DirSep value + */ + public void setDirSep( String sep ) + { + dirSep = sep; + } + + /** + * Override the default path separator string for the target os + * + * @param sep The new PathSep value + */ + public void setPathSep( String sep ) + { + pathSep = sep; + } + + /** + * Set the value of the proprty attribute - this is the property into which + * our converted path will be placed. + * + * @param p The new Property value + */ + public void setProperty( String p ) + { + property = p; + } + + /** + * Adds a reference to a PATH or FILESET defined elsewhere. + * + * @param r The new Refid value + */ + public void setRefid( Reference r ) + { + if( path != null ) + throw noChildrenAllowed(); + + refid = r; + } + + /** + * Set the value of the targetos attribute + * + * @param target The new Targetos value + */ + public void setTargetos( String target ) + { + + targetOS = target.toLowerCase(); + + if( !targetOS.equals( "windows" ) && !target.equals( "unix" ) && + !targetOS.equals( "netware" ) ) + { + throw new BuildException( "targetos must be one of 'unix', 'netware', or 'windows'" ); + } + + // Currently, we deal with only two path formats: Unix and Windows + // And Unix is everything that is not Windows + + // for NetWare, piggy-back on Windows, since in the validateSetup code, + // the same assumptions can be made as with windows - + // that ; is the path separator + + targetWindows = ( targetOS.equals( "windows" ) || targetOS.equals( "netware" ) ); + } + + /** + * Has the refid attribute of this element been set? + * + * @return The Reference value + */ + public boolean isReference() + { + return refid != null; + } + + /** + * Create a nested MAP element + * + * @return Description of the Returned Value + */ + public MapEntry createMap() + { + + MapEntry entry = new MapEntry(); + prefixMap.addElement( entry ); + return entry; + } + + /** + * Create a nested PATH element + * + * @return Description of the Returned Value + */ + public Path createPath() + { + + if( isReference() ) + throw noChildrenAllowed(); + + if( path == null ) + { + path = new Path( getProject() ); + } + return path.createPath(); + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + + // If we are a reference, the create a Path from the reference + if( isReference() ) + { + path = new Path( getProject() ).createPath(); + + Object obj = refid.getReferencedObject( getProject() ); + + if( obj instanceof Path ) + { + path.setRefid( refid ); + } + else if( obj instanceof FileSet ) + { + FileSet fs = ( FileSet )obj; + path.addFileset( fs ); + } + else + { + throw new BuildException( "'refid' does not refer to a path or fileset" ); + } + } + + validateSetup();// validate our setup + + // Currently, we deal with only two path formats: Unix and Windows + // And Unix is everything that is not Windows + // (with the exception for NetWare below) + + String osname = System.getProperty( "os.name" ).toLowerCase(); + + // for NetWare, piggy-back on Windows, since here and in the + // apply code, the same assumptions can be made as with windows - + // that \\ is an OK separator, and do comparisons case-insensitive. + onWindows = ( ( osname.indexOf( "windows" ) >= 0 ) || + ( osname.indexOf( "netware" ) >= 0 ) ); + + // Determine the from/to char mappings for dir sep + char fromDirSep = onWindows ? '\\' : '/'; + char toDirSep = dirSep.charAt( 0 ); + + StringBuffer rslt = new StringBuffer( 100 ); + + // Get the list of path components in canonical form + String[] elems = path.list(); + + for( int i = 0; i < elems.length; i++ ) + { + String elem = elems[i]; + + elem = mapElement( elem );// Apply the path prefix map + + // Now convert the path and file separator characters from the + // current os to the target os. + + elem = elem.replace( fromDirSep, toDirSep ); + + if( i != 0 ) + rslt.append( pathSep ); + rslt.append( elem ); + } + + // Place the result into the specified property + String value = rslt.toString(); + + log( "Set property " + property + " = " + value, Project.MSG_VERBOSE ); + + getProject().setNewProperty( property, value ); + } + + /** + * Apply the configured map to a path element. The map is used to convert + * between Windows drive letters and Unix paths. If no map is configured, + * then the input string is returned unchanged. + * + * @param elem The path element to apply the map to + * @return String Updated element + */ + private String mapElement( String elem ) + { + + int size = prefixMap.size(); + + if( size != 0 ) + { + + // Iterate over the map entries and apply each one. Stop when one of the + // entries actually changes the element + + for( int i = 0; i < size; i++ ) + { + MapEntry entry = ( MapEntry )prefixMap.elementAt( i ); + String newElem = entry.apply( elem ); + + // Note I'm using "!=" to see if we got a new object back from + // the apply method. + + if( newElem != elem ) + { + elem = newElem; + break;// We applied one, so we're done + } + } + } + + return elem; + } + + /** + * Creates an exception that indicates that this XML element must not have + * child elements if the refid attribute is set. + * + * @return Description of the Returned Value + */ + private BuildException noChildrenAllowed() + { + return new BuildException( "You must not specify nested PATH elements when using refid" ); + } + + /** + * Validate that all our parameters have been properly initialized. + * + * @throws BuildException if something is not setup properly + */ + private void validateSetup() + throws BuildException + { + + if( path == null ) + throw new BuildException( "You must specify a path to convert" ); + + if( property == null ) + throw new BuildException( "You must specify a property" ); + + // Must either have a target OS or both a dirSep and pathSep + + if( targetOS == null && pathSep == null && dirSep == null ) + throw new BuildException( "You must specify at least one of targetOS, dirSep, or pathSep" ); + + // Determine the separator strings. The dirsep and pathsep attributes + // override the targetOS settings. + String dsep = File.separator; + String psep = File.pathSeparator; + + if( targetOS != null ) + { + psep = targetWindows ? ";" : ":"; + dsep = targetWindows ? "\\" : "/"; + } + + if( pathSep != null ) + {// override with pathsep= + psep = pathSep; + } + + if( dirSep != null ) + {// override with dirsep= + dsep = dirSep; + } + + pathSep = psep; + dirSep = dsep; + } + + /** + * Helper class, holds the nested values. Elements will look like + * this: <map from="d:" to="/foo"/>

              + * + * When running on windows, the prefix comparison will be case insensitive. + * + * @author RT + */ + public class MapEntry + { + + // Members + private String from = null; + private String to = null; + + /** + * Set the "from" attribute of the map entry + * + * @param from The new From value + */ + public void setFrom( String from ) + { + this.from = from; + } + + /** + * Set the "to" attribute of the map entry + * + * @param to The new To value + */ + public void setTo( String to ) + { + this.to = to; + } + + /** + * Apply this map entry to a given path element + * + * @param elem Path element to process + * @return String Updated path element after mapping + */ + public String apply( String elem ) + { + if( from == null || to == null ) + { + throw new BuildException( "Both 'from' and 'to' must be set in a map entry" ); + } + + // If we're on windows, then do the comparison ignoring case + String cmpElem = onWindows ? elem.toLowerCase() : elem; + String cmpFrom = onWindows ? from.toLowerCase() : from; + + // If the element starts with the configured prefix, then convert the prefix + // to the configured 'to' value. + + if( cmpElem.startsWith( cmpFrom ) ) + { + int len = from.length(); + + if( len >= elem.length() ) + { + elem = to; + } + else + { + elem = to + elem.substring( len ); + } + } + + return elem; + } + }// User override on directory sep char +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ProcessDestroyer.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ProcessDestroyer.java new file mode 100644 index 000000000..590e86fd1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/ProcessDestroyer.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Vector; + +/** + * Destroys all registered Processes when the VM exits. + * + * @author Michael Newcomb + */ +class ProcessDestroyer + extends Thread +{ + + private Vector processes = new Vector(); + + /** + * Constructs a ProcessDestroyer and registers it as a shutdown + * hook. + */ + public ProcessDestroyer() + { + try + { + // check to see if the method exists (support pre-JDK 1.3 VMs) + // + Class[] paramTypes = {Thread.class}; + Method addShutdownHook = + Runtime.class.getMethod( "addShutdownHook", paramTypes ); + + // add the hook + // + Object[] args = {this}; + addShutdownHook.invoke( Runtime.getRuntime(), args ); + } + catch( Exception e ) + { + // it just won't be added as a shutdown hook... :( + } + } + + /** + * Returns true if the specified Process was + * successfully added to the list of processes to destroy upon VM exit. + * + * @param process the process to add + * @return true if the specified Process was + * successfully added + */ + public boolean add( Process process ) + { + processes.addElement( process ); + return processes.contains( process ); + } + + /** + * Returns true if the specified Process was + * successfully removed from the list of processes to destroy upon VM exit. + * + * @param process the process to remove + * @return true if the specified Process was + * successfully removed + */ + public boolean remove( Process process ) + { + return processes.removeElement( process ); + } + + /** + * Invoked by the VM when it is exiting. + */ + public void run() + { + synchronized( processes ) + { + Enumeration e = processes.elements(); + while( e.hasMoreElements() ) + { + ( ( Process )e.nextElement() ).destroy(); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Property.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Property.java new file mode 100644 index 000000000..15b182ecf --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Property.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Will set a Project property. Used to be a hack in ProjectHelper Will not + * override values set by the command line or parent projects. + * + * @author costin@dnt.ro + * @author Sam Ruby + * @author Glenn McAllister + */ +public class Property extends Task +{ + protected Path classpath; + protected String env; + protected File file; + + protected String name; + protected Reference ref; + protected String resource; + + protected boolean userProperty; + protected String value;// set read-only properties + + public Property() + { + super(); + } + + protected Property( boolean userProperty ) + { + this.userProperty = userProperty; + } + + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setEnvironment( String env ) + { + this.env = env; + } + + public void setFile( File file ) + { + this.file = file; + } + + public void setLocation( File location ) + { + setValue( location.getAbsolutePath() ); + } + + public void setName( String name ) + { + this.name = name; + } + + public void setRefid( Reference ref ) + { + this.ref = ref; + } + + public void setResource( String resource ) + { + this.resource = resource; + } + + /** + * @param userProperty The new UserProperty value + * @deprecated This was never a supported feature and has been deprecated + * without replacement + */ + public void setUserProperty( boolean userProperty ) + { + log( "DEPRECATED: Ignoring request to set user property in Property task.", + Project.MSG_WARN ); + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getEnvironment() + { + return env; + } + + public File getFile() + { + return file; + } + + public String getName() + { + return name; + } + + public Reference getRefid() + { + return ref; + } + + public String getResource() + { + return resource; + } + + public String getValue() + { + return value; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + public void execute() + throws BuildException + { + if( name != null ) + { + if( value == null && ref == null ) + { + throw new BuildException( "You must specify value, location or refid with the name attribute", + location ); + } + } + else + { + if( file == null && resource == null && env == null ) + { + throw new BuildException( "You must specify file, resource or environment when not using the name attribute", + location ); + } + } + + if( ( name != null ) && ( value != null ) ) + { + addProperty( name, value ); + } + + if( file != null ) + loadFile( file ); + + if( resource != null ) + loadResource( resource ); + + if( env != null ) + loadEnvironment( env ); + + if( ( name != null ) && ( ref != null ) ) + { + Object obj = ref.getReferencedObject( getProject() ); + if( obj != null ) + { + addProperty( name, obj.toString() ); + } + } + } + + public String toString() + { + return value == null ? "" : value; + } + + protected void addProperties( Properties props ) + { + resolveAllProperties( props ); + Enumeration e = props.keys(); + while( e.hasMoreElements() ) + { + String name = ( String )e.nextElement(); + String value = ( String )props.getProperty( name ); + + String v = project.replaceProperties( value ); + addProperty( name, v ); + } + } + + protected void addProperty( String n, String v ) + { + if( userProperty ) + { + if( project.getUserProperty( n ) == null ) + { + project.setUserProperty( n, v ); + } + else + { + log( "Override ignored for " + n, Project.MSG_VERBOSE ); + } + } + else + { + project.setNewProperty( n, v ); + } + } + + protected void loadEnvironment( String prefix ) + { + Properties props = new Properties(); + if( !prefix.endsWith( "." ) ) + prefix += "."; + log( "Loading Environment " + prefix, Project.MSG_VERBOSE ); + Vector osEnv = Execute.getProcEnvironment(); + for( Enumeration e = osEnv.elements(); e.hasMoreElements(); ) + { + String entry = ( String )e.nextElement(); + int pos = entry.indexOf( '=' ); + if( pos == -1 ) + { + log( "Ignoring: " + entry, Project.MSG_WARN ); + } + else + { + props.put( prefix + entry.substring( 0, pos ), + entry.substring( pos + 1 ) ); + } + } + addProperties( props ); + } + + protected void loadFile( File file ) + throws BuildException + { + Properties props = new Properties(); + log( "Loading " + file.getAbsolutePath(), Project.MSG_VERBOSE ); + try + { + if( file.exists() ) + { + FileInputStream fis = new FileInputStream( file ); + try + { + props.load( fis ); + } + finally + { + if( fis != null ) + { + fis.close(); + } + } + addProperties( props ); + } + else + { + log( "Unable to find property file: " + file.getAbsolutePath(), + Project.MSG_VERBOSE ); + } + } + catch( IOException ex ) + { + throw new BuildException( ex ); + } + } + + protected void loadResource( String name ) + { + Properties props = new Properties(); + log( "Resource Loading " + name, Project.MSG_VERBOSE ); + try + { + ClassLoader cL = null; + InputStream is = null; + + if( classpath != null ) + { + cL = new AntClassLoader( project, classpath ); + } + else + { + cL = this.getClass().getClassLoader(); + } + + if( cL == null ) + { + is = ClassLoader.getSystemResourceAsStream( name ); + } + else + { + is = cL.getResourceAsStream( name ); + } + + if( is != null ) + { + props.load( is ); + addProperties( props ); + } + else + { + log( "Unable to find resource " + name, Project.MSG_WARN ); + } + } + catch( IOException ex ) + { + throw new BuildException( ex ); + } + } + + private void resolveAllProperties( Properties props ) + throws BuildException + { + for( Enumeration e = props.keys(); e.hasMoreElements(); ) + { + String name = ( String )e.nextElement(); + String value = props.getProperty( name ); + + boolean resolved = false; + while( !resolved ) + { + Vector fragments = new Vector(); + Vector propertyRefs = new Vector(); + ProjectHelper.parsePropertyString( value, fragments, propertyRefs ); + + resolved = true; + if( propertyRefs.size() != 0 ) + { + StringBuffer sb = new StringBuffer(); + Enumeration i = fragments.elements(); + Enumeration j = propertyRefs.elements(); + while( i.hasMoreElements() ) + { + String fragment = ( String )i.nextElement(); + if( fragment == null ) + { + String propertyName = ( String )j.nextElement(); + if( propertyName.equals( name ) ) + { + throw new BuildException( "Property " + name + " was circularly defined." ); + } + fragment = getProject().getProperty( propertyName ); + if( fragment == null ) + { + if( props.containsKey( propertyName ) ) + { + fragment = props.getProperty( propertyName ); + resolved = false; + } + else + { + fragment = "${" + propertyName + "}"; + } + } + } + sb.append( fragment ); + } + value = sb.toString(); + props.put( name, value ); + } + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PumpStreamHandler.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PumpStreamHandler.java new file mode 100644 index 000000000..ab5109052 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/PumpStreamHandler.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Copies standard output and error of subprocesses to standard output and error + * of the parent process. TODO: standard input of the subprocess is not + * implemented. + * + * @author thomas.haas@softwired-inc.com + */ +public class PumpStreamHandler implements ExecuteStreamHandler +{ + private Thread errorThread; + + private Thread inputThread; + + private OutputStream out, err; + + public PumpStreamHandler( OutputStream out, OutputStream err ) + { + this.out = out; + this.err = err; + } + + public PumpStreamHandler( OutputStream outAndErr ) + { + this( outAndErr, outAndErr ); + } + + public PumpStreamHandler() + { + this( System.out, System.err ); + } + + + public void setProcessErrorStream( InputStream is ) + { + createProcessErrorPump( is, err ); + } + + + public void setProcessInputStream( OutputStream os ) { } + + public void setProcessOutputStream( InputStream is ) + { + createProcessOutputPump( is, out ); + } + + + public void start() + { + inputThread.start(); + errorThread.start(); + } + + + public void stop() + { + try + { + inputThread.join(); + } + catch( InterruptedException e ) + {} + try + { + errorThread.join(); + } + catch( InterruptedException e ) + {} + try + { + err.flush(); + } + catch( IOException e ) + {} + try + { + out.flush(); + } + catch( IOException e ) + {} + } + + protected OutputStream getErr() + { + return err; + } + + protected OutputStream getOut() + { + return out; + } + + protected void createProcessErrorPump( InputStream is, OutputStream os ) + { + errorThread = createPump( is, os ); + } + + protected void createProcessOutputPump( InputStream is, OutputStream os ) + { + inputThread = createPump( is, os ); + } + + + /** + * Creates a stream pumper to copy the given input stream to the given + * output stream. + * + * @param is Description of Parameter + * @param os Description of Parameter + * @return Description of the Returned Value + */ + protected Thread createPump( InputStream is, OutputStream os ) + { + final Thread result = new Thread( new StreamPumper( is, os ) ); + result.setDaemon( true ); + return result; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Recorder.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Recorder.java new file mode 100644 index 000000000..ebe3fcc92 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Recorder.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * This task is the manager for RecorderEntry's. It is this class that holds all + * entries, modifies them every time the <recorder> task is called, and + * addes them to the build listener process. + * + * @author J D Glanville + * @version 0.5 + * @see RecorderEntry + */ +public class Recorder extends Task +{ + /** + * The list of recorder entries. + */ + private static Hashtable recorderEntries = new Hashtable(); + + ////////////////////////////////////////////////////////////////////// + // ATTRIBUTES + + /** + * The name of the file to record to. + */ + private String filename = null; + /** + * Whether or not to append. Need Boolean to record an unset state (null). + */ + private Boolean append = null; + /** + * Whether to start or stop recording. Need Boolean to record an unset state + * (null). + */ + private Boolean start = null; + /** + * What level to log? -1 means not initialized yet. + */ + private int loglevel = -1; + + /** + * Sets the action for the associated recorder entry. + * + * @param action The action for the entry to take: start or stop. + */ + public void setAction( ActionChoices action ) + { + if( action.getValue().equalsIgnoreCase( "start" ) ) + { + start = Boolean.TRUE; + } + else + { + start = Boolean.FALSE; + } + } + + /** + * Whether or not the logger should append to a previous file. + * + * @param append The new Append value + */ + public void setAppend( boolean append ) + { + this.append = new Boolean( append ); + } + + /** + * Sets the level to which this recorder entry should log to. + * + * @param level The new Loglevel value + * @see VerbosityLevelChoices + */ + public void setLoglevel( VerbosityLevelChoices level ) + { + //I hate cascading if/elseif clauses !!! + String lev = level.getValue(); + if( lev.equalsIgnoreCase( "error" ) ) + { + loglevel = Project.MSG_ERR; + } + else if( lev.equalsIgnoreCase( "warn" ) ) + { + loglevel = Project.MSG_WARN; + } + else if( lev.equalsIgnoreCase( "info" ) ) + { + loglevel = Project.MSG_INFO; + } + else if( lev.equalsIgnoreCase( "verbose" ) ) + { + loglevel = Project.MSG_VERBOSE; + } + else if( lev.equalsIgnoreCase( "debug" ) ) + { + loglevel = Project.MSG_DEBUG; + } + } + + ////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS / INITIALIZERS + + ////////////////////////////////////////////////////////////////////// + // ACCESSOR METHODS + + /** + * Sets the name of the file to log to, and the name of the recorder entry. + * + * @param fname File name of logfile. + */ + public void setName( String fname ) + { + filename = fname; + } + + ////////////////////////////////////////////////////////////////////// + // CORE / MAIN BODY + + /** + * The main execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( filename == null ) + throw new BuildException( "No filename specified" ); + + getProject().log( "setting a recorder for name " + filename, + Project.MSG_DEBUG ); + + // get the recorder entry + RecorderEntry recorder = getRecorder( filename, getProject() ); + // set the values on the recorder + recorder.setMessageOutputLevel( loglevel ); + recorder.setRecordState( start ); + } + + /** + * Gets the recorder that's associated with the passed in name. If the + * recorder doesn't exist, then a new one is created. + * + * @param name Description of Parameter + * @param proj Description of Parameter + * @return The Recorder value + * @exception BuildException Description of Exception + */ + protected RecorderEntry getRecorder( String name, Project proj ) + throws BuildException + { + Object o = recorderEntries.get( name ); + RecorderEntry entry; + if( o == null ) + { + // create a recorder entry + try + { + entry = new RecorderEntry( name ); + PrintStream out = null; + if( append == null ) + { + out = new PrintStream( + new FileOutputStream( name ) ); + } + else + { + out = new PrintStream( + new FileOutputStream( name, append.booleanValue() ) ); + } + entry.setErrorPrintStream( out ); + entry.setOutputPrintStream( out ); + } + catch( IOException ioe ) + { + throw new BuildException( "Problems creating a recorder entry", + ioe ); + } + proj.addBuildListener( entry ); + recorderEntries.put( name, entry ); + } + else + { + entry = ( RecorderEntry )o; + } + return entry; + } + + ////////////////////////////////////////////////////////////////////// + // INNER CLASSES + + /** + * A list of possible values for the setAction() method. + * Possible values include: start and stop. + * + * @author RT + */ + public static class ActionChoices extends EnumeratedAttribute + { + private final static String[] values = {"start", "stop"}; + + public String[] getValues() + { + return values; + } + } + + /** + * A list of possible values for the setLoglevel() method. + * Possible values include: error, warn, info, verbose, debug. + * + * @author RT + */ + public static class VerbosityLevelChoices extends EnumeratedAttribute + { + private final static String[] values = {"error", "warn", "info", + "verbose", "debug"}; + + public String[] getValues() + { + return values; + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/RecorderEntry.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/RecorderEntry.java new file mode 100644 index 000000000..4dad6229e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/RecorderEntry.java @@ -0,0 +1,208 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.PrintStream; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildLogger; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.StringUtils; + + +/** + * This is a class that represents a recorder. This is the listener to the build + * process. + * + * @author J D Glanville + * @version 0.5 + */ +public class RecorderEntry implements BuildLogger +{ + + ////////////////////////////////////////////////////////////////////// + // ATTRIBUTES + + /** + * The name of the file associated with this recorder entry. + */ + private String filename = null; + /** + * The state of the recorder (recorder on or off). + */ + private boolean record = true; + /** + * The current verbosity level to record at. + */ + private int loglevel = Project.MSG_INFO; + /** + * The output PrintStream to record to. + */ + private PrintStream out = null; + /** + * The start time of the last know target. + */ + private long targetStartTime = 0l; + + ////////////////////////////////////////////////////////////////////// + // CONSTRUCTORS / INITIALIZERS + + /** + * @param name The name of this recorder (used as the filename). + */ + protected RecorderEntry( String name ) + { + filename = name; + } + + private static String formatTime( long millis ) + { + long seconds = millis / 1000; + long minutes = seconds / 60; + + if( minutes > 0 ) + { + return Long.toString( minutes ) + " minute" + + ( minutes == 1 ? " " : "s " ) + + Long.toString( seconds % 60 ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + else + { + return Long.toString( seconds ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + + } + + public void setEmacsMode( boolean emacsMode ) + { + throw new java.lang.RuntimeException( "Method setEmacsMode() not yet implemented." ); + } + + public void setErrorPrintStream( PrintStream err ) + { + out = err; + } + + public void setMessageOutputLevel( int level ) + { + if( level >= Project.MSG_ERR && level <= Project.MSG_DEBUG ) + loglevel = level; + } + + public void setOutputPrintStream( PrintStream output ) + { + out = output; + } + + /** + * Turns off or on this recorder. + * + * @param state true for on, false for off, null for no change. + */ + public void setRecordState( Boolean state ) + { + if( state != null ) + record = state.booleanValue(); + } + + ////////////////////////////////////////////////////////////////////// + // ACCESSOR METHODS + + /** + * @return the name of the file the output is sent to. + */ + public String getFilename() + { + return filename; + } + + public void buildFinished( BuildEvent event ) + { + log( "< BUILD FINISHED", Project.MSG_DEBUG ); + + Throwable error = event.getException(); + if( error == null ) + { + out.println( StringUtils.LINE_SEP + "BUILD SUCCESSFUL" ); + } + else + { + out.println( StringUtils.LINE_SEP + "BUILD FAILED" + StringUtils.LINE_SEP ); + error.printStackTrace( out ); + } + out.flush(); + out.close(); + } + + public void buildStarted( BuildEvent event ) + { + log( "> BUILD STARTED", Project.MSG_DEBUG ); + } + + public void messageLogged( BuildEvent event ) + { + log( "--- MESSAGE LOGGED", Project.MSG_DEBUG ); + + StringBuffer buf = new StringBuffer(); + if( event.getTask() != null ) + { + String name = "[" + event.getTask().getTaskName() + "]"; + /** + * @todo replace 12 with DefaultLogger.LEFT_COLUMN_SIZE + */ + for( int i = 0; i < ( 12 - name.length() ); i++ ) + { + buf.append( " " ); + }// for + buf.append( name ); + }// if + buf.append( event.getMessage() ); + + log( buf.toString(), event.getPriority() ); + } + + public void targetFinished( BuildEvent event ) + { + log( "<< TARGET FINISHED -- " + event.getTarget(), Project.MSG_DEBUG ); + String time = formatTime( System.currentTimeMillis() - targetStartTime ); + log( event.getTarget() + ": duration " + time, Project.MSG_VERBOSE ); + out.flush(); + } + + public void targetStarted( BuildEvent event ) + { + log( ">> TARGET STARTED -- " + event.getTarget(), Project.MSG_DEBUG ); + log( StringUtils.LINE_SEP + event.getTarget().getName() + ":", Project.MSG_INFO ); + targetStartTime = System.currentTimeMillis(); + } + + public void taskFinished( BuildEvent event ) + { + log( "<<< TASK FINISHED -- " + event.getTask(), Project.MSG_DEBUG ); + out.flush(); + } + + public void taskStarted( BuildEvent event ) + { + log( ">>> TASK STARTED -- " + event.getTask(), Project.MSG_DEBUG ); + } + + /** + * The thing that actually sends the information to the output. + * + * @param mesg The message to log. + * @param level The verbosity level of the message. + */ + private void log( String mesg, int level ) + { + if( record && ( level <= loglevel ) ) + { + out.println( mesg ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rename.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rename.java new file mode 100644 index 000000000..7e1090049 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rename.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Renames a file. + * + * @author haas@softwired.ch + * @deprecated The rename task is deprecated. Use move instead. + */ +public class Rename extends Task +{ + private boolean replace = true; + private File dest; + + private File src; + + /** + * Sets the new name of the file. + * + * @param dest the new name of the file. + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Sets wheter an existing file should be replaced. + * + * @param replace on, if an existing file should be replaced. + */ + public void setReplace( String replace ) + { + this.replace = project.toBoolean( replace ); + } + + + /** + * Sets the file to be renamed. + * + * @param src the file to rename + */ + public void setSrc( File src ) + { + this.src = src; + } + + + /** + * Renames the file src to dest + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + log( "DEPRECATED - The rename task is deprecated. Use move instead." ); + + if( dest == null ) + { + throw new BuildException( "dest attribute is required", location ); + } + + if( src == null ) + { + throw new BuildException( "src attribute is required", location ); + } + + if( replace && dest.exists() ) + { + if( !dest.delete() ) + { + throw new BuildException( "Unable to remove existing file " + + dest ); + } + } + if( !src.renameTo( dest ) ) + { + throw new BuildException( "Unable to rename " + src + " to " + + dest ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Replace.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Replace.java new file mode 100644 index 000000000..626ac8216 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Replace.java @@ -0,0 +1,591 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.FileUtils; + +/** + * Replaces all occurrences of one or more string tokens with given values in + * the indicated files. Each value can be either a string or the value of a + * property available in a designated property file. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Erik Langenbach + */ +public class Replace extends MatchingTask +{ + + private File src = null; + private NestedString token = null; + private NestedString value = new NestedString(); + + private File propertyFile = null; + private Properties properties = null; + private Vector replacefilters = new Vector(); + + private File dir = null; + private boolean summary = false; + + /** + * The encoding used to read and write files - if null, uses default + */ + private String encoding = null; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + + private int fileCount; + private int replaceCount; + + + /** + * Set the source files path when using matching tasks. + * + * @param dir The new Dir value + */ + public void setDir( File dir ) + { + this.dir = dir; + } + + /** + * Set the file encoding to use on the files read and written by replace + * + * @param encoding the encoding to use on the files + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + + /** + * Set the source file. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.src = file; + } + + /** + * Sets a file to be searched for property values. + * + * @param filename The new PropertyFile value + */ + public void setPropertyFile( File filename ) + { + propertyFile = filename; + } + + /** + * Request a summary + * + * @param summary true if you would like a summary logged of the replace + * operation + */ + public void setSummary( boolean summary ) + { + this.summary = summary; + } + + /** + * Set the string token to replace. + * + * @param token The new Token value + */ + public void setToken( String token ) + { + createReplaceToken().addText( token ); + } + + /** + * Set the string value to use as token replacement. + * + * @param value The new Value value + */ + public void setValue( String value ) + { + createReplaceValue().addText( value ); + } + + public Properties getProperties( File propertyFile ) + throws BuildException + { + Properties properties = new Properties(); + + try + { + properties.load( new FileInputStream( propertyFile ) ); + } + catch( FileNotFoundException e ) + { + String message = "Property file (" + propertyFile.getPath() + ") not found."; + throw new BuildException( message ); + } + catch( IOException e ) + { + String message = "Property file (" + propertyFile.getPath() + ") cannot be loaded."; + throw new BuildException( message ); + } + + return properties; + } + + /** + * Nested <replacetoken> element. + * + * @return Description of the Returned Value + */ + public NestedString createReplaceToken() + { + if( token == null ) + { + token = new NestedString(); + } + return token; + } + + /** + * Nested <replacevalue> element. + * + * @return Description of the Returned Value + */ + public NestedString createReplaceValue() + { + return value; + } + + /** + * Add nested <replacefilter> element. + * + * @return Description of the Returned Value + */ + public Replacefilter createReplacefilter() + { + Replacefilter filter = new Replacefilter(); + replacefilters.addElement( filter ); + return filter; + } + + /** + * Do the execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + validateAttributes(); + + if( propertyFile != null ) + { + properties = getProperties( propertyFile ); + } + + validateReplacefilters(); + fileCount = 0; + replaceCount = 0; + + if( src != null ) + { + processFile( src ); + } + + if( dir != null ) + { + DirectoryScanner ds = super.getDirectoryScanner( dir ); + String[] srcs = ds.getIncludedFiles(); + + for( int i = 0; i < srcs.length; i++ ) + { + File file = new File( dir, srcs[i] ); + processFile( file ); + } + } + + if( summary ) + { + log( "Replaced " + replaceCount + " occurrences in " + fileCount + " files.", Project.MSG_INFO ); + } + } + + /** + * Validate attributes provided for this task in .xml build file. + * + * @exception BuildException if any supplied attribute is invalid or any + * mandatory attribute is missing + */ + public void validateAttributes() + throws BuildException + { + if( src == null && dir == null ) + { + String message = "Either the file or the dir attribute " + "must be specified"; + throw new BuildException( message, location ); + } + if( propertyFile != null && !propertyFile.exists() ) + { + String message = "Property file " + propertyFile.getPath() + " does not exist."; + throw new BuildException( message, location ); + } + if( token == null && replacefilters.size() == 0 ) + { + String message = "Either token or a nested replacefilter " + + "must be specified"; + throw new BuildException( message, location ); + } + if( token != null && "".equals( token.getText() ) ) + { + String message = "The token attribute must not be an empty string."; + throw new BuildException( message, location ); + } + } + + /** + * Validate nested elements. + * + * @exception BuildException if any supplied attribute is invalid or any + * mandatory attribute is missing + */ + public void validateReplacefilters() + throws BuildException + { + for( int i = 0; i < replacefilters.size(); i++ ) + { + Replacefilter element = ( Replacefilter )replacefilters.elementAt( i ); + element.validate(); + } + } + + /** + * Perform the replacement on the given file. The replacement is performed + * on a temporary file which then replaces the original file. + * + * @param src the source file + * @exception BuildException Description of Exception + */ + private void processFile( File src ) + throws BuildException + { + if( !src.exists() ) + { + throw new BuildException( "Replace: source file " + src.getPath() + " doesn't exist", location ); + } + + File temp = fileUtils.createTempFile( "rep", ".tmp", + fileUtils.getParentFile( src ) ); + + Reader reader = null; + Writer writer = null; + try + { + reader = encoding == null ? new FileReader( src ) + : new InputStreamReader( new FileInputStream( src ), encoding ); + writer = encoding == null ? new FileWriter( temp ) + : new OutputStreamWriter( new FileOutputStream( temp ), encoding ); + + BufferedReader br = new BufferedReader( reader ); + BufferedWriter bw = new BufferedWriter( writer ); + + // read the entire file into a StringBuffer + // size of work buffer may be bigger than needed + // when multibyte characters exist in the source file + // but then again, it might be smaller than needed on + // platforms like Windows where length can't be trusted + int fileLengthInBytes = ( int )( src.length() ); + StringBuffer tmpBuf = new StringBuffer( fileLengthInBytes ); + int readChar = 0; + int totread = 0; + while( true ) + { + readChar = br.read(); + if( readChar < 0 ) + { + break; + } + tmpBuf.append( ( char )readChar ); + totread++; + } + + // create a String so we can use indexOf + String buf = tmpBuf.toString(); + + //Preserve original string (buf) so we can compare the result + String newString = new String( buf ); + + if( token != null ) + { + // line separators in values and tokens are "\n" + // in order to compare with the file contents, replace them + // as needed + String linesep = System.getProperty( "line.separator" ); + String val = stringReplace( value.getText(), "\n", linesep ); + String tok = stringReplace( token.getText(), "\n", linesep ); + + // for each found token, replace with value + log( "Replacing in " + src.getPath() + ": " + token.getText() + " --> " + value.getText(), Project.MSG_VERBOSE ); + newString = stringReplace( newString, tok, val ); + } + + if( replacefilters.size() > 0 ) + { + newString = processReplacefilters( newString, src.getPath() ); + } + + boolean changes = !newString.equals( buf ); + if( changes ) + { + bw.write( newString, 0, newString.length() ); + bw.flush(); + } + + // cleanup + bw.close(); + writer = null; + br.close(); + reader = null; + + // If there were changes, move the new one to the old one; + // otherwise, delete the new one + if( changes ) + { + ++fileCount; + src.delete(); + temp.renameTo( src ); + temp = null; + } + } + catch( IOException ioe ) + { + throw new BuildException( "IOException in " + src + " - " + + ioe.getClass().getName() + ":" + ioe.getMessage(), ioe, location ); + } + finally + { + if( reader != null ) + { + try + { + reader.close(); + } + catch( IOException e ) + {} + } + if( writer != null ) + { + try + { + writer.close(); + } + catch( IOException e ) + {} + } + if( temp != null ) + { + temp.delete(); + } + } + + } + + private String processReplacefilters( String buffer, String filename ) + { + String newString = new String( buffer ); + + for( int i = 0; i < replacefilters.size(); i++ ) + { + Replacefilter filter = ( Replacefilter )replacefilters.elementAt( i ); + + //for each found token, replace with value + log( "Replacing in " + filename + ": " + filter.getToken() + " --> " + filter.getReplaceValue(), Project.MSG_VERBOSE ); + newString = stringReplace( newString, filter.getToken(), filter.getReplaceValue() ); + } + + return newString; + } + + /** + * Replace occurrences of str1 in string str with str2 + * + * @param str Description of Parameter + * @param str1 Description of Parameter + * @param str2 Description of Parameter + * @return Description of the Returned Value + */ + private String stringReplace( String str, String str1, String str2 ) + { + StringBuffer ret = new StringBuffer(); + int start = 0; + int found = str.indexOf( str1 ); + while( found >= 0 ) + { + // write everything up to the found str1 + if( found > start ) + { + ret.append( str.substring( start, found ) ); + } + + // write the replacement str2 + if( str2 != null ) + { + ret.append( str2 ); + } + + // search again + start = found + str1.length(); + found = str.indexOf( str1, start ); + ++replaceCount; + } + + // write the remaining characters + if( str.length() > start ) + { + ret.append( str.substring( start, str.length() ) ); + } + + return ret.toString(); + } + + //Inner class + public class NestedString + { + + private StringBuffer buf = new StringBuffer(); + + public String getText() + { + return buf.toString(); + } + + public void addText( String val ) + { + buf.append( val ); + } + } + + //Inner class + public class Replacefilter + { + private String property; + private String token; + private String value; + + public void setProperty( String property ) + { + this.property = property; + } + + public void setToken( String token ) + { + this.token = token; + } + + public void setValue( String value ) + { + this.value = value; + } + + public String getProperty() + { + return property; + } + + public String getReplaceValue() + { + if( property != null ) + { + return ( String )properties.getProperty( property ); + } + else if( value != null ) + { + return value; + } + else if( Replace.this.value != null ) + { + return Replace.this.value.getText(); + } + else + { + //Default is empty string + return new String( "" ); + } + } + + public String getToken() + { + return token; + } + + public String getValue() + { + return value; + } + + public void validate() + throws BuildException + { + //Validate mandatory attributes + if( token == null ) + { + String message = "token is a mandatory attribute " + "of replacefilter."; + throw new BuildException( message ); + } + + if( "".equals( token ) ) + { + String message = "The token attribute must not be an empty string."; + throw new BuildException( message ); + } + + //value and property are mutually exclusive attributes + if( ( value != null ) && ( property != null ) ) + { + String message = "Either value or property " + "can be specified, but a replacefilter " + "element cannot have both."; + throw new BuildException( message ); + } + + if( ( property != null ) ) + { + //the property attribute must have access to a property file + if( propertyFile == null ) + { + String message = "The replacefilter's property attribute " + "can only be used with the replacetask's " + "propertyFile attribute."; + throw new BuildException( message ); + } + + //Make sure property exists in property file + if( properties == null || + properties.getProperty( property ) == null ) + { + String message = "property \"" + property + "\" was not found in " + propertyFile.getPath(); + throw new BuildException( message ); + } + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rmic.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rmic.java new file mode 100644 index 000000000..e37fac08e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Rmic.java @@ -0,0 +1,688 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.rmi.Remote; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.rmic.RmicAdapter; +import org.apache.tools.ant.taskdefs.rmic.RmicAdapterFactory; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Task to compile RMI stubs and skeletons. This task can take the following + * arguments: + *

                + *
              • base: The base directory for the compiled stubs and skeletons + *
              • class: The name of the class to generate the stubs from + *
              • stubVersion: The version of the stub prototol to use (1.1, 1.2, + * compat) + *
              • sourceBase: The base directory for the generated stubs and skeletons + * + *
              • classpath: Additional classpath, appended before the system classpath + * + *
              • iiop: Generate IIOP compatable output + *
              • iiopopts: Include IIOP options + *
              • idl: Generate IDL output + *
              • idlopts: Include IDL options + *
              • includeantruntime + *
              • includejavaruntime + *
              • extdirs + *
              + * Of these arguments, base is required.

              + * + * If classname is specified then only that classname will be compiled. If it is + * absent, then base is traversed for classes according to patterns.

              + * + * + * + * @author duncan@x180.com + * @author ludovic.claude@websitewatchers.co.uk + * @author David Maclean david@cm.co.za + * @author Stefan Bodewig + * @author Takashi Okamoto tokamoto@rd.nttdata.co.jp + */ + +public class Rmic extends MatchingTask +{ + + private final static String FAIL_MSG + = "Rmic failed, messages should have been provided."; + private boolean verify = false; + private boolean filtering = false; + + private boolean iiop = false; + private boolean idl = false; + private boolean debug = false; + private boolean includeAntRuntime = true; + private boolean includeJavaRuntime = false; + + private Vector compileList = new Vector(); + + private ClassLoader loader = null; + + private File baseDir; + private String classname; + private Path compileClasspath; + private Path extdirs; + private String idlopts; + private String iiopopts; + private File sourceBase; + private String stubVersion; + + /** + * Sets the base directory to output generated class. + * + * @param base The new Base value + */ + public void setBase( File base ) + { + this.baseDir = base; + } + + /** + * Sets the class name to compile. + * + * @param classname The new Classname value + */ + public void setClassname( String classname ) + { + this.classname = classname; + } + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( compileClasspath == null ) + { + compileClasspath = classpath; + } + else + { + compileClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Sets the debug flag. + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Sets the extension directories that will be used during the compilation. + * + * @param extdirs The new Extdirs value + */ + public void setExtdirs( Path extdirs ) + { + if( this.extdirs == null ) + { + this.extdirs = extdirs; + } + else + { + this.extdirs.append( extdirs ); + } + } + + public void setFiltering( boolean filter ) + { + filtering = filter; + } + + /** + * Indicates that IDL output should be generated. This defaults to false if + * not set. + * + * @param idl The new Idl value + */ + public void setIdl( boolean idl ) + { + this.idl = idl; + } + + /** + * pass additional arguments for idl compile + * + * @param idlopts The new Idlopts value + */ + public void setIdlopts( String idlopts ) + { + this.idlopts = idlopts; + } + + /** + * Indicates that IIOP compatible stubs should be generated. This defaults + * to false if not set. + * + * @param iiop The new Iiop value + */ + public void setIiop( boolean iiop ) + { + this.iiop = iiop; + } + + /** + * pass additional arguments for iiop + * + * @param iiopopts The new Iiopopts value + */ + public void setIiopopts( String iiopopts ) + { + this.iiopopts = iiopopts; + } + + /** + * Include ant's own classpath in this task's classpath? + * + * @param include The new Includeantruntime value + */ + public void setIncludeantruntime( boolean include ) + { + includeAntRuntime = include; + } + + /** + * Sets whether or not to include the java runtime libraries to this task's + * classpath. + * + * @param include The new Includejavaruntime value + */ + public void setIncludejavaruntime( boolean include ) + { + includeJavaRuntime = include; + } + + /** + * Sets the source dirs to find the source java files. + * + * @param sourceBase The new SourceBase value + */ + public void setSourceBase( File sourceBase ) + { + this.sourceBase = sourceBase; + } + + /** + * Sets the stub version. + * + * @param stubVersion The new StubVersion value + */ + public void setStubVersion( String stubVersion ) + { + this.stubVersion = stubVersion; + } + + /** + * Indicates that the classes found by the directory match should be checked + * to see if they implement java.rmi.Remote. This defaults to false if not + * set. + * + * @param verify The new Verify value + */ + public void setVerify( boolean verify ) + { + this.verify = verify; + } + + /** + * Gets the base directory to output generated class. + * + * @return The Base value + */ + public File getBase() + { + return this.baseDir; + } + + /** + * Gets the class name to compile. + * + * @return The Classname value + */ + public String getClassname() + { + return classname; + } + + /** + * Gets the classpath. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return compileClasspath; + } + + public Vector getCompileList() + { + return compileList; + } + + /** + * Gets the debug flag. + * + * @return The Debug value + */ + public boolean getDebug() + { + return debug; + } + + /** + * Gets the extension directories that will be used during the compilation. + * + * @return The Extdirs value + */ + public Path getExtdirs() + { + return extdirs; + } + + /** + * Gets file list to compile. + * + * @return The FileList value + */ + public Vector getFileList() + { + return compileList; + } + + public boolean getFiltering() + { + return filtering; + } + + /* + * Gets IDL flags. + */ + public boolean getIdl() + { + return idl; + } + + /** + * Gets additional arguments for idl compile. + * + * @return The Idlopts value + */ + public String getIdlopts() + { + return idlopts; + } + + /** + * Gets iiop flags. + * + * @return The Iiop value + */ + public boolean getIiop() + { + return iiop; + } + + /** + * Gets additional arguments for iiop. + * + * @return The Iiopopts value + */ + public String getIiopopts() + { + return iiopopts; + } + + /** + * Gets whether or not the ant classpath is to be included in the task's + * classpath. + * + * @return The Includeantruntime value + */ + public boolean getIncludeantruntime() + { + return includeAntRuntime; + } + + /** + * Gets whether or not the java runtime should be included in this task's + * classpath. + * + * @return The Includejavaruntime value + */ + public boolean getIncludejavaruntime() + { + return includeJavaRuntime; + } + + /** + * Classloader for the user-specified classpath. + * + * @return The Loader value + */ + public ClassLoader getLoader() + { + return loader; + } + + /** + * Returns the topmost interface that extends Remote for a given class - if + * one exists. + * + * @param testClass Description of Parameter + * @return The RemoteInterface value + */ + public Class getRemoteInterface( Class testClass ) + { + if( Remote.class.isAssignableFrom( testClass ) ) + { + Class[] interfaces = testClass.getInterfaces(); + if( interfaces != null ) + { + for( int i = 0; i < interfaces.length; i++ ) + { + if( Remote.class.isAssignableFrom( interfaces[i] ) ) + { + return interfaces[i]; + } + } + } + } + return null; + } + + /** + * Gets the source dirs to find the source java files. + * + * @return The SourceBase value + */ + public File getSourceBase() + { + return sourceBase; + } + + public String getStubVersion() + { + return stubVersion; + } + + /** + * Get verify flag. + * + * @return The Verify value + */ + public boolean getVerify() + { + return verify; + } + + /** + * Load named class and test whether it can be rmic'ed + * + * @param classname Description of Parameter + * @return The ValidRmiRemote value + */ + public boolean isValidRmiRemote( String classname ) + { + try + { + Class testClass = loader.loadClass( classname ); + // One cannot RMIC an interface for "classic" RMI (JRMP) + if( testClass.isInterface() && !iiop && !idl ) + { + return false; + } + return isValidRmiRemote( testClass ); + } + catch( ClassNotFoundException e ) + { + log( "Unable to verify class " + classname + + ". It could not be found.", Project.MSG_WARN ); + } + catch( NoClassDefFoundError e ) + { + log( "Unable to verify class " + classname + + ". It is not defined.", Project.MSG_WARN ); + } + catch( Throwable t ) + { + log( "Unable to verify class " + classname + + ". Loading caused Exception: " + + t.getMessage(), Project.MSG_WARN ); + } + // we only get here if an exception has been thrown + return false; + } + + /** + * Creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + return compileClasspath.createPath(); + } + + /** + * Maybe creates a nested extdirs element. + * + * @return Description of the Returned Value + */ + public Path createExtdirs() + { + if( extdirs == null ) + { + extdirs = new Path( project ); + } + return extdirs.createPath(); + } + + public void execute() + throws BuildException + { + if( baseDir == null ) + { + throw new BuildException( "base attribute must be set!", location ); + } + if( !baseDir.exists() ) + { + throw new BuildException( "base does not exist!", location ); + } + + if( verify ) + { + log( "Verify has been turned on.", Project.MSG_INFO ); + } + + String compiler = project.getProperty( "build.rmic" ); + RmicAdapter adapter = RmicAdapterFactory.getRmic( compiler, this ); + + // now we need to populate the compiler adapter + adapter.setRmic( this ); + + Path classpath = adapter.getClasspath(); + loader = new AntClassLoader( project, classpath ); + + // scan base dirs to build up compile lists only if a + // specific classname is not given + if( classname == null ) + { + DirectoryScanner ds = this.getDirectoryScanner( baseDir ); + String[] files = ds.getIncludedFiles(); + scanDir( baseDir, files, adapter.getMapper() ); + } + else + { + // otherwise perform a timestamp comparison - at least + scanDir( baseDir, + new String[]{classname.replace( '.', File.separatorChar ) + ".class"}, + adapter.getMapper() ); + } + + int fileCount = compileList.size(); + if( fileCount > 0 ) + { + log( "RMI Compiling " + fileCount + + " class" + ( fileCount > 1 ? "es" : "" ) + " to " + baseDir, + Project.MSG_INFO ); + + // finally, lets execute the compiler!! + if( !adapter.execute() ) + { + throw new BuildException( FAIL_MSG, location ); + } + } + + /* + * Move the generated source file to the base directory. If + * base directory and sourcebase are the same, the generated + * sources are already in place. + */ + if( null != sourceBase && !baseDir.equals( sourceBase ) ) + { + if( idl ) + { + log( "Cannot determine sourcefiles in idl mode, ", + Project.MSG_WARN ); + log( "sourcebase attribute will be ignored.", Project.MSG_WARN ); + } + else + { + for( int j = 0; j < fileCount; j++ ) + { + moveGeneratedFile( baseDir, sourceBase, + ( String )compileList.elementAt( j ), + adapter ); + } + } + } + compileList.removeAllElements(); + } + + /** + * Scans the directory looking for class files to be compiled. The result is + * returned in the class variable compileList. + * + * @param baseDir Description of Parameter + * @param files Description of Parameter + * @param mapper Description of Parameter + */ + protected void scanDir( File baseDir, String files[], + FileNameMapper mapper ) + { + + String[] newFiles = files; + if( idl ) + { + log( "will leave uptodate test to rmic implementation in idl mode.", + Project.MSG_VERBOSE ); + } + else if( iiop + && iiopopts != null && iiopopts.indexOf( "-always" ) > -1 ) + { + log( "no uptodate test as -always option has been specified", + Project.MSG_VERBOSE ); + } + else + { + SourceFileScanner sfs = new SourceFileScanner( this ); + newFiles = sfs.restrict( files, baseDir, baseDir, mapper ); + } + + for( int i = 0; i < newFiles.length; i++ ) + { + String classname = newFiles[i].replace( File.separatorChar, '.' ); + classname = classname.substring( 0, classname.lastIndexOf( ".class" ) ); + compileList.addElement( classname ); + } + } + + /** + * Check to see if the class or (super)interfaces implement java.rmi.Remote. + * + * @param testClass Description of Parameter + * @return The ValidRmiRemote value + */ + private boolean isValidRmiRemote( Class testClass ) + { + return getRemoteInterface( testClass ) != null; + } + + /** + * Move the generated source file(s) to the base directory + * + * @param baseDir Description of Parameter + * @param sourceBaseFile Description of Parameter + * @param classname Description of Parameter + * @param adapter Description of Parameter + * @exception BuildException Description of Exception + */ + private void moveGeneratedFile( File baseDir, File sourceBaseFile, + String classname, + RmicAdapter adapter ) + throws BuildException + { + + String classFileName = + classname.replace( '.', File.separatorChar ) + ".class"; + String[] generatedFiles = + adapter.getMapper().mapFileName( classFileName ); + + for( int i = 0; i < generatedFiles.length; i++ ) + { + String sourceFileName = + classFileName.substring( 0, classFileName.length() - 6 ) + ".java"; + File oldFile = new File( baseDir, sourceFileName ); + File newFile = new File( sourceBaseFile, sourceFileName ); + try + { + project.copyFile( oldFile, newFile, filtering ); + oldFile.delete(); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + oldFile + " to " + + newFile + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + } + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SQLExec.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SQLExec.java new file mode 100644 index 000000000..27acf92a0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SQLExec.java @@ -0,0 +1,867 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.Reader; +import java.io.StringReader; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Driver; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.Enumeration; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Reads in a text file containing SQL statements seperated with semicolons and + * executes it in a given db. Comments may be created with REM -- or //. + * + * @author Jeff Martin + * @author Michael McCallum + * @author Tim Stephenson + */ +public class SQLExec extends Task +{ + + private int goodSql = 0, totalSql = 0; + + private Vector filesets = new Vector(); + + /** + * Database connection + */ + private Connection conn = null; + + /** + * Autocommit flag. Default value is false + */ + private boolean autocommit = false; + + /** + * SQL statement + */ + private Statement statement = null; + + /** + * DB driver. + */ + private String driver = null; + + /** + * DB url. + */ + private String url = null; + + /** + * User name. + */ + private String userId = null; + + /** + * Password + */ + private String password = null; + + /** + * SQL input file + */ + private File srcFile = null; + + /** + * SQL input command + */ + private String sqlCommand = ""; + + /** + * SQL transactions to perform + */ + private Vector transactions = new Vector(); + + /** + * SQL Statement delimiter + */ + private String delimiter = ";"; + + /** + * The delimiter type indicating whether the delimiter will only be + * recognized on a line by itself + */ + private String delimiterType = DelimiterType.NORMAL; + + /** + * Print SQL results. + */ + private boolean print = false; + + /** + * Print header columns. + */ + private boolean showheaders = true; + + /** + * Results Output file. + */ + private File output = null; + + /** + * RDBMS Product needed for this SQL. + */ + private String rdbms = null; + + /** + * RDBMS Version needed for this SQL. + */ + private String version = null; + + /** + * Action to perform if an error is found + */ + private String onError = "abort"; + + /** + * Encoding to use when reading SQL statements from a file + */ + private String encoding = null; + + private Path classpath; + + private AntClassLoader loader; + + /** + * Set the autocommit flag for the DB connection. + * + * @param autocommit The new Autocommit value + */ + public void setAutocommit( boolean autocommit ) + { + this.autocommit = autocommit; + } + + /** + * Set the classpath for loading the driver. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + /** + * Set the classpath for loading the driver using the classpath reference. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Set the statement delimiter.

              + * + * For example, set this to "go" and delimitertype to "ROW" for Sybase ASE + * or MS SQL Server.

              + * + * @param delimiter The new Delimiter value + */ + public void setDelimiter( String delimiter ) + { + this.delimiter = delimiter; + } + + /** + * Set the Delimiter type for this sql task. The delimiter type takes two + * values - normal and row. Normal means that any occurence of the delimiter + * terminate the SQL command whereas with row, only a line containing just + * the delimiter is recognized as the end of the command. + * + * @param delimiterType The new DelimiterType value + */ + public void setDelimiterType( DelimiterType delimiterType ) + { + this.delimiterType = delimiterType.getValue(); + } + + /** + * Set the JDBC driver to be used. + * + * @param driver The new Driver value + */ + public void setDriver( String driver ) + { + this.driver = driver; + } + + /** + * Set the file encoding to use on the sql files read in + * + * @param encoding the encoding to use on the files + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Set the action to perform onerror + * + * @param action The new Onerror value + */ + public void setOnerror( OnError action ) + { + this.onError = action.getValue(); + } + + /** + * Set the output file. + * + * @param output The new Output value + */ + public void setOutput( File output ) + { + this.output = output; + } + + + /** + * Set the password for the DB connection. + * + * @param password The new Password value + */ + public void setPassword( String password ) + { + this.password = password; + } + + /** + * Set the print flag. + * + * @param print The new Print value + */ + public void setPrint( boolean print ) + { + this.print = print; + } + + /** + * Set the rdbms required + * + * @param vendor The new Rdbms value + */ + public void setRdbms( String vendor ) + { + this.rdbms = vendor.toLowerCase(); + } + + /** + * Set the showheaders flag. + * + * @param showheaders The new Showheaders value + */ + public void setShowheaders( boolean showheaders ) + { + this.showheaders = showheaders; + } + + /** + * Set the name of the sql file to be run. + * + * @param srcFile The new Src value + */ + public void setSrc( File srcFile ) + { + this.srcFile = srcFile; + } + + /** + * Set the DB connection url. + * + * @param url The new Url value + */ + public void setUrl( String url ) + { + this.url = url; + } + + /** + * Set the user name for the DB connection. + * + * @param userId The new Userid value + */ + public void setUserid( String userId ) + { + this.userId = userId; + } + + /** + * Set the version required + * + * @param version The new Version value + */ + public void setVersion( String version ) + { + this.version = version.toLowerCase(); + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Set the sql command to execute + * + * @param sql The feature to be added to the Text attribute + */ + public void addText( String sql ) + { + this.sqlCommand += sql; + } + + /** + * Create the classpath for loading the driver. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + + /** + * Set the sql command to execute + * + * @return Description of the Returned Value + */ + public Transaction createTransaction() + { + Transaction t = new Transaction(); + transactions.addElement( t ); + return t; + } + + /** + * Load the sql file and then execute it + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + sqlCommand = sqlCommand.trim(); + + if( srcFile == null && sqlCommand.length() == 0 && filesets.isEmpty() ) + { + if( transactions.size() == 0 ) + { + throw new BuildException( "Source file or fileset, transactions or sql statement must be set!", location ); + } + } + else + { + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File srcDir = fs.getDir( project ); + + String[] srcFiles = ds.getIncludedFiles(); + + // Make a transaction for each file + for( int j = 0; j < srcFiles.length; j++ ) + { + Transaction t = createTransaction(); + t.setSrc( new File( srcDir, srcFiles[j] ) ); + } + } + + // Make a transaction group for the outer command + Transaction t = createTransaction(); + t.setSrc( srcFile ); + t.addText( sqlCommand ); + } + + if( driver == null ) + { + throw new BuildException( "Driver attribute must be set!", location ); + } + if( userId == null ) + { + throw new BuildException( "User Id attribute must be set!", location ); + } + if( password == null ) + { + throw new BuildException( "Password attribute must be set!", location ); + } + if( url == null ) + { + throw new BuildException( "Url attribute must be set!", location ); + } + if( srcFile != null && !srcFile.exists() ) + { + throw new BuildException( "Source file does not exist!", location ); + } + Driver driverInstance = null; + // Load the driver using the + try + { + Class dc; + if( classpath != null ) + { + log( "Loading " + driver + " using AntClassLoader with classpath " + classpath, + Project.MSG_VERBOSE ); + + loader = new AntClassLoader( project, classpath ); + dc = loader.loadClass( driver ); + } + else + { + log( "Loading " + driver + " using system loader.", Project.MSG_VERBOSE ); + dc = Class.forName( driver ); + } + driverInstance = ( Driver )dc.newInstance(); + } + catch( ClassNotFoundException e ) + { + throw new BuildException( "Class Not Found: JDBC driver " + driver + " could not be loaded", location ); + } + catch( IllegalAccessException e ) + { + throw new BuildException( "Illegal Access: JDBC driver " + driver + " could not be loaded", location ); + } + catch( InstantiationException e ) + { + throw new BuildException( "Instantiation Exception: JDBC driver " + driver + " could not be loaded", location ); + } + + try + { + log( "connecting to " + url, Project.MSG_VERBOSE ); + Properties info = new Properties(); + info.put( "user", userId ); + info.put( "password", password ); + conn = driverInstance.connect( url, info ); + + if( conn == null ) + { + // Driver doesn't understand the URL + throw new SQLException( "No suitable Driver for " + url ); + } + + if( !isValidRdbms( conn ) ) + return; + + conn.setAutoCommit( autocommit ); + + statement = conn.createStatement(); + + PrintStream out = System.out; + try + { + if( output != null ) + { + log( "Opening PrintStream to output file " + output, Project.MSG_VERBOSE ); + out = new PrintStream( new BufferedOutputStream( new FileOutputStream( output ) ) ); + } + + // Process all transactions + for( Enumeration e = transactions.elements(); + e.hasMoreElements(); ) + { + + ( ( Transaction )e.nextElement() ).runTransaction( out ); + if( !autocommit ) + { + log( "Commiting transaction", Project.MSG_VERBOSE ); + conn.commit(); + } + } + } + finally + { + if( out != null && out != System.out ) + { + out.close(); + } + } + } + catch( IOException e ) + { + if( !autocommit && conn != null && onError.equals( "abort" ) ) + { + try + { + conn.rollback(); + } + catch( SQLException ex ) + {} + } + throw new BuildException( e ); + } + catch( SQLException e ) + { + if( !autocommit && conn != null && onError.equals( "abort" ) ) + { + try + { + conn.rollback(); + } + catch( SQLException ex ) + {} + } + throw new BuildException( e ); + } + finally + { + try + { + if( statement != null ) + { + statement.close(); + } + if( conn != null ) + { + conn.close(); + } + } + catch( SQLException e ) + {} + } + + log( goodSql + " of " + totalSql + + " SQL statements executed successfully" ); + } + + /** + * Verify if connected to the correct RDBMS + * + * @param conn Description of Parameter + * @return The ValidRdbms value + */ + protected boolean isValidRdbms( Connection conn ) + { + if( rdbms == null && version == null ) + return true; + + try + { + DatabaseMetaData dmd = conn.getMetaData(); + + if( rdbms != null ) + { + String theVendor = dmd.getDatabaseProductName().toLowerCase(); + + log( "RDBMS = " + theVendor, Project.MSG_VERBOSE ); + if( theVendor == null || theVendor.indexOf( rdbms ) < 0 ) + { + log( "Not the required RDBMS: " + rdbms, Project.MSG_VERBOSE ); + return false; + } + } + + if( version != null ) + { + String theVersion = dmd.getDatabaseProductVersion().toLowerCase(); + + log( "Version = " + theVersion, Project.MSG_VERBOSE ); + if( theVersion == null || + !( theVersion.startsWith( version ) || + theVersion.indexOf( " " + version ) >= 0 ) ) + { + log( "Not the required version: \"" + version + "\"", Project.MSG_VERBOSE ); + return false; + } + } + } + catch( SQLException e ) + { + // Could not get the required information + log( "Failed to obtain required RDBMS information", Project.MSG_ERR ); + return false; + } + + return true; + } + + /** + * Exec the sql statement. + * + * @param sql Description of Parameter + * @param out Description of Parameter + * @exception SQLException Description of Exception + */ + protected void execSQL( String sql, PrintStream out ) + throws SQLException + { + // Check and ignore empty statements + if( "".equals( sql.trim() ) ) + return; + + try + { + totalSql++; + if( !statement.execute( sql ) ) + { + log( statement.getUpdateCount() + " rows affected", + Project.MSG_VERBOSE ); + } + else + { + if( print ) + { + printResults( out ); + } + } + + SQLWarning warning = conn.getWarnings(); + while( warning != null ) + { + log( warning + " sql warning", Project.MSG_VERBOSE ); + warning = warning.getNextWarning(); + } + conn.clearWarnings(); + goodSql++; + } + catch( SQLException e ) + { + log( "Failed to execute: " + sql, Project.MSG_ERR ); + if( !onError.equals( "continue" ) ) + throw e; + log( e.toString(), Project.MSG_ERR ); + } + } + + /** + * print any results in the statement. + * + * @param out Description of Parameter + * @exception java.sql.SQLException Description of Exception + */ + protected void printResults( PrintStream out ) + throws java.sql.SQLException + { + ResultSet rs = null; + do + { + rs = statement.getResultSet(); + if( rs != null ) + { + log( "Processing new result set.", Project.MSG_VERBOSE ); + ResultSetMetaData md = rs.getMetaData(); + int columnCount = md.getColumnCount(); + StringBuffer line = new StringBuffer(); + if( showheaders ) + { + for( int col = 1; col < columnCount; col++ ) + { + line.append( md.getColumnName( col ) ); + line.append( "," ); + } + line.append( md.getColumnName( columnCount ) ); + out.println( line ); + line.setLength( 0 ); + } + while( rs.next() ) + { + boolean first = true; + for( int col = 1; col <= columnCount; col++ ) + { + String columnValue = rs.getString( col ); + if( columnValue != null ) + { + columnValue = columnValue.trim(); + } + + if( first ) + { + first = false; + } + else + { + line.append( "," ); + } + line.append( columnValue ); + } + out.println( line ); + line.setLength( 0 ); + } + } + }while ( statement.getMoreResults() ); + out.println(); + } + + protected void runStatements( Reader reader, PrintStream out ) + throws SQLException, IOException + { + String sql = ""; + String line = ""; + + BufferedReader in = new BufferedReader( reader ); + + try + { + while( ( line = in.readLine() ) != null ) + { + line = line.trim(); + line = project.replaceProperties( line ); + if( line.startsWith( "//" ) ) + continue; + if( line.startsWith( "--" ) ) + continue; + StringTokenizer st = new StringTokenizer( line ); + if( st.hasMoreTokens() ) + { + String token = st.nextToken(); + if( "REM".equalsIgnoreCase( token ) ) + { + continue; + } + } + + sql += " " + line; + sql = sql.trim(); + + // SQL defines "--" as a comment to EOL + // and in Oracle it may contain a hint + // so we cannot just remove it, instead we must end it + if( line.indexOf( "--" ) >= 0 ) + sql += "\n"; + + if( delimiterType.equals( DelimiterType.NORMAL ) && sql.endsWith( delimiter ) || + delimiterType.equals( DelimiterType.ROW ) && line.equals( delimiter ) ) + { + log( "SQL: " + sql, Project.MSG_VERBOSE ); + execSQL( sql.substring( 0, sql.length() - delimiter.length() ), out ); + sql = ""; + } + } + + // Catch any statements not followed by ; + if( !sql.equals( "" ) ) + { + execSQL( sql, out ); + } + } + catch( SQLException e ) + { + throw e; + } + + } + + public static class DelimiterType extends EnumeratedAttribute + { + public final static String NORMAL = "normal"; + public final static String ROW = "row"; + + public String[] getValues() + { + return new String[]{NORMAL, ROW}; + } + } + + /** + * Enumerated attribute with the values "continue", "stop" and "abort" for + * the onerror attribute. + * + * @author RT + */ + public static class OnError extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"continue", "stop", "abort"}; + } + } + + /** + * Contains the definition of a new transaction element. Transactions allow + * several files or blocks of statements to be executed using the same JDBC + * connection and commit operation in between. + * + * @author RT + */ + public class Transaction + { + private File tSrcFile = null; + private String tSqlCommand = ""; + + public void setSrc( File src ) + { + this.tSrcFile = src; + } + + public void addText( String sql ) + { + this.tSqlCommand += sql; + } + + private void runTransaction( PrintStream out ) + throws IOException, SQLException + { + if( tSqlCommand.length() != 0 ) + { + log( "Executing commands", Project.MSG_INFO ); + runStatements( new StringReader( tSqlCommand ), out ); + } + + if( tSrcFile != null ) + { + log( "Executing file: " + tSrcFile.getAbsolutePath(), + Project.MSG_INFO ); + Reader reader = ( encoding == null ) ? new FileReader( tSrcFile ) + : new InputStreamReader( new FileInputStream( tSrcFile ), encoding ); + runStatements( reader, out ); + reader.close(); + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SendEmail.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SendEmail.java new file mode 100644 index 000000000..052ed4940 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SendEmail.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.mail.MailMessage; + +/** + * A task to send SMTP email.

              + * + * + * + * + * + * + * + * Attribute + * + * + * + * Description + * + * + * + * Required + * + * + * + * + * + * + * + * from + * + * + * + * Email address of sender. + * + * + * + * Yes + * + * + * + * + * + * + * + * mailhost + * + * + * + * Host name of the mail server. + * + * + * + * No, default to "localhost" + * + * + * + * + * + * + * + * toList + * + * + * + * Comma-separated list of recipients. + * + * + * + * Yes + * + * + * + * + * + * + * + * subject + * + * + * + * Email subject line. + * + * + * + * No + * + * + * + * + * + * + * + * files + * + * + * + * Filename(s) of text to send in the body of the email. Multiple files + * are comma-separated. + * + * + * + * One of these two attributes + * + * + * + * + * + * + * + * message + * + * + * + * Message to send inthe body of the email. + * + * + * + * + * + * + * + * + * + * includefilenames + * + * + * + * Includes filenames before file contents when set to true. + * + * + * + * No, default is false + * + * + * + *

              + * + * + * + * @author glenn_twiggs@bmc.com + * @author Magesh Umasankar + */ +public class SendEmail extends Task +{ + private String mailhost = "localhost"; + private int mailport = MailMessage.DEFAULT_PORT; + private Vector files = new Vector(); + /** + * failure flag + */ + private boolean failOnError = true; + private String from; + private boolean includefilenames; + private String message; + private String subject; + private String toList; + + + /** + * Creates new SendEmail + */ + public SendEmail() { } + + /** + * Sets the FailOnError attribute of the MimeMail object + * + * @param failOnError The new FailOnError value + * @since 1.5 + */ + public void setFailOnError( boolean failOnError ) + { + this.failOnError = failOnError; + } + + /** + * Sets the file parameter of this build task. + * + * @param filenames Filenames to include as the message body of this email. + */ + public void setFiles( String filenames ) + { + StringTokenizer t = new StringTokenizer( filenames, ", " ); + + while( t.hasMoreTokens() ) + { + files.addElement( project.resolveFile( t.nextToken() ) ); + } + } + + /** + * Sets the from parameter of this build task. + * + * @param from Email address of sender. + */ + public void setFrom( String from ) + { + this.from = from; + } + + /** + * Sets Includefilenames attribute + * + * @param includefilenames Set to true if file names are to be included. + * @since 1.5 + */ + public void setIncludefilenames( boolean includefilenames ) + { + this.includefilenames = includefilenames; + } + + /** + * Sets the mailhost parameter of this build task. + * + * @param mailhost Mail host name. + */ + public void setMailhost( String mailhost ) + { + this.mailhost = mailhost; + } + + /** + * Sets the mailport parameter of this build task. + * + * @param value mail port name. + */ + public void setMailport( Integer value ) + { + this.mailport = value.intValue(); + } + + /** + * Sets the message parameter of this build task. + * + * @param message Message body of this email. + */ + public void setMessage( String message ) + { + this.message = message; + } + + /** + * Sets the subject parameter of this build task. + * + * @param subject Subject of this email. + */ + public void setSubject( String subject ) + { + this.subject = subject; + } + + /** + * Sets the toList parameter of this build task. + * + * @param toList Comma-separated list of email recipient addreses. + */ + public void setToList( String toList ) + { + this.toList = toList; + } + + /** + * Executes this build task. + * + * @throws BuildException if there is an error during task execution. + */ + public void execute() + throws BuildException + { + try + { + MailMessage mailMessage = new MailMessage( mailhost ); + mailMessage.setPort( mailport ); + + if( from != null ) + { + mailMessage.from( from ); + } + else + { + throw new BuildException( "Attribute \"from\" is required." ); + } + + if( toList != null ) + { + StringTokenizer t = new StringTokenizer( toList, ", ", false ); + + while( t.hasMoreTokens() ) + { + mailMessage.to( t.nextToken() ); + } + } + else + { + throw new BuildException( "Attribute \"toList\" is required." ); + } + + if( subject != null ) + { + mailMessage.setSubject( subject ); + } + + if( !files.isEmpty() ) + { + PrintStream out = mailMessage.getPrintStream(); + + for( Enumeration e = files.elements(); e.hasMoreElements(); ) + { + File file = ( File )e.nextElement(); + + if( file.exists() && file.canRead() ) + { + int bufsize = 1024; + int length; + byte[] buf = new byte[bufsize]; + if( includefilenames ) + { + String filename = file.getName(); + int filenamelength = filename.length(); + out.println( filename ); + for( int star = 0; star < filenamelength; star++ ) + { + out.print( '=' ); + } + out.println(); + } + BufferedInputStream in = null; + try + { + in = new BufferedInputStream( + new FileInputStream( file ), bufsize ); + while( ( length = in.read( buf, 0, bufsize ) ) != -1 ) + { + out.write( buf, 0, length ); + } + if( includefilenames ) + { + out.println(); + } + } + finally + { + if( in != null ) + { + try + { + in.close(); + } + catch( IOException ioe ) + {} + } + } + + } + else + { + throw new BuildException( "File \"" + file.getName() + + "\" does not exist or is not readable." ); + } + } + } + else if( message != null ) + { + PrintStream out = mailMessage.getPrintStream(); + out.print( message ); + } + else + { + throw new BuildException( "Attribute \"file\" or \"message\" is required." ); + } + + log( "Sending email" ); + mailMessage.sendAndClose(); + } + catch( IOException ioe ) + { + String err = "IO error sending mail " + ioe.toString(); + if( failOnError ) + { + throw new BuildException( err, ioe, location ); + } + else + { + log( err, Project.MSG_ERR ); + } + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sequential.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sequential.java new file mode 100644 index 000000000..59fa575be --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sequential.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.TaskContainer; + + +/** + * Implements a single threaded task execution.

              + * + * + * + * @author Thomas Christen chr@active.ch + */ +public class Sequential extends Task + implements TaskContainer +{ + + /** + * Optional Vector holding the nested tasks + */ + private Vector nestedTasks = new Vector(); + + /** + * Add a nested task to Sequential.

              + * + * + * + * @param nestedTask Nested task to execute Sequential

              + * + * + */ + public void addTask( Task nestedTask ) + { + nestedTasks.addElement( nestedTask ); + } + + /** + * Execute all nestedTasks. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + for( Enumeration e = nestedTasks.elements(); e.hasMoreElements(); ) + { + Task nestedTask = ( Task )e.nextElement(); + nestedTask.perform(); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SignJar.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SignJar.java new file mode 100644 index 000000000..29ffddd1a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/SignJar.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; + +/** + * Sign a archive. + * + * @author Peter Donald donaldp@apache.org + * + * @author Nick Fortescue + * nick@ox.compsoc.net + */ +public class SignJar extends Task +{ + + /** + * the filesets of the jars to sign + */ + protected Vector filesets = new Vector(); + + /** + * The alias of signer. + */ + protected String alias; + protected boolean internalsf; + + /** + * The name of the jar file. + */ + protected File jar; + protected String keypass; + + /** + * The name of keystore file. + */ + protected File keystore; + /** + * Whether to assume a jar which has an appropriate .SF file in is already + * signed. + */ + protected boolean lazy; + protected boolean sectionsonly; + protected File sigfile; + protected File signedjar; + + protected String storepass; + protected String storetype; + protected boolean verbose; + + public void setAlias( final String alias ) + { + this.alias = alias; + } + + public void setInternalsf( final boolean internalsf ) + { + this.internalsf = internalsf; + } + + public void setJar( final File jar ) + { + this.jar = jar; + } + + public void setKeypass( final String keypass ) + { + this.keypass = keypass; + } + + public void setKeystore( final File keystore ) + { + this.keystore = keystore; + } + + public void setLazy( final boolean lazy ) + { + this.lazy = lazy; + } + + public void setSectionsonly( final boolean sectionsonly ) + { + this.sectionsonly = sectionsonly; + } + + public void setSigfile( final File sigfile ) + { + this.sigfile = sigfile; + } + + public void setSignedjar( final File signedjar ) + { + this.signedjar = signedjar; + } + + public void setStorepass( final String storepass ) + { + this.storepass = storepass; + } + + public void setStoretype( final String storetype ) + { + this.storetype = storetype; + } + + public void setVerbose( final boolean verbose ) + { + this.verbose = verbose; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( final FileSet set ) + { + filesets.addElement( set ); + } + + + public void execute() + throws BuildException + { + if( null == jar && null == filesets ) + { + throw new BuildException( "jar must be set through jar attribute or nested filesets" ); + } + if( null != jar ) + { + doOneJar( jar, signedjar ); + return; + } + else + { + //Assume null != filesets + + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] jarFiles = ds.getIncludedFiles(); + for( int j = 0; j < jarFiles.length; j++ ) + { + doOneJar( new File( fs.getDir( project ), jarFiles[j] ), null ); + } + } + } + } + + protected boolean isSigned( File file ) + { + final String SIG_START = "META-INF/"; + final String SIG_END = ".SF"; + + if( !file.exists() ) + { + return false; + } + ZipFile jarFile = null; + try + { + jarFile = new ZipFile( file ); + if( null == alias ) + { + Enumeration entries = jarFile.entries(); + while( entries.hasMoreElements() ) + { + String name = ( ( ZipEntry )entries.nextElement() ).getName(); + if( name.startsWith( SIG_START ) && name.endsWith( SIG_END ) ) + { + return true; + } + } + return false; + } + else + { + return jarFile.getEntry( SIG_START + alias.toUpperCase() + + SIG_END ) != null; + } + } + catch( IOException e ) + { + return false; + } + finally + { + if( jarFile != null ) + { + try + { + jarFile.close(); + } + catch( IOException e ) + {} + } + } + } + + protected boolean isUpToDate( File jarFile, File signedjarFile ) + { + if( null == jarFile ) + { + return false; + } + + if( null != signedjarFile ) + { + + if( !jarFile.exists() ) + return false; + if( !signedjarFile.exists() ) + return false; + if( jarFile.equals( signedjarFile ) ) + return false; + if( signedjarFile.lastModified() > jarFile.lastModified() ) + return true; + } + else + { + if( lazy ) + { + return isSigned( jarFile ); + } + } + + return false; + } + + private void doOneJar( File jarSource, File jarTarget ) + throws BuildException + { + if( project.getJavaVersion().equals( Project.JAVA_1_1 ) ) + { + throw new BuildException( "The signjar task is only available on JDK versions 1.2 or greater" ); + } + + if( null == alias ) + { + throw new BuildException( "alias attribute must be set" ); + } + + if( null == storepass ) + { + throw new BuildException( "storepass attribute must be set" ); + } + + if( isUpToDate( jarSource, jarTarget ) ) + return; + + final StringBuffer sb = new StringBuffer(); + + final ExecTask cmd = ( ExecTask )project.createTask( "exec" ); + cmd.setExecutable( "jarsigner" ); + + if( null != keystore ) + { + cmd.createArg().setValue( "-keystore" ); + cmd.createArg().setValue( keystore.toString() ); + } + + if( null != storepass ) + { + cmd.createArg().setValue( "-storepass" ); + cmd.createArg().setValue( storepass ); + } + + if( null != storetype ) + { + cmd.createArg().setValue( "-storetype" ); + cmd.createArg().setValue( storetype ); + } + + if( null != keypass ) + { + cmd.createArg().setValue( "-keypass" ); + cmd.createArg().setValue( keypass ); + } + + if( null != sigfile ) + { + cmd.createArg().setValue( "-sigfile" ); + cmd.createArg().setValue( sigfile.toString() ); + } + + if( null != jarTarget ) + { + cmd.createArg().setValue( "-signedjar" ); + cmd.createArg().setValue( jarTarget.toString() ); + } + + if( verbose ) + { + cmd.createArg().setValue( "-verbose" ); + } + + if( internalsf ) + { + cmd.createArg().setValue( "-internalsf" ); + } + + if( sectionsonly ) + { + cmd.createArg().setValue( "-sectionsonly" ); + } + + cmd.createArg().setValue( jarSource.toString() ); + + cmd.createArg().setValue( alias ); + + log( "Signing Jar : " + jarSource.getAbsolutePath() ); + cmd.setFailonerror( true ); + cmd.setTaskName( getTaskName() ); + cmd.execute(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sleep.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sleep.java new file mode 100644 index 000000000..2c8d81c86 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Sleep.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * A task to sleep for a period of time + * + * @author steve_l@iseran.com steve loughran + * @created 01 May 2001 + */ + +public class Sleep extends Task +{ + /** + * failure flag + */ + private boolean failOnError = true; + + /** + * Description of the Field + */ + private int seconds = 0; + /** + * Description of the Field + */ + private int hours = 0; + /** + * Description of the Field + */ + private int minutes = 0; + /** + * Description of the Field + */ + private int milliseconds = 0; + + + /** + * Creates new instance + */ + public Sleep() { } + + + /** + * Sets the FailOnError attribute of the MimeMail object + * + * @param failOnError The new FailOnError value + */ + public void setFailOnError( boolean failOnError ) + { + this.failOnError = failOnError; + } + + + /** + * Sets the Hours attribute of the Sleep object + * + * @param hours The new Hours value + */ + public void setHours( int hours ) + { + this.hours = hours; + } + + + /** + * Sets the Milliseconds attribute of the Sleep object + * + * @param milliseconds The new Milliseconds value + */ + public void setMilliseconds( int milliseconds ) + { + this.milliseconds = milliseconds; + } + + + /** + * Sets the Minutes attribute of the Sleep object + * + * @param minutes The new Minutes value + */ + public void setMinutes( int minutes ) + { + this.minutes = minutes; + } + + + /** + * Sets the Seconds attribute of the Sleep object + * + * @param seconds The new Seconds value + */ + public void setSeconds( int seconds ) + { + this.seconds = seconds; + } + + + /** + * sleep for a period of time + * + * @param millis time to sleep + */ + public void doSleep( long millis ) + { + try + { + Thread.currentThread().sleep( millis ); + } + catch( InterruptedException ie ) + { + } + } + + + /** + * Executes this build task. throws org.apache.tools.ant.BuildException if + * there is an error during task execution. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + try + { + validate(); + long sleepTime = getSleepTime(); + log( "sleeping for " + sleepTime + " milliseconds", + Project.MSG_VERBOSE ); + doSleep( sleepTime ); + } + catch( Exception e ) + { + if( failOnError ) + { + throw new BuildException( e ); + } + else + { + String text = e.toString(); + log( text, Project.MSG_ERR ); + } + } + } + + + /** + * verify parameters + * + * @throws BuildException if something is invalid + */ + public void validate() + throws BuildException + { + long sleepTime = getSleepTime(); + if( getSleepTime() < 0 ) + { + throw new BuildException( "Negative sleep periods are not supported" ); + } + } + + + /** + * return time to sleep + * + * @return sleep time. if below 0 then there is an error + */ + + private long getSleepTime() + { + return ( ( ( ( long )hours * 60 ) + minutes ) * 60 + seconds ) * 1000 + milliseconds; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/StreamPumper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/StreamPumper.java new file mode 100644 index 000000000..8e8a8e9ef --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/StreamPumper.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Copies all data from an input stream to an output stream. + * + * @author thomas.haas@softwired-inc.com + */ +public class StreamPumper implements Runnable +{ + + // TODO: make SIZE and SLEEP instance variables. + // TODO: add a status flag to note if an error occured in run. + + private final static int SLEEP = 5; + private final static int SIZE = 128; + private InputStream is; + private OutputStream os; + + + /** + * Create a new stream pumper. + * + * @param is input stream to read data from + * @param os output stream to write data to. + */ + public StreamPumper( InputStream is, OutputStream os ) + { + this.is = is; + this.os = os; + } + + + /** + * Copies data from the input stream to the output stream. Terminates as + * soon as the input stream is closed or an error occurs. + */ + public void run() + { + final byte[] buf = new byte[SIZE]; + + int length; + try + { + while( ( length = is.read( buf ) ) > 0 ) + { + os.write( buf, 0, length ); + try + { + Thread.sleep( SLEEP ); + } + catch( InterruptedException e ) + {} + } + } + catch( IOException e ) + {} + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tar.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tar.java new file mode 100644 index 000000000..2f3fe3cdc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tar.java @@ -0,0 +1,481 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.SourceFileScanner; +import org.apache.tools.tar.TarConstants; +import org.apache.tools.tar.TarEntry; +import org.apache.tools.tar.TarOutputStream; + +/** + * Creates a TAR archive. + * + * @author Stefano Mazzocchi + * stefano@apache.org + * @author Stefan Bodewig + * @author Magesh Umasankar + */ + +public class Tar extends MatchingTask +{ + + /** + * @deprecated Tar.WARN is deprecated and is replaced with + * Tar.TarLongFileMode.WARN + */ + public final static String WARN = "warn"; + /** + * @deprecated Tar.FAIL is deprecated and is replaced with + * Tar.TarLongFileMode.FAIL + */ + public final static String FAIL = "fail"; + /** + * @deprecated Tar.TRUNCATE is deprecated and is replaced with + * Tar.TarLongFileMode.TRUNCATE + */ + public final static String TRUNCATE = "truncate"; + /** + * @deprecated Tar.GNU is deprecated and is replaced with + * Tar.TarLongFileMode.GNU + */ + public final static String GNU = "gnu"; + /** + * @deprecated Tar.OMIT is deprecated and is replaced with + * Tar.TarLongFileMode.OMIT + */ + public final static String OMIT = "omit"; + + private TarLongFileMode longFileMode = new TarLongFileMode(); + + Vector filesets = new Vector(); + Vector fileSetFiles = new Vector(); + + /** + * Indicates whether the user has been warned about long files already. + */ + private boolean longWarningGiven = false; + File baseDir; + + File tarFile; + + /** + * This is the base directory to look in for things to tar. + * + * @param baseDir The new Basedir value + */ + public void setBasedir( File baseDir ) + { + this.baseDir = baseDir; + } + + /** + * Set how to handle long files. Allowable values are truncate - paths are + * truncated to the maximum length fail - paths greater than the maximim + * cause a build exception warn - paths greater than the maximum cause a + * warning and GNU is used gnu - GNU extensions are used for any paths + * greater than the maximum. omit - paths greater than the maximum are + * omitted from the archive + * + * @param mode The new Longfile value + * @deprecated setLongFile(String) is deprecated and is replaced with + * setLongFile(Tar.TarLongFileMode) to make Ant's Introspection + * mechanism do the work and also to encapsulate operations on the mode + * in its own class. + */ + public void setLongfile( String mode ) + { + log( "DEPRECATED - The setLongfile(String) method has been deprecated." + + " Use setLongfile(Tar.TarLongFileMode) instead." ); + this.longFileMode = new TarLongFileMode(); + longFileMode.setValue( mode ); + } + + /** + * Set how to handle long files. Allowable values are truncate - paths are + * truncated to the maximum length fail - paths greater than the maximim + * cause a build exception warn - paths greater than the maximum cause a + * warning and GNU is used gnu - GNU extensions are used for any paths + * greater than the maximum. omit - paths greater than the maximum are + * omitted from the archive + * + * @param mode The new Longfile value + */ + public void setLongfile( TarLongFileMode mode ) + { + this.longFileMode = mode; + } + + + /** + * This is the name/location of where to create the tar file. + * + * @param tarFile The new Tarfile value + */ + public void setTarfile( File tarFile ) + { + this.tarFile = tarFile; + } + + public TarFileSet createTarFileSet() + { + TarFileSet fileset = new TarFileSet(); + filesets.addElement( fileset ); + return fileset; + } + + public void execute() + throws BuildException + { + if( tarFile == null ) + { + throw new BuildException( "tarfile attribute must be set!", + location ); + } + + if( tarFile.exists() && tarFile.isDirectory() ) + { + throw new BuildException( "tarfile is a directory!", + location ); + } + + if( tarFile.exists() && !tarFile.canWrite() ) + { + throw new BuildException( "Can not write to the specified tarfile!", + location ); + } + + if( baseDir != null ) + { + if( !baseDir.exists() ) + { + throw new BuildException( "basedir does not exist!", location ); + } + + // add the main fileset to the list of filesets to process. + TarFileSet mainFileSet = new TarFileSet( fileset ); + mainFileSet.setDir( baseDir ); + filesets.addElement( mainFileSet ); + } + + if( filesets.size() == 0 ) + { + throw new BuildException( "You must supply either a basdir attribute or some nested filesets.", + location ); + } + + // check if tr is out of date with respect to each + // fileset + boolean upToDate = true; + for( Enumeration e = filesets.elements(); e.hasMoreElements(); ) + { + TarFileSet fs = ( TarFileSet )e.nextElement(); + String[] files = fs.getFiles( project ); + + if( !archiveIsUpToDate( files ) ) + { + upToDate = false; + } + + for( int i = 0; i < files.length; ++i ) + { + if( tarFile.equals( new File( fs.getDir( project ), files[i] ) ) ) + { + throw new BuildException( "A tar file cannot include itself", location ); + } + } + } + + if( upToDate ) + { + log( "Nothing to do: " + tarFile.getAbsolutePath() + " is up to date.", + Project.MSG_INFO ); + return; + } + + log( "Building tar: " + tarFile.getAbsolutePath(), Project.MSG_INFO ); + + TarOutputStream tOut = null; + try + { + tOut = new TarOutputStream( new FileOutputStream( tarFile ) ); + tOut.setDebug( true ); + if( longFileMode.isTruncateMode() ) + { + tOut.setLongFileMode( TarOutputStream.LONGFILE_TRUNCATE ); + } + else if( longFileMode.isFailMode() || + longFileMode.isOmitMode() ) + { + tOut.setLongFileMode( TarOutputStream.LONGFILE_ERROR ); + } + else + { + // warn or GNU + tOut.setLongFileMode( TarOutputStream.LONGFILE_GNU ); + } + + longWarningGiven = false; + for( Enumeration e = filesets.elements(); e.hasMoreElements(); ) + { + TarFileSet fs = ( TarFileSet )e.nextElement(); + String[] files = fs.getFiles( project ); + for( int i = 0; i < files.length; i++ ) + { + File f = new File( fs.getDir( project ), files[i] ); + String name = files[i].replace( File.separatorChar, '/' ); + tarFile( f, tOut, name, fs ); + } + } + } + catch( IOException ioe ) + { + String msg = "Problem creating TAR: " + ioe.getMessage(); + throw new BuildException( msg, ioe, location ); + } + finally + { + if( tOut != null ) + { + try + { + // close up + tOut.close(); + } + catch( IOException e ) + {} + } + } + } + + protected boolean archiveIsUpToDate( String[] files ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + MergingMapper mm = new MergingMapper(); + mm.setTo( tarFile.getAbsolutePath() ); + return sfs.restrict( files, baseDir, null, mm ).length == 0; + } + + protected void tarFile( File file, TarOutputStream tOut, String vPath, + TarFileSet tarFileSet ) + throws IOException + { + FileInputStream fIn = null; + + // don't add "" to the archive + if( vPath.length() <= 0 ) + { + return; + } + + if( file.isDirectory() && !vPath.endsWith( "/" ) ) + { + vPath += "/"; + } + + try + { + if( vPath.length() >= TarConstants.NAMELEN ) + { + if( longFileMode.isOmitMode() ) + { + log( "Omitting: " + vPath, Project.MSG_INFO ); + return; + } + else if( longFileMode.isWarnMode() ) + { + log( "Entry: " + vPath + " longer than " + + TarConstants.NAMELEN + " characters.", Project.MSG_WARN ); + if( !longWarningGiven ) + { + log( "Resulting tar file can only be processed successfully" + + " by GNU compatible tar commands", Project.MSG_WARN ); + longWarningGiven = true; + } + } + else if( longFileMode.isFailMode() ) + { + throw new BuildException( + "Entry: " + vPath + " longer than " + + TarConstants.NAMELEN + "characters.", location ); + } + } + + TarEntry te = new TarEntry( vPath ); + te.setModTime( file.lastModified() ); + if( !file.isDirectory() ) + { + te.setSize( file.length() ); + te.setMode( tarFileSet.getMode() ); + } + te.setUserName( tarFileSet.getUserName() ); + te.setGroupName( tarFileSet.getGroup() ); + + tOut.putNextEntry( te ); + + if( !file.isDirectory() ) + { + fIn = new FileInputStream( file ); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + tOut.write( buffer, 0, count ); + count = fIn.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + } + + tOut.closeEntry(); + } + finally + { + if( fIn != null ) + fIn.close(); + } + } + + public static class TarFileSet extends FileSet + { + private String[] files = null; + + private int mode = 0100644; + + private String userName = ""; + private String groupName = ""; + + + public TarFileSet( FileSet fileset ) + { + super( fileset ); + } + + public TarFileSet() + { + super(); + } + + public void setGroup( String groupName ) + { + this.groupName = groupName; + } + + public void setMode( String octalString ) + { + this.mode = 0100000 | Integer.parseInt( octalString, 8 ); + } + + public void setUserName( String userName ) + { + this.userName = userName; + } + + /** + * Get a list of files and directories specified in the fileset. + * + * @param p Description of Parameter + * @return a list of file and directory names, relative to the baseDir + * for the project. + */ + public String[] getFiles( Project p ) + { + if( files == null ) + { + DirectoryScanner ds = getDirectoryScanner( p ); + String[] directories = ds.getIncludedDirectories(); + String[] filesPerSe = ds.getIncludedFiles(); + files = new String[directories.length + filesPerSe.length]; + System.arraycopy( directories, 0, files, 0, directories.length ); + System.arraycopy( filesPerSe, 0, files, directories.length, + filesPerSe.length ); + } + + return files; + } + + public String getGroup() + { + return groupName; + } + + public int getMode() + { + return mode; + } + + public String getUserName() + { + return userName; + } + + } + + /** + * Valid Modes for LongFile attribute to Tar Task + * + * @author Magesh Umasankar + */ + public static class TarLongFileMode extends EnumeratedAttribute + { + + // permissable values for longfile attribute + public final static String WARN = "warn"; + public final static String FAIL = "fail"; + public final static String TRUNCATE = "truncate"; + public final static String GNU = "gnu"; + public final static String OMIT = "omit"; + + private final String[] validModes = {WARN, FAIL, TRUNCATE, GNU, OMIT}; + + public TarLongFileMode() + { + super(); + setValue( WARN ); + } + + public String[] getValues() + { + return validModes; + } + + public boolean isFailMode() + { + return FAIL.equalsIgnoreCase( getValue() ); + } + + public boolean isGnuMode() + { + return GNU.equalsIgnoreCase( getValue() ); + } + + public boolean isOmitMode() + { + return OMIT.equalsIgnoreCase( getValue() ); + } + + public boolean isTruncateMode() + { + return TRUNCATE.equalsIgnoreCase( getValue() ); + } + + public boolean isWarnMode() + { + return WARN.equalsIgnoreCase( getValue() ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/TaskOutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/TaskOutputStream.java new file mode 100644 index 000000000..5af5c9934 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/TaskOutputStream.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.IOException; +import java.io.OutputStream; +import org.apache.tools.ant.Task; + +/** + * Redirects text written to a stream thru the standard ant logging mechanism. + * This class is useful for integrating with tools that write to System.out and + * System.err. For example, the following will cause all text written to + * System.out to be logged with "info" priority:

              System.setOut(new PrintStream(new TaskOutputStream(project, Project.MSG_INFO)));
              + * + * @author James Duncan Davidson (duncan@x180.com) + * @deprecated use LogOutputStream instead. + */ + +public class TaskOutputStream extends OutputStream +{ + private StringBuffer line; + private int msgOutputLevel; + + private Task task; + + /** + * Constructs a new JavacOutputStream with the given project as the output + * source for messages. + * + * @param task Description of Parameter + * @param msgOutputLevel Description of Parameter + */ + + TaskOutputStream( Task task, int msgOutputLevel ) + { + this.task = task; + this.msgOutputLevel = msgOutputLevel; + + line = new StringBuffer(); + } + + /** + * Write a character to the output stream. This method looks to make sure + * that there isn't an error being reported and will flush each line of + * input out to the project's log stream. + * + * @param c Description of Parameter + * @exception IOException Description of Exception + */ + + public void write( int c ) + throws IOException + { + char cc = ( char )c; + if( cc == '\r' || cc == '\n' ) + { + // line feed + if( line.length() > 0 ) + { + processLine(); + } + } + else + { + line.append( cc ); + } + } + + /** + * Processes a line of input and determines if an error occured. + */ + + private void processLine() + { + String s = line.toString(); + task.log( s, msgOutputLevel ); + line = new StringBuffer(); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Taskdef.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Taskdef.java new file mode 100644 index 000000000..609a2771d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Taskdef.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; + +/** + * Define a new task. + * + * @author Stefan Bodewig + */ +public class Taskdef extends Definer +{ + protected void addDefinition( String name, Class c ) + throws BuildException + { + project.addTaskDefinition( name, c ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Touch.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Touch.java new file mode 100644 index 000000000..358597118 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Touch.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Locale; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.FileUtils; + +/** + * Touch a file and/or fileset(s) -- corresponds to the Unix touch command.

              + * + * If the file to touch doesn't exist, an empty one is created.

              + * + * Note: Setting the modification time of files is not supported in JDK 1.1.

              + * + * @author Stefan Bodewig + * @author Michael J. Sikorsky + * @author Robert Shaw + */ +public class Touch extends Task +{// required + private long millis = -1; + private Vector filesets = new Vector(); + private String dateTime; + + private File file; + private FileUtils fileUtils; + + public Touch() + { + fileUtils = FileUtils.newFileUtils(); + } + + /** + * Date in the format MM/DD/YYYY HH:MM AM_PM. + * + * @param dateTime The new Datetime value + */ + public void setDatetime( String dateTime ) + { + this.dateTime = dateTime; + } + + /** + * Sets a single source file to touch. If the file does not exist an empty + * file will be created. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Milliseconds since 01/01/1970 00:00 am. + * + * @param millis The new Millis value + */ + public void setMillis( long millis ) + { + this.millis = millis; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Execute the touch operation. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( file == null && filesets.size() == 0 ) + { + throw + new BuildException( "Specify at least one source - a file or a fileset." ); + } + + if( file != null && file.exists() && file.isDirectory() ) + { + throw new BuildException( "Use a fileset to touch directories." ); + } + + if( dateTime != null ) + { + DateFormat df = DateFormat.getDateTimeInstance( DateFormat.SHORT, + DateFormat.SHORT, + Locale.US ); + try + { + setMillis( df.parse( dateTime ).getTime() ); + if( millis < 0 ) + { + throw new BuildException( "Date of " + dateTime + + " results in negative milliseconds value relative to epoch (January 1, 1970, 00:00:00 GMT)." ); + } + } + catch( ParseException pe ) + { + throw new BuildException( pe.getMessage(), pe, location ); + } + } + + touch(); + } + + /** + * Does the actual work. Entry point for Untar and Expand as well. + * + * @exception BuildException Description of Exception + */ + protected void touch() + throws BuildException + { + if( file != null ) + { + if( !file.exists() ) + { + log( "Creating " + file, Project.MSG_INFO ); + try + { + FileOutputStream fos = new FileOutputStream( file ); + fos.write( new byte[0] ); + fos.close(); + } + catch( IOException ioe ) + { + throw new BuildException( "Could not create " + file, ioe, + location ); + } + } + } + + if( millis >= 0 && project.getJavaVersion() == Project.JAVA_1_1 ) + { + log( "modification time of files cannot be set in JDK 1.1", + Project.MSG_WARN ); + return; + } + + boolean resetMillis = false; + if( millis < 0 ) + { + resetMillis = true; + millis = System.currentTimeMillis(); + } + + if( file != null ) + { + touch( file ); + } + + // deal with the filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + File fromDir = fs.getDir( project ); + + String[] srcFiles = ds.getIncludedFiles(); + String[] srcDirs = ds.getIncludedDirectories(); + + for( int j = 0; j < srcFiles.length; j++ ) + { + touch( new File( fromDir, srcFiles[j] ) ); + } + + for( int j = 0; j < srcDirs.length; j++ ) + { + touch( new File( fromDir, srcDirs[j] ) ); + } + } + + if( resetMillis ) + { + millis = -1; + } + } + + protected void touch( File file ) + throws BuildException + { + if( !file.canWrite() ) + { + throw new BuildException( "Can not change modification date of read-only file " + file ); + } + + if( project.getJavaVersion() == Project.JAVA_1_1 ) + { + return; + } + + fileUtils.setFileLastModified( file, millis ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Transform.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Transform.java new file mode 100644 index 000000000..02ecfae96 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Transform.java @@ -0,0 +1,17 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +/** + * Has been merged into ExecuteOn, empty class for backwards compatibility. + * + * @author Stefan Bodewig + */ +public class Transform extends ExecuteOn +{ +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tstamp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tstamp.java new file mode 100644 index 000000000..276d964e0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Tstamp.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import java.util.NoSuchElementException; +import java.util.StringTokenizer; +import java.util.TimeZone; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Sets TSTAMP, DSTAMP and TODAY + * + * @author costin@dnt.ro + * @author stefano@apache.org + * @author roxspring@yahoo.com + * @author conor@cognet.com.au + * @author Magesh Umasankar + */ +public class Tstamp extends Task +{ + + private Vector customFormats = new Vector(); + private String prefix = ""; + + public void setPrefix( String prefix ) + { + this.prefix = prefix; + if( !this.prefix.endsWith( "." ) ) + { + this.prefix += "."; + } + } + + public CustomFormat createFormat() + { + CustomFormat cts = new CustomFormat( prefix ); + customFormats.addElement( cts ); + return cts; + } + + public void execute() + throws BuildException + { + try + { + Date d = new Date(); + + SimpleDateFormat dstamp = new SimpleDateFormat( "yyyyMMdd" ); + project.setNewProperty( prefix + "DSTAMP", dstamp.format( d ) ); + + SimpleDateFormat tstamp = new SimpleDateFormat( "HHmm" ); + project.setNewProperty( prefix + "TSTAMP", tstamp.format( d ) ); + + SimpleDateFormat today = new SimpleDateFormat( "MMMM d yyyy", Locale.US ); + project.setNewProperty( prefix + "TODAY", today.format( d ) ); + + Enumeration i = customFormats.elements(); + while( i.hasMoreElements() ) + { + CustomFormat cts = ( CustomFormat )i.nextElement(); + cts.execute( project, d, location ); + } + + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + public static class Unit extends EnumeratedAttribute + { + + private final static String MILLISECOND = "millisecond"; + private final static String SECOND = "second"; + private final static String MINUTE = "minute"; + private final static String HOUR = "hour"; + private final static String DAY = "day"; + private final static String WEEK = "week"; + private final static String MONTH = "month"; + private final static String YEAR = "year"; + + private final static String[] units = { + MILLISECOND, + SECOND, + MINUTE, + HOUR, + DAY, + WEEK, + MONTH, + YEAR + }; + + private Hashtable calendarFields = new Hashtable(); + + public Unit() + { + calendarFields.put( MILLISECOND, + new Integer( Calendar.MILLISECOND ) ); + calendarFields.put( SECOND, new Integer( Calendar.SECOND ) ); + calendarFields.put( MINUTE, new Integer( Calendar.MINUTE ) ); + calendarFields.put( HOUR, new Integer( Calendar.HOUR_OF_DAY ) ); + calendarFields.put( DAY, new Integer( Calendar.DATE ) ); + calendarFields.put( WEEK, new Integer( Calendar.WEEK_OF_YEAR ) ); + calendarFields.put( MONTH, new Integer( Calendar.MONTH ) ); + calendarFields.put( YEAR, new Integer( Calendar.YEAR ) ); + } + + public int getCalendarField() + { + String key = getValue().toLowerCase(); + Integer i = ( Integer )calendarFields.get( key ); + return i.intValue(); + } + + public String[] getValues() + { + return units; + } + } + + public class CustomFormat + { + private int offset = 0; + private int field = Calendar.DATE; + private String prefix = ""; + private String country; + private String language; + private String pattern; + private String propertyName; + private TimeZone timeZone; + private String variant; + + public CustomFormat( String prefix ) + { + this.prefix = prefix; + } + + public void setLocale( String locale ) + { + StringTokenizer st = new StringTokenizer( locale, " \t\n\r\f," ); + try + { + language = st.nextToken(); + if( st.hasMoreElements() ) + { + country = st.nextToken(); + if( st.hasMoreElements() ) + { + country = st.nextToken(); + if( st.hasMoreElements() ) + { + throw new BuildException( "bad locale format", getLocation() ); + } + } + } + else + { + country = ""; + } + } + catch( NoSuchElementException e ) + { + throw new BuildException( "bad locale format", e, getLocation() ); + } + } + + public void setOffset( int offset ) + { + this.offset = offset; + } + + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + public void setProperty( String propertyName ) + { + this.propertyName = prefix + propertyName; + } + + public void setTimezone( String id ) + { + timeZone = TimeZone.getTimeZone( id ); + } + + /** + * @param unit The new Unit value + * @deprecated setUnit(String) is deprecated and is replaced with + * setUnit(Tstamp.Unit) to make Ant's Introspection mechanism do + * the work and also to encapsulate operations on the unit in its + * own class. + */ + public void setUnit( String unit ) + { + log( "DEPRECATED - The setUnit(String) method has been deprecated." + + " Use setUnit(Tstamp.Unit) instead." ); + Unit u = new Unit(); + u.setValue( unit ); + field = u.getCalendarField(); + } + + public void setUnit( Unit unit ) + { + field = unit.getCalendarField(); + } + + public void execute( Project project, Date date, Location location ) + { + if( propertyName == null ) + { + throw new BuildException( "property attribute must be provided", location ); + } + + if( pattern == null ) + { + throw new BuildException( "pattern attribute must be provided", location ); + } + + SimpleDateFormat sdf; + if( language == null ) + { + sdf = new SimpleDateFormat( pattern ); + } + else if( variant == null ) + { + sdf = new SimpleDateFormat( pattern, new Locale( language, country ) ); + } + else + { + sdf = new SimpleDateFormat( pattern, new Locale( language, country, variant ) ); + } + if( offset != 0 ) + { + Calendar calendar = Calendar.getInstance(); + calendar.setTime( date ); + calendar.add( field, offset ); + date = calendar.getTime(); + } + if( timeZone != null ) + { + sdf.setTimeZone( timeZone ); + } + project.setNewProperty( propertyName, sdf.format( date ) ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Typedef.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Typedef.java new file mode 100644 index 000000000..2bb6ac880 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Typedef.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import org.apache.tools.ant.BuildException; + +/** + * Define a new data type. + * + * @author Stefan Bodewig + */ +public class Typedef extends Definer +{ + protected void addDefinition( String name, Class c ) + throws BuildException + { + project.addDataTypeDefinition( name, c ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Unpack.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Unpack.java new file mode 100644 index 000000000..1e641b251 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Unpack.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Abstract Base class for unpack tasks. + * + * @author Magesh Umasankar + */ + +public abstract class Unpack extends Task +{ + protected File dest; + + protected File source; + + public void setDest( String dest ) + { + this.dest = project.resolveFile( dest ); + } + + public void setSrc( String src ) + { + source = project.resolveFile( src ); + } + + public void execute() + throws BuildException + { + validate(); + extract(); + } + + protected abstract String getDefaultExtension(); + + protected abstract void extract(); + + private void createDestFile( String defaultExtension ) + { + String sourceName = source.getName(); + int len = sourceName.length(); + if( defaultExtension != null + && len > defaultExtension.length() + && defaultExtension.equalsIgnoreCase( sourceName.substring( len - defaultExtension.length() ) ) ) + { + dest = new File( dest, sourceName.substring( 0, + len - defaultExtension.length() ) ); + } + else + { + dest = new File( dest, sourceName ); + } + } + + private void validate() + throws BuildException + { + if( source == null ) + { + throw new BuildException( "No Src for gunzip specified", location ); + } + + if( !source.exists() ) + { + throw new BuildException( "Src doesn't exist", location ); + } + + if( source.isDirectory() ) + { + throw new BuildException( "Cannot expand a directory", location ); + } + + if( dest == null ) + { + dest = new File( source.getParent() ); + } + + if( dest.isDirectory() ) + { + String defaultExtension = getDefaultExtension(); + createDestFile( defaultExtension ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Untar.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Untar.java new file mode 100644 index 000000000..99fba8759 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Untar.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Date; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.tar.TarEntry; +import org.apache.tools.tar.TarInputStream; + +/** + * Untar a file. Heavily based on the Expand task. + * + * @author Stefan Bodewig + * @author Magesh Umasankar + */ +public class Untar extends Expand +{ + + protected void expandFile( FileUtils fileUtils, File srcF, File dir ) + { + TarInputStream tis = null; + try + { + log( "Expanding: " + srcF + " into " + dir, Project.MSG_INFO ); + + tis = new TarInputStream( new FileInputStream( srcF ) ); + TarEntry te = null; + + while( ( te = tis.getNextEntry() ) != null ) + { + extractFile( fileUtils, srcF, dir, tis, + te.getName(), + te.getModTime(), te.isDirectory() ); + } + log( "expand complete", Project.MSG_VERBOSE ); + + } + catch( IOException ioe ) + { + throw new BuildException( "Error while expanding " + srcF.getPath(), + ioe, location ); + } + finally + { + if( tis != null ) + { + try + { + tis.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/UpToDate.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/UpToDate.java new file mode 100644 index 000000000..968131b0a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/UpToDate.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Will set the given property if the specified target has a timestamp greater + * than all of the source files. + * + * @author William Ferguson + * williamf@mincom.com + * @author Hiroaki Nakamura + * hnakamur@mc.neweb.ne.jp + * @author Stefan Bodewig + */ + +public class UpToDate extends MatchingTask implements Condition +{ + private Vector sourceFileSets = new Vector(); + + protected Mapper mapperElement = null; + + private String _property; + private File _targetFile; + private String _value; + + /** + * The property to set if the target file is more up to date than each of + * the source files. + * + * @param property the name of the property to set if Target is up to date. + */ + public void setProperty( String property ) + { + _property = property; + } + + /** + * The file which must be more up to date than each of the source files if + * the property is to be set. + * + * @param file the file which we are checking against. + */ + public void setTargetFile( File file ) + { + _targetFile = file; + } + + /** + * The value to set the named property to if the target file is more up to + * date than each of the source files. Defaults to 'true'. + * + * @param value the value to set the property to if Target is up to date + */ + public void setValue( String value ) + { + _value = value; + } + + /** + * Nested <srcfiles> element. + * + * @param fs The feature to be added to the Srcfiles attribute + */ + public void addSrcfiles( FileSet fs ) + { + sourceFileSets.addElement( fs ); + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapperElement != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapperElement = new Mapper( project ); + return mapperElement; + } + + /** + * Evaluate all target and source files, see if the targets are up-to-date. + * + * @return Description of the Returned Value + */ + public boolean eval() + { + if( sourceFileSets.size() == 0 ) + { + throw new BuildException( "At least one element must be set" ); + } + + if( _targetFile == null && mapperElement == null ) + { + throw new BuildException( "The targetfile attribute or a nested mapper element must be set" ); + } + + // if not there then it can't be up to date + if( _targetFile != null && !_targetFile.exists() ) + return false; + + Enumeration enum = sourceFileSets.elements(); + boolean upToDate = true; + while( upToDate && enum.hasMoreElements() ) + { + FileSet fs = ( FileSet )enum.nextElement(); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + upToDate = upToDate && scanDir( fs.getDir( project ), + ds.getIncludedFiles() ); + } + return upToDate; + } + + + /** + * Sets property to true if target files have a more recent timestamp than + * each of the corresponding source files. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + boolean upToDate = eval(); + if( upToDate ) + { + this.project.setProperty( _property, this.getValue() ); + if( mapperElement == null ) + { + log( "File \"" + _targetFile.getAbsolutePath() + "\" is up to date.", + Project.MSG_VERBOSE ); + } + else + { + log( "All target files have been up to date.", + Project.MSG_VERBOSE ); + } + } + } + + protected boolean scanDir( File srcDir, String files[] ) + { + SourceFileScanner sfs = new SourceFileScanner( this ); + FileNameMapper mapper = null; + File dir = srcDir; + if( mapperElement == null ) + { + MergingMapper mm = new MergingMapper(); + mm.setTo( _targetFile.getAbsolutePath() ); + mapper = mm; + dir = null; + } + else + { + mapper = mapperElement.getImplementation(); + } + return sfs.restrict( files, srcDir, dir, mapper ).length == 0; + } + + /** + * Returns the value, or "true" if a specific value wasn't provided. + * + * @return The Value value + */ + private String getValue() + { + return ( _value != null ) ? _value : "true"; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/WaitFor.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/WaitFor.java new file mode 100644 index 000000000..9f87abb1d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/WaitFor.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.taskdefs.condition.ConditionBase; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * Wait for an external event to occur. Wait for an external process to start or + * to complete some task. This is useful with the parallel task to + * syncronize the execution of tests with server startup. The following + * attributes can be specified on a waitfor task: + *
                + *
              • maxwait - maximum length of time to wait before giving up
              • + *
              • maxwaitunit - The unit to be used to interpret maxwait attribute
              • + * + *
              • checkevery - amount of time to sleep between each check
              • + *
              • checkeveryunit - The unit to be used to interpret checkevery attribute + *
              • + *
              • timeoutproperty - name of a property to set if maxwait has been + * exceeded.
              • + *
              + * The maxwaitunit and checkeveryunit are allowed to have the following values: + * millesond, second, minute, hour, day and week. The default is millisecond. + * + * @author Denis Hennessy + * @author Magesh Umasankar + */ + +public class WaitFor extends ConditionBase +{ + private long maxWaitMillis = 1000l * 60l * 3l;// default max wait time + private long maxWaitMultiplier = 1l; + private long checkEveryMillis = 500l; + private long checkEveryMultiplier = 1l; + private String timeoutProperty; + + /** + * Set the time between each check + * + * @param time The new CheckEvery value + */ + public void setCheckEvery( long time ) + { + checkEveryMillis = time; + } + + /** + * Set the check every time unit + * + * @param unit The new CheckEveryUnit value + */ + public void setCheckEveryUnit( Unit unit ) + { + checkEveryMultiplier = unit.getMultiplier(); + } + + /** + * Set the maximum length of time to wait + * + * @param time The new MaxWait value + */ + public void setMaxWait( long time ) + { + maxWaitMillis = time; + } + + /** + * Set the max wait time unit + * + * @param unit The new MaxWaitUnit value + */ + public void setMaxWaitUnit( Unit unit ) + { + maxWaitMultiplier = unit.getMultiplier(); + } + + /** + * Set the timeout property. + * + * @param p The new TimeoutProperty value + */ + public void setTimeoutProperty( String p ) + { + timeoutProperty = p; + } + + /** + * Check repeatedly for the specified conditions until they become true or + * the timeout expires. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( countConditions() > 1 ) + { + throw new BuildException( "You must not nest more than one condition into " ); + } + if( countConditions() < 1 ) + { + throw new BuildException( "You must nest a condition into " ); + } + Condition c = ( Condition )getConditions().nextElement(); + + maxWaitMillis *= maxWaitMultiplier; + checkEveryMillis *= checkEveryMultiplier; + long start = System.currentTimeMillis(); + long end = start + maxWaitMillis; + + while( System.currentTimeMillis() < end ) + { + if( c.eval() ) + { + return; + } + try + { + Thread.sleep( checkEveryMillis ); + } + catch( InterruptedException e ) + { + } + } + + if( timeoutProperty != null ) + { + project.setNewProperty( timeoutProperty, "true" ); + } + } + + public static class Unit extends EnumeratedAttribute + { + + private final static String MILLISECOND = "millisecond"; + private final static String SECOND = "second"; + private final static String MINUTE = "minute"; + private final static String HOUR = "hour"; + private final static String DAY = "day"; + private final static String WEEK = "week"; + + private final static String[] units = { + MILLISECOND, SECOND, MINUTE, HOUR, DAY, WEEK + }; + + private Hashtable timeTable = new Hashtable(); + + public Unit() + { + timeTable.put( MILLISECOND, new Long( 1l ) ); + timeTable.put( SECOND, new Long( 1000l ) ); + timeTable.put( MINUTE, new Long( 1000l * 60l ) ); + timeTable.put( HOUR, new Long( 1000l * 60l * 60l ) ); + timeTable.put( DAY, new Long( 1000l * 60l * 60l * 24l ) ); + timeTable.put( WEEK, new Long( 1000l * 60l * 60l * 24l * 7l ) ); + } + + public long getMultiplier() + { + String key = getValue().toLowerCase(); + Long l = ( Long )timeTable.get( key ); + return l.longValue(); + } + + public String[] getValues() + { + return units; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/War.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/War.java new file mode 100644 index 000000000..ff2bf9c48 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/War.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.zip.ZipOutputStream; + + +/** + * Creates a WAR archive. + * + * @author Stefan Bodewig + */ +public class War extends Jar +{ + + private File deploymentDescriptor; + private boolean descriptorAdded; + + public War() + { + super(); + archiveType = "war"; + emptyBehavior = "create"; + } + + public void setWarfile( File warFile ) + { + log( "DEPRECATED - The warfile attribute is deprecated. Use file attribute instead." ); + setFile( warFile ); + } + + public void setWebxml( File descr ) + { + deploymentDescriptor = descr; + if( !deploymentDescriptor.exists() ) + throw new BuildException( "Deployment descriptor: " + deploymentDescriptor + " does not exist." ); + + // Create a ZipFileSet for this file, and pass it up. + ZipFileSet fs = new ZipFileSet(); + fs.setDir( new File( deploymentDescriptor.getParent() ) ); + fs.setIncludes( deploymentDescriptor.getName() ); + fs.setFullpath( "WEB-INF/web.xml" ); + super.addFileset( fs ); + } + + public void addClasses( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "WEB-INF/classes/" ); + super.addFileset( fs ); + } + + public void addLib( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "WEB-INF/lib/" ); + super.addFileset( fs ); + } + + public void addWebinf( ZipFileSet fs ) + { + // We just set the prefix for this fileset, and pass it up. + fs.setPrefix( "WEB-INF/" ); + super.addFileset( fs ); + } + + /** + * Make sure we don't think we already have a web.xml next time this task + * gets executed. + */ + protected void cleanUp() + { + descriptorAdded = false; + super.cleanUp(); + } + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException + { + // If no webxml file is specified, it's an error. + if( deploymentDescriptor == null && !isInUpdateMode() ) + { + throw new BuildException( "webxml attribute is required", location ); + } + + super.initZipOutputStream( zOut ); + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + // If the file being added is WEB-INF/web.xml, we warn if it's not the + // one specified in the "webxml" attribute - or if it's being added twice, + // meaning the same file is specified by the "webxml" attribute and in + // a element. + if( vPath.equalsIgnoreCase( "WEB-INF/web.xml" ) ) + { + if( deploymentDescriptor == null || !deploymentDescriptor.equals( file ) || descriptorAdded ) + { + log( "Warning: selected " + archiveType + " files include a WEB-INF/web.xml which will be ignored " + + "(please use webxml attribute to " + archiveType + " task)", Project.MSG_WARN ); + } + else + { + super.zipFile( file, zOut, vPath ); + descriptorAdded = true; + } + } + else + { + super.zipFile( file, zOut, vPath ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLiaison.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLiaison.java new file mode 100644 index 000000000..a4d01bd8e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLiaison.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; + +/** + * Proxy interface for XSLT processors. + * + * @author Sam Ruby + * @author Stephane Bailliez + * @see XSLTProcess + */ +public interface XSLTLiaison +{ + + /** + * the file protocol prefix for systemid. This file protocol must be + * appended to an absolute path. Typically: FILE_PROTOCOL_PREFIX + + * file.getAbsolutePath() This is not correct in specification terms + * since an absolute url in Unix is file:// + file.getAbsolutePath() while + * it is file:/// + file.getAbsolutePath() under Windows. Whatever, it + * should not be a problem to put file:/// in every case since most parsers + * for now incorrectly makes no difference between it.. and users also have + * problem with that :) + */ + String FILE_PROTOCOL_PREFIX = "file:///"; + + /** + * set the stylesheet to use for the transformation. + * + * @param stylesheet the stylesheet to be used for transformation. + * @exception Exception Description of Exception + */ + void setStylesheet( File stylesheet ) + throws Exception; + + /** + * Add a parameter to be set during the XSL transformation. + * + * @param name the parameter name. + * @param expression the parameter value as an expression string. + * @throws Exception thrown if any problems happens. + */ + void addParam( String name, String expression ) + throws Exception; + + /** + * set the output type to use for the transformation. Only "xml" (the + * default) is guaranteed to work for all parsers. Xalan2 also supports + * "html" and "text". + * + * @param type the output method to use + * @exception Exception Description of Exception + */ + void setOutputtype( String type ) + throws Exception; + + /** + * Perform the transformation of a file into another. + * + * @param infile the input file, probably an XML one. :-) + * @param outfile the output file resulting from the transformation + * @see #setStylesheet(File) + * @throws Exception thrown if any problems happens. + */ + void transform( File infile, File outfile ) + throws Exception; + +}//-- XSLTLiaison diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLogger.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLogger.java new file mode 100644 index 000000000..4579d4085 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLogger.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +public interface XSLTLogger +{ + /** + * Log a message. + * + * @param msg Description of Parameter + */ + void log( String msg ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java new file mode 100644 index 000000000..ae2051ae0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTLoggerAware.java @@ -0,0 +1,13 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; + +public interface XSLTLoggerAware +{ + void setLogger( XSLTLogger l ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTProcess.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTProcess.java new file mode 100644 index 000000000..13a17d2b2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/XSLTProcess.java @@ -0,0 +1,581 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.apache.tools.ant.util.FileUtils; + + +/** + * A Task to process via XSLT a set of XML documents. This is useful for + * building views of XML based documentation. arguments: + *
                + *
              • basedir + *
              • destdir + *
              • style + *
              • includes + *
              • excludes + *
              + * Of these arguments, the sourcedir and destdir are required.

              + * + * This task will recursively scan the sourcedir and destdir looking for XML + * documents to process via XSLT. Any other files, such as images, or html files + * in the source directory will be copied into the destination directory. + * + * @author Keith Visco + * @author Sam Ruby + * @author Russell Gold + * @author Stefan Bodewig + */ + +public class XSLTProcess extends MatchingTask implements XSLTLogger +{ + + private File destDir = null; + + private File baseDir = null; + + private String xslFile = null; + + private String targetExtension = ".html"; + private Vector params = new Vector(); + + private File inFile = null; + + private File outFile = null; + private Path classpath = null; + private boolean stylesheetLoaded = false; + + private boolean force = false; + + private String outputtype = null; + + private FileUtils fileUtils; + private XSLTLiaison liaison; + + private String processor; + + /** + * Creates a new XSLTProcess Task. + */ + public XSLTProcess() + { + fileUtils = FileUtils.newFileUtils(); + }//-- setForce + + /** + * Set the base directory. + * + * @param dir The new Basedir value + */ + public void setBasedir( File dir ) + { + baseDir = dir; + } + + /** + * Set the classpath to load the Processor through (attribute). + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + createClasspath().append( classpath ); + } + + /** + * Set the classpath to load the Processor through via reference + * (attribute). + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + }//-- setSourceDir + + /** + * Set the destination directory into which the XSL result files should be + * copied to + * + * @param dir The new Destdir value + */ + public void setDestdir( File dir ) + { + destDir = dir; + }//-- setDestDir + + /** + * Set the desired file extension to be used for the target + * + * @param name the extension to use + */ + public void setExtension( String name ) + { + targetExtension = name; + }//-- execute + + /** + * Set whether to check dependencies, or always generate. + * + * @param force The new Force value + */ + public void setForce( boolean force ) + { + this.force = force; + } + + /** + * Sets an input xml file to be styled + * + * @param inFile The new In value + */ + public void setIn( File inFile ) + { + this.inFile = inFile; + } + + /** + * Sets an out file + * + * @param outFile The new Out value + */ + public void setOut( File outFile ) + { + this.outFile = outFile; + } + + /** + * Set the output type to use for the transformation. Only "xml" (the + * default) is guaranteed to work for all parsers. Xalan2 also supports + * "html" and "text". + * + * @param type the output method to use + */ + public void setOutputtype( String type ) + { + this.outputtype = type; + } + + + public void setProcessor( String processor ) + { + this.processor = processor; + }//-- setDestDir + + /** + * Sets the file to use for styling relative to the base directory of this + * task. + * + * @param xslFile The new Style value + */ + public void setStyle( String xslFile ) + { + this.xslFile = xslFile; + } + + /** + * Set the classpath to load the Processor through (nested element). + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + public Param createParam() + { + Param p = new Param(); + params.addElement( p ); + return p; + }//-- XSLTProcess + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + + public void execute() + throws BuildException + { + DirectoryScanner scanner; + String[] list; + String[] dirs; + + if( xslFile == null ) + { + throw new BuildException( "no stylesheet specified", location ); + } + + if( baseDir == null ) + { + baseDir = project.resolveFile( "." ); + } + + liaison = getLiaison(); + + // check if liaison wants to log errors using us as logger + if( liaison instanceof XSLTLoggerAware ) + { + ( ( XSLTLoggerAware )liaison ).setLogger( this ); + } + + log( "Using " + liaison.getClass().toString(), Project.MSG_VERBOSE ); + + File stylesheet = project.resolveFile( xslFile ); + if( !stylesheet.exists() ) + { + stylesheet = fileUtils.resolveFile( baseDir, xslFile ); + /* + * shouldn't throw out deprecation warnings before we know, + * the wrong version has been used. + */ + if( stylesheet.exists() ) + { + log( "DEPRECATED - the style attribute should be relative to the project\'s" ); + log( " basedir, not the tasks\'s basedir." ); + } + } + + // if we have an in file and out then process them + if( inFile != null && outFile != null ) + { + process( inFile, outFile, stylesheet ); + return; + } + + /* + * if we get here, in and out have not been specified, we are + * in batch processing mode. + */ + //-- make sure Source directory exists... + if( destDir == null ) + { + String msg = "destdir attributes must be set!"; + throw new BuildException( msg ); + } + scanner = getDirectoryScanner( baseDir ); + log( "Transforming into " + destDir, Project.MSG_INFO ); + + // Process all the files marked for styling + list = scanner.getIncludedFiles(); + for( int i = 0; i < list.length; ++i ) + { + process( baseDir, list[i], destDir, stylesheet ); + } + + // Process all the directoried marked for styling + dirs = scanner.getIncludedDirectories(); + for( int j = 0; j < dirs.length; ++j ) + { + list = new File( baseDir, dirs[j] ).list(); + for( int i = 0; i < list.length; ++i ) + process( baseDir, list[i], destDir, stylesheet ); + } + } + + protected XSLTLiaison getLiaison() + { + // if processor wasn't specified, see if TraX is available. If not, + // default it to xslp or xalan, depending on which is in the classpath + if( liaison == null ) + { + if( processor != null ) + { + try + { + resolveProcessor( processor ); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + else + { + try + { + resolveProcessor( "trax" ); + } + catch( Throwable e1 ) + { + try + { + resolveProcessor( "xalan" ); + } + catch( Throwable e2 ) + { + try + { + resolveProcessor( "adaptx" ); + } + catch( Throwable e3 ) + { + try + { + resolveProcessor( "xslp" ); + } + catch( Throwable e4 ) + { + e4.printStackTrace(); + e3.printStackTrace(); + e2.printStackTrace(); + throw new BuildException( e1 ); + } + } + } + } + } + } + return liaison; + } + + /** + * Loads the stylesheet and set xsl:param parameters. + * + * @param stylesheet Description of Parameter + * @exception BuildException Description of Exception + */ + protected void configureLiaison( File stylesheet ) + throws BuildException + { + if( stylesheetLoaded ) + { + return; + } + stylesheetLoaded = true; + + try + { + log( "Loading stylesheet " + stylesheet, Project.MSG_INFO ); + liaison.setStylesheet( stylesheet ); + for( Enumeration e = params.elements(); e.hasMoreElements(); ) + { + Param p = ( Param )e.nextElement(); + liaison.addParam( p.getName(), p.getExpression() ); + } + } + catch( Exception ex ) + { + log( "Failed to read stylesheet " + stylesheet, Project.MSG_INFO ); + throw new BuildException( ex ); + } + } + + private void ensureDirectoryFor( File targetFile ) + throws BuildException + { + File directory = new File( targetFile.getParent() ); + if( !directory.exists() ) + { + if( !directory.mkdirs() ) + { + throw new BuildException( "Unable to create directory: " + + directory.getAbsolutePath() ); + } + } + } + + /** + * Load named class either via the system classloader or a given custom + * classloader. + * + * @param classname Description of Parameter + * @return Description of the Returned Value + * @exception Exception Description of Exception + */ + private Class loadClass( String classname ) + throws Exception + { + if( classpath == null ) + { + return Class.forName( classname ); + } + else + { + AntClassLoader al = new AntClassLoader( project, classpath ); + Class c = al.loadClass( classname ); + AntClassLoader.initializeClass( c ); + return c; + } + } + + /** + * Processes the given input XML file and stores the result in the given + * resultFile. + * + * @param baseDir Description of Parameter + * @param xmlFile Description of Parameter + * @param destDir Description of Parameter + * @param stylesheet Description of Parameter + * @exception BuildException Description of Exception + */ + private void process( File baseDir, String xmlFile, File destDir, + File stylesheet ) + throws BuildException + { + + String fileExt = targetExtension; + File outFile = null; + File inFile = null; + + try + { + long styleSheetLastModified = stylesheet.lastModified(); + inFile = new File( baseDir, xmlFile ); + int dotPos = xmlFile.lastIndexOf( '.' ); + if( dotPos > 0 ) + { + outFile = new File( destDir, xmlFile.substring( 0, xmlFile.lastIndexOf( '.' ) ) + fileExt ); + } + else + { + outFile = new File( destDir, xmlFile + fileExt ); + } + if( force || + inFile.lastModified() > outFile.lastModified() || + styleSheetLastModified > outFile.lastModified() ) + { + ensureDirectoryFor( outFile ); + log( "Processing " + inFile + " to " + outFile ); + + configureLiaison( stylesheet ); + liaison.transform( inFile, outFile ); + } + } + catch( Exception ex ) + { + // If failed to process document, must delete target document, + // or it will not attempt to process it the second time + log( "Failed to process " + inFile, Project.MSG_INFO ); + if( outFile != null ) + { + outFile.delete(); + } + + throw new BuildException( ex ); + } + + }//-- processXML + + private void process( File inFile, File outFile, File stylesheet ) + throws BuildException + { + try + { + long styleSheetLastModified = stylesheet.lastModified(); + log( "In file " + inFile + " time: " + inFile.lastModified(), Project.MSG_DEBUG ); + log( "Out file " + outFile + " time: " + outFile.lastModified(), Project.MSG_DEBUG ); + log( "Style file " + xslFile + " time: " + styleSheetLastModified, Project.MSG_DEBUG ); + if( force || + inFile.lastModified() > outFile.lastModified() || + styleSheetLastModified > outFile.lastModified() ) + { + ensureDirectoryFor( outFile ); + log( "Processing " + inFile + " to " + outFile, Project.MSG_INFO ); + configureLiaison( stylesheet ); + liaison.transform( inFile, outFile ); + } + } + catch( Exception ex ) + { + log( "Failed to process " + inFile, Project.MSG_INFO ); + if( outFile != null ) + outFile.delete(); + throw new BuildException( ex ); + } + } + + /** + * Load processor here instead of in setProcessor - this will be called from + * within execute, so we have access to the latest classpath. + * + * @param proc Description of Parameter + * @exception Exception Description of Exception + */ + private void resolveProcessor( String proc ) + throws Exception + { + if( proc.equals( "trax" ) ) + { + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.TraXLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else if( proc.equals( "xslp" ) ) + { + log( "DEPRECATED - xslp processor is deprecated. Use trax or xalan instead." ); + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.XslpLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else if( proc.equals( "xalan" ) ) + { + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.XalanLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else if( proc.equals( "adaptx" ) ) + { + log( "DEPRECATED - adaptx processor is deprecated. Use trax or xalan instead." ); + final Class clazz = + loadClass( "org.apache.tools.ant.taskdefs.optional.AdaptxLiaison" ); + liaison = ( XSLTLiaison )clazz.newInstance(); + } + else + { + liaison = ( XSLTLiaison )loadClass( proc ).newInstance(); + } + } + + public class Param + { + private String name = null; + private String expression = null; + + public void setExpression( String expression ) + { + this.expression = expression; + } + + public void setName( String name ) + { + this.name = name; + } + + public String getExpression() + throws BuildException + { + if( expression == null ) + throw new BuildException( "Expression attribute is missing." ); + return expression; + } + + public String getName() + throws BuildException + { + if( name == null ) + throw new BuildException( "Name attribute is missing." ); + return name; + } + } + +}//-- XSLTProcess diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Zip.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Zip.java new file mode 100644 index 000000000..802ec0476 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/Zip.java @@ -0,0 +1,888 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Hashtable; +import java.util.Stack; +import java.util.Vector; +import java.util.zip.CRC32; +import java.util.zip.ZipInputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.FileScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.ZipFileSet; +import org.apache.tools.ant.types.ZipScanner; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.MergingMapper; +import org.apache.tools.ant.util.SourceFileScanner; +import org.apache.tools.zip.ZipEntry; +import org.apache.tools.zip.ZipOutputStream; + +/** + * Create a ZIP archive. + * + * @author James Davidson duncan@x180.com + * @author Jon S. Stevens jon@clearink.com + * @author Stefan Bodewig + */ +public class Zip extends MatchingTask +{ + + // For directories: + private final static long EMPTY_CRC = new CRC32().getValue(); + private boolean doCompress = true; + private boolean doUpdate = false; + private boolean doFilesonly = false; + protected String archiveType = "zip"; + protected String emptyBehavior = "skip"; + private Vector filesets = new Vector(); + protected Hashtable addedDirs = new Hashtable(); + private Vector addedFiles = new Vector(); + + protected File zipFile; + + /** + * true when we are adding new files into the Zip file, as opposed to adding + * back the unchanged files + */ + private boolean addingNewFiles; + private File baseDir; + + /** + * Encoding to use for filenames, defaults to the platform's default + * encoding. + */ + private String encoding; + + protected static String[][] grabFileNames( FileScanner[] scanners ) + { + String[][] result = new String[scanners.length][]; + for( int i = 0; i < scanners.length; i++ ) + { + String[] files = scanners[i].getIncludedFiles(); + String[] dirs = scanners[i].getIncludedDirectories(); + result[i] = new String[files.length + dirs.length]; + System.arraycopy( files, 0, result[i], 0, files.length ); + System.arraycopy( dirs, 0, result[i], files.length, dirs.length ); + } + return result; + } + + protected static File[] grabFiles( FileScanner[] scanners ) + { + return grabFiles( scanners, grabFileNames( scanners ) ); + } + + protected static File[] grabFiles( FileScanner[] scanners, + String[][] fileNames ) + { + Vector files = new Vector(); + for( int i = 0; i < fileNames.length; i++ ) + { + File thisBaseDir = scanners[i].getBasedir(); + for( int j = 0; j < fileNames[i].length; j++ ) + files.addElement( new File( thisBaseDir, fileNames[i][j] ) ); + } + File[] toret = new File[files.size()]; + files.copyInto( toret ); + return toret; + } + + /** + * This is the base directory to look in for things to zip. + * + * @param baseDir The new Basedir value + */ + public void setBasedir( File baseDir ) + { + this.baseDir = baseDir; + } + + /** + * Sets whether we want to compress the files or only store them. + * + * @param c The new Compress value + */ + public void setCompress( boolean c ) + { + doCompress = c; + } + + /** + * Encoding to use for filenames, defaults to the platform's default + * encoding.

              + * + * For a list of possible values see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * .

              + * + * @param encoding The new Encoding value + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * This is the name/location of where to create the .zip file. + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.zipFile = file; + } + + /** + * Emulate Sun's jar utility by not adding parent dirs + * + * @param f The new Filesonly value + */ + public void setFilesonly( boolean f ) + { + doFilesonly = f; + } + + /** + * Sets whether we want to update the file (if it exists) or create a new + * one. + * + * @param c The new Update value + */ + public void setUpdate( boolean c ) + { + doUpdate = c; + } + + /** + * Sets behavior of the task when no files match. Possible values are: + * fail (throw an exception and halt the build); skip + * (do not create any archive, but issue a warning); create + * (make an archive with no entries). Default for zip tasks is skip + * ; for jar tasks, create. + * + * @param we The new Whenempty value + */ + public void setWhenempty( WhenEmpty we ) + { + emptyBehavior = we.getValue(); + } + + /** + * This is the name/location of where to create the .zip file. + * + * @param zipFile The new Zipfile value + * @deprecated Use setFile() instead + */ + public void setZipfile( File zipFile ) + { + log( "DEPRECATED - The zipfile attribute is deprecated. Use file attribute instead." ); + setFile( zipFile ); + } + + /** + * Are we updating an existing archive? + * + * @return The InUpdateMode value + */ + public boolean isInUpdateMode() + { + return doUpdate; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Adds a set of files (nested zipfileset attribute) that can be read from + * an archive and be given a prefix/fullpath. + * + * @param set The feature to be added to the Zipfileset attribute + */ + public void addZipfileset( ZipFileSet set ) + { + filesets.addElement( set ); + } + + public void execute() + throws BuildException + { + if( baseDir == null && filesets.size() == 0 && "zip".equals( archiveType ) ) + { + throw new BuildException( "basedir attribute must be set, or at least " + + "one fileset must be given!" ); + } + + if( zipFile == null ) + { + throw new BuildException( "You must specify the " + archiveType + " file to create!" ); + } + + // Renamed version of original file, if it exists + File renamedFile = null; + // Whether or not an actual update is required - + // we don't need to update if the original file doesn't exist + + addingNewFiles = true; + doUpdate = doUpdate && zipFile.exists(); + if( doUpdate ) + { + FileUtils fileUtils = FileUtils.newFileUtils(); + renamedFile = fileUtils.createTempFile( "zip", ".tmp", + fileUtils.getParentFile( zipFile ) ); + + try + { + if( !zipFile.renameTo( renamedFile ) ) + { + throw new BuildException( "Unable to rename old file to temporary file" ); + } + } + catch( SecurityException e ) + { + throw new BuildException( "Not allowed to rename old file to temporary file" ); + } + } + + // Create the scanners to pass to isUpToDate(). + Vector dss = new Vector(); + if( baseDir != null ) + { + dss.addElement( getDirectoryScanner( baseDir ) ); + } + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + dss.addElement( fs.getDirectoryScanner( project ) ); + } + int dssSize = dss.size(); + FileScanner[] scanners = new FileScanner[dssSize]; + dss.copyInto( scanners ); + + // quick exit if the target is up to date + // can also handle empty archives + if( isUpToDate( scanners, zipFile ) ) + { + return; + } + + String action = doUpdate ? "Updating " : "Building "; + + log( action + archiveType + ": " + zipFile.getAbsolutePath() ); + + boolean success = false; + try + { + ZipOutputStream zOut = + new ZipOutputStream( new FileOutputStream( zipFile ) ); + zOut.setEncoding( encoding ); + try + { + if( doCompress ) + { + zOut.setMethod( ZipOutputStream.DEFLATED ); + } + else + { + zOut.setMethod( ZipOutputStream.STORED ); + } + initZipOutputStream( zOut ); + + // Add the implicit fileset to the archive. + if( baseDir != null ) + { + addFiles( getDirectoryScanner( baseDir ), zOut, "", "" ); + } + // Add the explicit filesets to the archive. + addFiles( filesets, zOut ); + if( doUpdate ) + { + addingNewFiles = false; + ZipFileSet oldFiles = new ZipFileSet(); + oldFiles.setSrc( renamedFile ); + + StringBuffer exclusionPattern = new StringBuffer(); + for( int i = 0; i < addedFiles.size(); i++ ) + { + if( i != 0 ) + { + exclusionPattern.append( "," ); + } + exclusionPattern.append( ( String )addedFiles.elementAt( i ) ); + } + oldFiles.setExcludes( exclusionPattern.toString() ); + Vector tmp = new Vector(); + tmp.addElement( oldFiles ); + addFiles( tmp, zOut ); + } + finalizeZipOutputStream( zOut ); + success = true; + } + finally + { + // Close the output stream. + try + { + if( zOut != null ) + { + zOut.close(); + } + } + catch( IOException ex ) + { + // If we're in this finally clause because of an exception, we don't + // really care if there's an exception when closing the stream. E.g. if it + // throws "ZIP file must have at least one entry", because an exception happened + // before we added any files, then we must swallow this exception. Otherwise, + // the error that's reported will be the close() error, which is not the real + // cause of the problem. + if( success ) + throw ex; + } + } + } + catch( IOException ioe ) + { + String msg = "Problem creating " + archiveType + ": " + ioe.getMessage(); + + // delete a bogus ZIP file + if( !zipFile.delete() ) + { + msg += " (and the archive is probably corrupt but I could not delete it)"; + } + + if( doUpdate ) + { + if( !renamedFile.renameTo( zipFile ) ) + { + msg += " (and I couldn't rename the temporary file " + + renamedFile.getName() + " back)"; + } + } + + throw new BuildException( msg, ioe, location ); + } + finally + { + cleanUp(); + } + + // If we've been successful on an update, delete the temporary file + if( success && doUpdate ) + { + if( !renamedFile.delete() ) + { + log( "Warning: unable to delete temporary file " + + renamedFile.getName(), Project.MSG_WARN ); + } + } + } + + /** + * Indicates if the task is adding new files into the archive as opposed to + * copying back unchanged files from the backup copy + * + * @return The AddingNewFiles value + */ + protected boolean isAddingNewFiles() + { + return addingNewFiles; + } + + + /** + * Check whether the archive is up-to-date; and handle behavior for empty + * archives. + * + * @param scanners list of prepared scanners containing files to archive + * @param zipFile intended archive file (may or may not exist) + * @return true if nothing need be done (may have done something already); + * false if archive creation should proceed + * @exception BuildException if it likes + */ + protected boolean isUpToDate( FileScanner[] scanners, File zipFile ) + throws BuildException + { + String[][] fileNames = grabFileNames( scanners ); + File[] files = grabFiles( scanners, fileNames ); + if( files.length == 0 ) + { + if( emptyBehavior.equals( "skip" ) ) + { + log( "Warning: skipping " + archiveType + " archive " + zipFile + + " because no files were included.", Project.MSG_WARN ); + return true; + } + else if( emptyBehavior.equals( "fail" ) ) + { + throw new BuildException( "Cannot create " + archiveType + " archive " + zipFile + + ": no files were included.", location ); + } + else + { + // Create. + return createEmptyZip( zipFile ); + } + } + else + { + for( int i = 0; i < files.length; ++i ) + { + if( files[i].equals( zipFile ) ) + { + throw new BuildException( "A zip file cannot include itself", location ); + } + } + + if( !zipFile.exists() ) + return false; + + SourceFileScanner sfs = new SourceFileScanner( this ); + MergingMapper mm = new MergingMapper(); + mm.setTo( zipFile.getAbsolutePath() ); + for( int i = 0; i < scanners.length; i++ ) + { + if( sfs.restrict( fileNames[i], scanners[i].getBasedir(), null, + mm ).length > 0 ) + { + return false; + } + } + return true; + } + } + + /** + * Add all files of the given FileScanner to the ZipOutputStream prependig + * the given prefix to each filename.

              + * + * Ensure parent directories have been added as well. + * + * @param scanner The feature to be added to the Files attribute + * @param zOut The feature to be added to the Files attribute + * @param prefix The feature to be added to the Files attribute + * @param fullpath The feature to be added to the Files attribute + * @exception IOException Description of Exception + */ + protected void addFiles( FileScanner scanner, ZipOutputStream zOut, + String prefix, String fullpath ) + throws IOException + { + if( prefix.length() > 0 && fullpath.length() > 0 ) + throw new BuildException( "Both prefix and fullpath attributes may not be set on the same fileset." ); + + File thisBaseDir = scanner.getBasedir(); + + // directories that matched include patterns + String[] dirs = scanner.getIncludedDirectories(); + if( dirs.length > 0 && fullpath.length() > 0 ) + throw new BuildException( "fullpath attribute may only be specified for filesets that specify a single file." ); + for( int i = 0; i < dirs.length; i++ ) + { + if( "".equals( dirs[i] ) ) + { + continue; + } + String name = dirs[i].replace( File.separatorChar, '/' ); + if( !name.endsWith( "/" ) ) + { + name += "/"; + } + addParentDirs( thisBaseDir, name, zOut, prefix ); + } + + // files that matched include patterns + String[] files = scanner.getIncludedFiles(); + if( files.length > 1 && fullpath.length() > 0 ) + throw new BuildException( "fullpath attribute may only be specified for filesets that specify a single file." ); + for( int i = 0; i < files.length; i++ ) + { + File f = new File( thisBaseDir, files[i] ); + if( fullpath.length() > 0 ) + { + // Add this file at the specified location. + addParentDirs( null, fullpath, zOut, "" ); + zipFile( f, zOut, fullpath ); + } + else + { + // Add this file with the specified prefix. + String name = files[i].replace( File.separatorChar, '/' ); + addParentDirs( thisBaseDir, name, zOut, prefix ); + zipFile( f, zOut, prefix + name ); + } + } + } + + /** + * Iterate over the given Vector of (zip)filesets and add all files to the + * ZipOutputStream using the given prefix or fullpath. + * + * @param filesets The feature to be added to the Files attribute + * @param zOut The feature to be added to the Files attribute + * @exception IOException Description of Exception + */ + protected void addFiles( Vector filesets, ZipOutputStream zOut ) + throws IOException + { + // Add each fileset in the Vector. + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + + String prefix = ""; + String fullpath = ""; + if( fs instanceof ZipFileSet ) + { + ZipFileSet zfs = ( ZipFileSet )fs; + prefix = zfs.getPrefix(); + fullpath = zfs.getFullpath(); + } + + if( prefix.length() > 0 + && !prefix.endsWith( "/" ) + && !prefix.endsWith( "\\" ) ) + { + prefix += "/"; + } + + // Need to manually add either fullpath's parent directory, or + // the prefix directory, to the archive. + if( prefix.length() > 0 ) + { + addParentDirs( null, prefix, zOut, "" ); + zipDir( null, zOut, prefix ); + } + else if( fullpath.length() > 0 ) + { + addParentDirs( null, fullpath, zOut, "" ); + } + + if( fs instanceof ZipFileSet + && ( ( ZipFileSet )fs ).getSrc() != null ) + { + addZipEntries( ( ZipFileSet )fs, ds, zOut, prefix, fullpath ); + } + else + { + // Add the fileset. + addFiles( ds, zOut, prefix, fullpath ); + } + } + } + + /** + * Ensure all parent dirs of a given entry have been added. + * + * @param baseDir The feature to be added to the ParentDirs attribute + * @param entry The feature to be added to the ParentDirs attribute + * @param zOut The feature to be added to the ParentDirs attribute + * @param prefix The feature to be added to the ParentDirs attribute + * @exception IOException Description of Exception + */ + protected void addParentDirs( File baseDir, String entry, + ZipOutputStream zOut, String prefix ) + throws IOException + { + if( !doFilesonly ) + { + Stack directories = new Stack(); + int slashPos = entry.length(); + + while( ( slashPos = entry.lastIndexOf( ( int )'/', slashPos - 1 ) ) != -1 ) + { + String dir = entry.substring( 0, slashPos + 1 ); + if( addedDirs.get( prefix + dir ) != null ) + { + break; + } + directories.push( dir ); + } + + while( !directories.isEmpty() ) + { + String dir = ( String )directories.pop(); + File f = null; + if( baseDir != null ) + { + f = new File( baseDir, dir ); + } + else + { + f = new File( dir ); + } + zipDir( f, zOut, prefix + dir ); + } + } + } + + protected void addZipEntries( ZipFileSet fs, DirectoryScanner ds, + ZipOutputStream zOut, String prefix, String fullpath ) + throws IOException + { + if( prefix.length() > 0 && fullpath.length() > 0 ) + throw new BuildException( "Both prefix and fullpath attributes may not be set on the same fileset." ); + + ZipScanner zipScanner = ( ZipScanner )ds; + File zipSrc = fs.getSrc(); + + ZipEntry entry; + java.util.zip.ZipEntry origEntry; + ZipInputStream in = null; + try + { + in = new ZipInputStream( new FileInputStream( zipSrc ) ); + + while( ( origEntry = in.getNextEntry() ) != null ) + { + entry = new ZipEntry( origEntry ); + String vPath = entry.getName(); + if( zipScanner.match( vPath ) ) + { + if( fullpath.length() > 0 ) + { + addParentDirs( null, fullpath, zOut, "" ); + zipFile( in, zOut, fullpath, entry.getTime() ); + } + else + { + addParentDirs( null, vPath, zOut, prefix ); + if( !entry.isDirectory() ) + { + zipFile( in, zOut, prefix + vPath, entry.getTime() ); + } + } + } + } + } + finally + { + if( in != null ) + { + in.close(); + } + } + } + + /** + * Do any clean up necessary to allow this instance to be used again.

              + * + * When we get here, the Zip file has been closed and all we need to do is + * to reset some globals.

              + */ + protected void cleanUp() + { + addedDirs = new Hashtable(); + addedFiles = new Vector(); + filesets = new Vector(); + zipFile = null; + baseDir = null; + doCompress = true; + doUpdate = false; + doFilesonly = false; + addingNewFiles = false; + encoding = null; + } + + /** + * Create an empty zip file + * + * @param zipFile Description of Parameter + * @return true if the file is then considered up to date. + */ + protected boolean createEmptyZip( File zipFile ) + { + // In this case using java.util.zip will not work + // because it does not permit a zero-entry archive. + // Must create it manually. + log( "Note: creating empty " + archiveType + " archive " + zipFile, Project.MSG_INFO ); + try + { + OutputStream os = new FileOutputStream( zipFile ); + try + { + // Cf. PKZIP specification. + byte[] empty = new byte[22]; + empty[0] = 80;// P + empty[1] = 75;// K + empty[2] = 5; + empty[3] = 6; + // remainder zeros + os.write( empty ); + } + finally + { + os.close(); + } + } + catch( IOException ioe ) + { + throw new BuildException( "Could not create empty ZIP archive", ioe, location ); + } + return true; + } + + protected void finalizeZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException { } + + protected void initZipOutputStream( ZipOutputStream zOut ) + throws IOException, BuildException { } + + protected void zipDir( File dir, ZipOutputStream zOut, String vPath ) + throws IOException + { + if( addedDirs.get( vPath ) != null ) + { + // don't add directories we've already added. + // no warning if we try, it is harmless in and of itself + return; + } + addedDirs.put( vPath, vPath ); + + ZipEntry ze = new ZipEntry( vPath ); + if( dir != null && dir.exists() ) + { + ze.setTime( dir.lastModified() ); + } + else + { + ze.setTime( System.currentTimeMillis() ); + } + ze.setSize( 0 ); + ze.setMethod( ZipEntry.STORED ); + // This is faintly ridiculous: + ze.setCrc( EMPTY_CRC ); + + // this is 040775 | MS-DOS directory flag in reverse byte order + ze.setExternalAttributes( 0x41FD0010L ); + + zOut.putNextEntry( ze ); + } + + protected void zipFile( InputStream in, ZipOutputStream zOut, String vPath, + long lastModified ) + throws IOException + { + ZipEntry ze = new ZipEntry( vPath ); + ze.setTime( lastModified ); + + /* + * XXX ZipOutputStream.putEntry expects the ZipEntry to know its + * size and the CRC sum before you start writing the data when using + * STORED mode. + * + * This forces us to process the data twice. + * + * I couldn't find any documentation on this, just found out by try + * and error. + */ + if( !doCompress ) + { + long size = 0; + CRC32 cal = new CRC32(); + if( !in.markSupported() ) + { + // Store data into a byte[] + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + size += count; + cal.update( buffer, 0, count ); + bos.write( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + in = new ByteArrayInputStream( bos.toByteArray() ); + + } + else + { + in.mark( Integer.MAX_VALUE ); + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + size += count; + cal.update( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + in.reset(); + } + ze.setSize( size ); + ze.setCrc( cal.getValue() ); + } + + zOut.putNextEntry( ze ); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + if( count != 0 ) + { + zOut.write( buffer, 0, count ); + } + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + addedFiles.addElement( vPath ); + } + + protected void zipFile( File file, ZipOutputStream zOut, String vPath ) + throws IOException + { + if( file.equals( zipFile ) ) + { + throw new BuildException( "A zip file cannot include itself", location ); + } + + FileInputStream fIn = new FileInputStream( file ); + try + { + zipFile( fIn, zOut, vPath, file.lastModified() ); + } + finally + { + fIn.close(); + } + } + + + /** + * Possible behaviors when there are no matching files for the task. + * + * @author RT + */ + public static class WhenEmpty extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"fail", "skip", "create"}; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java new file mode 100644 index 000000000..f82338b01 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapter.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Javac; + +/** + * The interface that all compiler adapters must adher to.

              + * + * A compiler adapter is an adapter that interprets the javac's parameters in + * preperation to be passed off to the compier this adapter represents. As all + * the necessary values are stored in the Javac task itself, the only thing all + * adapters need is the javac task, the execute command and a parameterless + * constructor (for reflection).

              + * + * @author Jay Dickon Glanville + * jayglanville@home.com + */ + +public interface CompilerAdapter +{ + + /** + * Sets the compiler attributes, which are stored in the Javac task. + * + * @param attributes The new Javac value + */ + void setJavac( Javac attributes ); + + /** + * Executes the task. + * + * @return has the compilation been successful + * @exception BuildException Description of Exception + */ + boolean execute() + throws BuildException; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java new file mode 100644 index 000000000..5e0e91536 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/CompilerAdapterFactory.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; + +/** + * Creates the necessary compiler adapter, given basic criteria. + * + * @author J D Glanville + */ +public class CompilerAdapterFactory +{ + + /** + * This is a singlton -- can't create instances!! + */ + private CompilerAdapterFactory() { } + + /** + * Based on the parameter passed in, this method creates the necessary + * factory desired. The current mapping for compiler names are as follows: + * + *
                + *
              • jikes = jikes compiler + *
              • classic, javac1.1, javac1.2 = the standard compiler from JDK + * 1.1/1.2 + *
              • modern, javac1.3 = the new compiler of JDK 1.3 + *
              • jvc, microsoft = the command line compiler from Microsoft's SDK + * for Java / Visual J++ + *
              • kjc = the kopi compiler
              • + *
              • gcj = the gcj compiler from gcc
              • + *
              • a fully quallified classname = the name of a compiler + * adapter + *
              + * + * + * @param compilerType either the name of the desired compiler, or the full + * classname of the compiler's adapter. + * @param task a task to log through. + * @return The Compiler value + * @throws BuildException if the compiler type could not be resolved into a + * compiler adapter. + */ + public static CompilerAdapter getCompiler( String compilerType, Task task ) + throws BuildException + { + /* + * If I've done things right, this should be the extent of the + * conditional statements required. + */ + if( compilerType.equalsIgnoreCase( "jikes" ) ) + { + return new Jikes(); + } + if( compilerType.equalsIgnoreCase( "extJavac" ) ) + { + return new JavacExternal(); + } + if( compilerType.equalsIgnoreCase( "classic" ) || + compilerType.equalsIgnoreCase( "javac1.1" ) || + compilerType.equalsIgnoreCase( "javac1.2" ) ) + { + return new Javac12(); + } + if( compilerType.equalsIgnoreCase( "modern" ) || + compilerType.equalsIgnoreCase( "javac1.3" ) || + compilerType.equalsIgnoreCase( "javac1.4" ) ) + { + // does the modern compiler exist? + try + { + Class.forName( "com.sun.tools.javac.Main" ); + } + catch( ClassNotFoundException cnfe ) + { + task.log( "Modern compiler is not available - using " + + "classic compiler", Project.MSG_WARN ); + return new Javac12(); + } + return new Javac13(); + } + if( compilerType.equalsIgnoreCase( "jvc" ) || + compilerType.equalsIgnoreCase( "microsoft" ) ) + { + return new Jvc(); + } + if( compilerType.equalsIgnoreCase( "kjc" ) ) + { + return new Kjc(); + } + if( compilerType.equalsIgnoreCase( "gcj" ) ) + { + return new Gcj(); + } + if( compilerType.equalsIgnoreCase( "sj" ) || + compilerType.equalsIgnoreCase( "symantec" ) ) + { + return new Sj(); + } + return resolveClassName( compilerType ); + } + + /** + * Tries to resolve the given classname into a compiler adapter. Throws a + * fit if it can't. + * + * @param className The fully qualified classname to be created. + * @return Description of the Returned Value + * @throws BuildException This is the fit that is thrown if className isn't + * an instance of CompilerAdapter. + */ + private static CompilerAdapter resolveClassName( String className ) + throws BuildException + { + try + { + Class c = Class.forName( className ); + Object o = c.newInstance(); + return ( CompilerAdapter )o; + } + catch( ClassNotFoundException cnfe ) + { + throw new BuildException( className + " can\'t be found.", cnfe ); + } + catch( ClassCastException cce ) + { + throw new BuildException( className + " isn\'t the classname of " + + "a compiler adapter.", cce ); + } + catch( Throwable t ) + { + // for all other possibilities + throw new BuildException( className + " caused an interesting " + + "exception.", t ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java new file mode 100644 index 000000000..a4675b48d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/DefaultCompilerAdapter.java @@ -0,0 +1,494 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.Javac; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.util.FileUtils; + +/** + * This is the default implementation for the CompilerAdapter interface. + * Currently, this is a cut-and-paste of the original javac task. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public abstract class DefaultCompilerAdapter implements CompilerAdapter +{ + protected static String lSep = System.getProperty( "line.separator" ); + protected boolean debug = false; + protected boolean optimize = false; + protected boolean deprecation = false; + protected boolean depend = false; + protected boolean verbose = false; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + protected Javac attributes; + protected Path bootclasspath; + protected Path compileClasspath; + + protected File[] compileList; + protected File destDir; + protected String encoding; + protected Path extdirs; + protected boolean includeAntRuntime; + protected boolean includeJavaRuntime; + protected Location location; + protected String memoryInitialSize; + protected String memoryMaximumSize; + protected Project project; + + /* + * jdg - TODO - all these attributes are currently protected, but they + * should probably be private in the near future. + */ + protected Path src; + protected String target; + + public void setJavac( Javac attributes ) + { + this.attributes = attributes; + src = attributes.getSrcdir(); + destDir = attributes.getDestdir(); + encoding = attributes.getEncoding(); + debug = attributes.getDebug(); + optimize = attributes.getOptimize(); + deprecation = attributes.getDeprecation(); + depend = attributes.getDepend(); + verbose = attributes.getVerbose(); + target = attributes.getTarget(); + bootclasspath = attributes.getBootclasspath(); + extdirs = attributes.getExtdirs(); + compileList = attributes.getFileList(); + compileClasspath = attributes.getClasspath(); + project = attributes.getProject(); + location = attributes.getLocation(); + includeAntRuntime = attributes.getIncludeantruntime(); + includeJavaRuntime = attributes.getIncludejavaruntime(); + memoryInitialSize = attributes.getMemoryInitialSize(); + memoryMaximumSize = attributes.getMemoryMaximumSize(); + } + + public Javac getJavac() + { + return attributes; + } + + protected Commandline setupJavacCommand() + { + return setupJavacCommand( false ); + } + + /** + * Does the command line argument processing for classic and adds the files + * to compile as well. + * + * @param debugLevelCheck Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline setupJavacCommand( boolean debugLevelCheck ) + { + Commandline cmd = new Commandline(); + setupJavacCommandlineSwitches( cmd, debugLevelCheck ); + logAndAddFilesToCompile( cmd ); + return cmd; + } + + protected Commandline setupJavacCommandlineSwitches( Commandline cmd ) + { + return setupJavacCommandlineSwitches( cmd, false ); + } + + /** + * Does the command line argument processing common to classic and modern. + * Doesn't add the files to compile. + * + * @param cmd Description of Parameter + * @param useDebugLevel Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline setupJavacCommandlineSwitches( Commandline cmd, + boolean useDebugLevel ) + { + Path classpath = getCompileClasspath(); + + // we cannot be using Java 1.0 when forking, so we only have to + // distinguish between Java 1.1, and Java 1.2 and higher, as Java 1.1 + // has its own parameter format + boolean usingJava1_1 = Project.getJavaVersion().equals( Project.JAVA_1_1 ); + String memoryParameterPrefix = usingJava1_1 ? "-J-" : "-J-X"; + if( memoryInitialSize != null ) + { + if( !attributes.isForkedJavac() ) + { + attributes.log( "Since fork is false, ignoring memoryInitialSize setting.", + Project.MSG_WARN ); + } + else + { + cmd.createArgument().setValue( memoryParameterPrefix + "ms" + memoryInitialSize ); + } + } + + if( memoryMaximumSize != null ) + { + if( !attributes.isForkedJavac() ) + { + attributes.log( "Since fork is false, ignoring memoryMaximumSize setting.", + Project.MSG_WARN ); + } + else + { + cmd.createArgument().setValue( memoryParameterPrefix + "mx" + memoryMaximumSize ); + } + } + + if( attributes.getNowarn() ) + { + cmd.createArgument().setValue( "-nowarn" ); + } + + if( deprecation == true ) + { + cmd.createArgument().setValue( "-deprecation" ); + } + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + cmd.createArgument().setValue( "-classpath" ); + + // Just add "sourcepath" to classpath ( for JDK1.1 ) + // as well as "bootclasspath" and "extdirs" + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + Path cp = new Path( project ); + /* + * XXX - This doesn't mix very well with build.systemclasspath, + */ + if( bootclasspath != null ) + { + cp.append( bootclasspath ); + } + if( extdirs != null ) + { + cp.addExtdirs( extdirs ); + } + cp.append( classpath ); + cp.append( src ); + cmd.createArgument().setPath( cp ); + } + else + { + cmd.createArgument().setPath( classpath ); + cmd.createArgument().setValue( "-sourcepath" ); + cmd.createArgument().setPath( src ); + if( target != null ) + { + cmd.createArgument().setValue( "-target" ); + cmd.createArgument().setValue( target ); + } + if( bootclasspath != null ) + { + cmd.createArgument().setValue( "-bootclasspath" ); + cmd.createArgument().setPath( bootclasspath ); + } + if( extdirs != null ) + { + cmd.createArgument().setValue( "-extdirs" ); + cmd.createArgument().setPath( extdirs ); + } + } + + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + if( debug ) + { + if( useDebugLevel + && Project.getJavaVersion() != Project.JAVA_1_0 + && Project.getJavaVersion() != Project.JAVA_1_1 ) + { + + String debugLevel = attributes.getDebugLevel(); + if( debugLevel != null ) + { + cmd.createArgument().setValue( "-g:" + debugLevel ); + } + else + { + cmd.createArgument().setValue( "-g" ); + } + } + else + { + cmd.createArgument().setValue( "-g" ); + } + } + else if( Project.getJavaVersion() != Project.JAVA_1_0 && + Project.getJavaVersion() != Project.JAVA_1_1 ) + { + cmd.createArgument().setValue( "-g:none" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "-O" ); + } + + if( depend ) + { + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + cmd.createArgument().setValue( "-depend" ); + } + else if( Project.getJavaVersion().startsWith( "1.2" ) ) + { + cmd.createArgument().setValue( "-Xdepend" ); + } + else + { + attributes.log( "depend attribute is not supported by the modern compiler", + Project.MSG_WARN ); + } + } + + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + + addCurrentCompilerArgs( cmd ); + + return cmd; + } + + /** + * Does the command line argument processing for modern and adds the files + * to compile as well. + * + * @return Description of the Returned Value + */ + protected Commandline setupModernJavacCommand() + { + Commandline cmd = new Commandline(); + setupModernJavacCommandlineSwitches( cmd ); + + logAndAddFilesToCompile( cmd ); + return cmd; + } + + /** + * Does the command line argument processing for modern. Doesn't add the + * files to compile. + * + * @param cmd Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline setupModernJavacCommandlineSwitches( Commandline cmd ) + { + setupJavacCommandlineSwitches( cmd, true ); + if( attributes.getSource() != null ) + { + cmd.createArgument().setValue( "-source" ); + cmd.createArgument().setValue( attributes.getSource() ); + } + return cmd; + } + + /** + * Builds the compilation classpath. + * + * @return The CompileClasspath value + */ + protected Path getCompileClasspath() + { + Path classpath = new Path( project ); + + // add dest dir to classpath so that previously compiled and + // untouched classes are on classpath + + if( destDir != null ) + { + classpath.setLocation( destDir ); + } + + // Combine the build classpath with the system classpath, in an + // order determined by the value of build.classpath + + if( compileClasspath == null ) + { + if( includeAntRuntime ) + { + classpath.addExisting( Path.systemClasspath ); + } + } + else + { + if( includeAntRuntime ) + { + classpath.addExisting( compileClasspath.concatSystemClasspath( "last" ) ); + } + else + { + classpath.addExisting( compileClasspath.concatSystemClasspath( "ignore" ) ); + } + } + + if( includeJavaRuntime ) + { + classpath.addJavaRuntime(); + } + + return classpath; + } + + /** + * Adds the command line arguments specifc to the current implementation. + * + * @param cmd The feature to be added to the CurrentCompilerArgs attribute + */ + protected void addCurrentCompilerArgs( Commandline cmd ) + { + cmd.addArguments( getJavac().getCurrentCompilerArgs() ); + } + + /** + * Do the compile with the specified arguments. + * + * @param args - arguments to pass to process on command line + * @param firstFileName - index of the first source file in args + * @return Description of the Returned Value + */ + protected int executeExternalCompile( String[] args, int firstFileName ) + { + String[] commandArray = null; + File tmpFile = null; + + try + { + /* + * Many system have been reported to get into trouble with + * long command lines - no, not only Windows ;-). + * + * POSIX seems to define a lower limit of 4k, so use a temporary + * file if the total length of the command line exceeds this limit. + */ + if( Commandline.toString( args ).length() > 4096 ) + { + PrintWriter out = null; + try + { + tmpFile = fileUtils.createTempFile( "jikes", "", null ); + out = new PrintWriter( new FileWriter( tmpFile ) ); + for( int i = firstFileName; i < args.length; i++ ) + { + out.println( args[i] ); + } + out.flush(); + commandArray = new String[firstFileName + 1]; + System.arraycopy( args, 0, commandArray, 0, firstFileName ); + commandArray[firstFileName] = "@" + tmpFile.getAbsolutePath(); + } + catch( IOException e ) + { + throw new BuildException( "Error creating temporary file", e, location ); + } + finally + { + if( out != null ) + { + try + { + out.close(); + } + catch( Throwable t ) + {} + } + } + } + else + { + commandArray = args; + } + + try + { + Execute exe = new Execute( new LogStreamHandler( attributes, + Project.MSG_INFO, + Project.MSG_WARN ) ); + exe.setAntRun( project ); + exe.setWorkingDirectory( project.getBaseDir() ); + exe.setCommandline( commandArray ); + exe.execute(); + return exe.getExitValue(); + } + catch( IOException e ) + { + throw new BuildException( "Error running " + args[0] + + " compiler", e, location ); + } + } + finally + { + if( tmpFile != null ) + { + tmpFile.delete(); + } + } + } + + /** + * Logs the compilation parameters, adds the files to compile and logs the + * &qout;niceSourceList" + * + * @param cmd Description of Parameter + */ + protected void logAndAddFilesToCompile( Commandline cmd ) + { + attributes.log( "Compilation args: " + cmd.toString(), + Project.MSG_VERBOSE ); + + StringBuffer niceSourceList = new StringBuffer( "File" ); + if( compileList.length != 1 ) + { + niceSourceList.append( "s" ); + } + niceSourceList.append( " to be compiled:" ); + + niceSourceList.append( lSep ); + + for( int i = 0; i < compileList.length; i++ ) + { + String arg = compileList[i].getAbsolutePath(); + cmd.createArgument().setValue( arg ); + niceSourceList.append( " " + arg + lSep ); + } + + attributes.log( niceSourceList.toString(), Project.MSG_VERBOSE ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Gcj.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Gcj.java new file mode 100644 index 000000000..ee0918e10 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Gcj.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the gcj compiler. This is primarily a cut-and-paste + * from the jikes. + * + * @author Takashi Okamoto + */ +public class Gcj extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the gcj compiler. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @author tora@debian.org + */ + public boolean execute() + throws BuildException + { + Commandline cmd; + attributes.log( "Using gcj compiler", Project.MSG_VERBOSE ); + cmd = setupGCJCommand(); + + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + + protected Commandline setupGCJCommand() + { + Commandline cmd = new Commandline(); + Path classpath = new Path( project ); + + // gcj doesn't support bootclasspath dir (-bootclasspath) + // so we'll emulate it for compatibility and convenience. + if( bootclasspath != null ) + { + classpath.append( bootclasspath ); + } + + // gcj doesn't support an extension dir (-extdir) + // so we'll emulate it for compatibility and convenience. + classpath.addExtdirs( extdirs ); + + if( ( bootclasspath == null ) || ( bootclasspath.size() == 0 ) ) + { + // no bootclasspath, therefore, get one from the java runtime + includeJavaRuntime = true; + } + classpath.append( getCompileClasspath() ); + + // Gcj has no option for source-path so we + // will add it to classpath. + classpath.append( src ); + + cmd.setExecutable( "gcj" ); + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + + if( destDir.mkdirs() ) + { + throw new BuildException( "Can't make output directories. Maybe permission is wrong. " ); + } + ; + } + + cmd.createArgument().setValue( "-classpath" ); + cmd.createArgument().setPath( classpath ); + + if( encoding != null ) + { + cmd.createArgument().setValue( "--encoding=" + encoding ); + } + if( debug ) + { + cmd.createArgument().setValue( "-g1" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "-O" ); + } + + /** + * gcj should be set for generate class. + */ + cmd.createArgument().setValue( "-C" ); + + addCurrentCompilerArgs( cmd ); + + return cmd; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac12.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac12.java new file mode 100644 index 000000000..9eee2523a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac12.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.types.Commandline; + +/** + * The implementation of the javac compiler for JDK 1.2 This is primarily a + * cut-and-paste from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Javac12 extends DefaultCompilerAdapter +{ + + public boolean execute() + throws BuildException + { + attributes.log( "Using classic compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupJavacCommand( true ); + + OutputStream logstr = new LogOutputStream( attributes, Project.MSG_WARN ); + try + { + // Create an instance of the compiler, redirecting output to + // the project log + Class c = Class.forName( "sun.tools.javac.Main" ); + Constructor cons = c.getConstructor( new Class[]{OutputStream.class, String.class} ); + Object compiler = cons.newInstance( new Object[]{logstr, "javac"} ); + + // Call the compile() method + Method compile = c.getMethod( "compile", new Class[]{String[].class} ); + Boolean ok = ( Boolean )compile.invoke( compiler, new Object[]{cmd.getArguments()} ); + return ok.booleanValue(); + } + catch( ClassNotFoundException ex ) + { + throw new BuildException( "Cannot use classic compiler, as it is not available" + + " A common solution is to set the environment variable" + + " JAVA_HOME to your jdk directory.", location ); + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting classic compiler: ", ex, location ); + } + } + finally + { + try + { + logstr.close(); + } + catch( IOException e ) + { + // plain impossible + throw new BuildException( e ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac13.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac13.java new file mode 100644 index 000000000..7516d43c9 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Javac13.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.lang.reflect.Method; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + +/** + * The implementation of the javac compiler for JDK 1.3 This is primarily a + * cut-and-paste from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Javac13 extends DefaultCompilerAdapter +{ + + /** + * Integer returned by the "Modern" jdk1.3 compiler to indicate success. + */ + private final static int MODERN_COMPILER_SUCCESS = 0; + + public boolean execute() + throws BuildException + { + attributes.log( "Using modern compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupModernJavacCommand(); + + // Use reflection to be able to build on all JDKs >= 1.1: + try + { + Class c = Class.forName( "com.sun.tools.javac.Main" ); + Object compiler = c.newInstance(); + Method compile = c.getMethod( "compile", + new Class[]{( new String[]{} ).getClass()} ); + int result = ( ( Integer )compile.invoke + ( compiler, new Object[]{cmd.getArguments()} ) ).intValue(); + return ( result == MODERN_COMPILER_SUCCESS ); + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting modern compiler", ex, location ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java new file mode 100644 index 000000000..528b7cd43 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/JavacExternal.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + +/** + * Performs a compile using javac externally. + * + * @author Brian Deitte + */ +public class JavacExternal extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the Javac externally. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean execute() + throws BuildException + { + attributes.log( "Using external javac compiler", Project.MSG_VERBOSE ); + + Commandline cmd = new Commandline(); + cmd.setExecutable( getJavac().getJavacExecutable() ); + setupModernJavacCommandlineSwitches( cmd ); + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jikes.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jikes.java new file mode 100644 index 000000000..a45f64c3c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jikes.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the jikes compiler. This is primarily a cut-and-paste + * from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Jikes extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the Jikes compiler from IBM.. Mostly of this + * code is identical to doClassicCompile() However, it does not support all + * options like bootclasspath, extdirs, deprecation and so on, because there + * is no option in jikes and I don't understand what they should do. It has + * been successfully tested with jikes >1.10 + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @author skanthak@muehlheim.de + */ + public boolean execute() + throws BuildException + { + attributes.log( "Using jikes compiler", Project.MSG_VERBOSE ); + + Path classpath = new Path( project ); + + // Jikes doesn't support bootclasspath dir (-bootclasspath) + // so we'll emulate it for compatibility and convenience. + if( bootclasspath != null ) + { + classpath.append( bootclasspath ); + } + + // Jikes doesn't support an extension dir (-extdir) + // so we'll emulate it for compatibility and convenience. + classpath.addExtdirs( extdirs ); + + if( ( bootclasspath == null ) || ( bootclasspath.size() == 0 ) ) + { + // no bootclasspath, therefore, get one from the java runtime + includeJavaRuntime = true; + } + else + { + // there is a bootclasspath stated. By default, the + // includeJavaRuntime is false. If the user has stated a + // bootclasspath and said to include the java runtime, it's on + // their head! + } + classpath.append( getCompileClasspath() ); + + // Jikes has no option for source-path so we + // will add it to classpath. + classpath.append( src ); + + // if the user has set JIKESPATH we should add the contents as well + String jikesPath = System.getProperty( "jikes.class.path" ); + if( jikesPath != null ) + { + classpath.append( new Path( project, jikesPath ) ); + } + + Commandline cmd = new Commandline(); + cmd.setExecutable( "jikes" ); + + if( deprecation == true ) + cmd.createArgument().setValue( "-deprecation" ); + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + cmd.createArgument().setValue( "-classpath" ); + cmd.createArgument().setPath( classpath ); + + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + if( debug ) + { + cmd.createArgument().setValue( "-g" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "-O" ); + } + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + if( depend ) + { + cmd.createArgument().setValue( "-depend" ); + } + /** + * XXX Perhaps we shouldn't use properties for these three options + * (emacs mode, warnings and pedantic), but include it in the javac + * directive? + */ + + /** + * Jikes has the nice feature to print error messages in a form readable + * by emacs, so that emacs can directly set the cursor to the place, + * where the error occured. + */ + String emacsProperty = project.getProperty( "build.compiler.emacs" ); + if( emacsProperty != null && Project.toBoolean( emacsProperty ) ) + { + cmd.createArgument().setValue( "+E" ); + } + + /** + * Jikes issues more warnings that javac, for example, when you have + * files in your classpath that don't exist. As this is often the case, + * these warning can be pretty annoying. + */ + String warningsProperty = project.getProperty( "build.compiler.warnings" ); + if( warningsProperty != null ) + { + attributes.log( "!! the build.compiler.warnings property is deprecated. !!", + Project.MSG_WARN ); + attributes.log( "!! Use the nowarn attribute instead. !!", + Project.MSG_WARN ); + if( !Project.toBoolean( warningsProperty ) ) + { + cmd.createArgument().setValue( "-nowarn" ); + } + } + if( attributes.getNowarn() ) + { + /* + * FIXME later + * + * let the magic property win over the attribute for backwards + * compatibility + */ + cmd.createArgument().setValue( "-nowarn" ); + } + + /** + * Jikes can issue pedantic warnings. + */ + String pedanticProperty = project.getProperty( "build.compiler.pedantic" ); + if( pedanticProperty != null && Project.toBoolean( pedanticProperty ) ) + { + cmd.createArgument().setValue( "+P" ); + } + + /** + * Jikes supports something it calls "full dependency checking", see the + * jikes documentation for differences between -depend and +F. + */ + String fullDependProperty = project.getProperty( "build.compiler.fulldepend" ); + if( fullDependProperty != null && Project.toBoolean( fullDependProperty ) ) + { + cmd.createArgument().setValue( "+F" ); + } + + addCurrentCompilerArgs( cmd ); + + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jvc.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jvc.java new file mode 100644 index 000000000..ccb0b0f2a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Jvc.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the jvc compiler from microsoft. This is primarily a + * cut-and-paste from the original javac task before it was refactored. + * + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + */ +public class Jvc extends DefaultCompilerAdapter +{ + + public boolean execute() + throws BuildException + { + attributes.log( "Using jvc compiler", Project.MSG_VERBOSE ); + + Path classpath = new Path( project ); + + // jvc doesn't support bootclasspath dir (-bootclasspath) + // so we'll emulate it for compatibility and convenience. + if( bootclasspath != null ) + { + classpath.append( bootclasspath ); + } + + // jvc doesn't support an extension dir (-extdir) + // so we'll emulate it for compatibility and convenience. + classpath.addExtdirs( extdirs ); + + if( ( bootclasspath == null ) || ( bootclasspath.size() == 0 ) ) + { + // no bootclasspath, therefore, get one from the java runtime + includeJavaRuntime = true; + } + else + { + // there is a bootclasspath stated. By default, the + // includeJavaRuntime is false. If the user has stated a + // bootclasspath and said to include the java runtime, it's on + // their head! + } + classpath.append( getCompileClasspath() ); + + // jvc has no option for source-path so we + // will add it to classpath. + classpath.append( src ); + + Commandline cmd = new Commandline(); + cmd.setExecutable( "jvc" ); + + if( destDir != null ) + { + cmd.createArgument().setValue( "/d" ); + cmd.createArgument().setFile( destDir ); + } + + // Add the Classpath before the "internal" one. + cmd.createArgument().setValue( "/cp:p" ); + cmd.createArgument().setPath( classpath ); + + // Enable MS-Extensions and ... + cmd.createArgument().setValue( "/x-" ); + // ... do not display a Message about this. + cmd.createArgument().setValue( "/nomessage" ); + // Do not display Logo + cmd.createArgument().setValue( "/nologo" ); + + if( debug ) + { + cmd.createArgument().setValue( "/g" ); + } + if( optimize ) + { + cmd.createArgument().setValue( "/O" ); + } + if( verbose ) + { + cmd.createArgument().setValue( "/verbose" ); + } + + addCurrentCompilerArgs( cmd ); + + int firstFileName = cmd.size(); + logAndAddFilesToCompile( cmd ); + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Kjc.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Kjc.java new file mode 100644 index 000000000..2cb2bdc48 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Kjc.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import java.lang.reflect.Method; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * The implementation of the Java compiler for KJC. This is primarily a + * cut-and-paste from Jikes.java and DefaultCompilerAdapter. + * + * @author Takashi Okamoto + + */ +public class Kjc extends DefaultCompilerAdapter +{ + + public boolean execute() + throws BuildException + { + attributes.log( "Using kjc compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupKjcCommand(); + + try + { + Class c = Class.forName( "at.dms.kjc.Main" ); + + // Call the compile() method + Method compile = c.getMethod( "compile", + new Class[]{String[].class} ); + Boolean ok = ( Boolean )compile.invoke( null, + new Object[]{cmd.getArguments()} ); + return ok.booleanValue(); + } + catch( ClassNotFoundException ex ) + { + throw new BuildException( "Cannot use kjc compiler, as it is not available" + + " A common solution is to set the environment variable" + + " CLASSPATH to your kjc archive (kjc.jar).", location ); + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting kjc compiler: ", ex, location ); + } + } + } + + /** + * setup kjc command arguments. + * + * @return Description of the Returned Value + */ + protected Commandline setupKjcCommand() + { + Commandline cmd = new Commandline(); + + // generate classpath, because kjc does't support sourcepath. + Path classpath = getCompileClasspath(); + + if( deprecation == true ) + { + cmd.createArgument().setValue( "-deprecation" ); + } + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + // generate the clsspath + cmd.createArgument().setValue( "-classpath" ); + + Path cp = new Path( project ); + + // kjc don't have bootclasspath option. + if( bootclasspath != null ) + { + cp.append( bootclasspath ); + } + + if( extdirs != null ) + { + cp.addExtdirs( extdirs ); + } + + cp.append( classpath ); + cp.append( src ); + + cmd.createArgument().setPath( cp ); + + // kjc-1.5A doesn't support -encoding option now. + // but it will be supported near the feature. + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + + if( debug ) + { + cmd.createArgument().setValue( "-g" ); + } + + if( optimize ) + { + cmd.createArgument().setValue( "-O2" ); + } + + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + + addCurrentCompilerArgs( cmd ); + + logAndAddFilesToCompile( cmd ); + return cmd; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Sj.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Sj.java new file mode 100644 index 000000000..3ab63f48b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/compilers/Sj.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + +/** + * The implementation of the sj compiler. Uses the defaults for + * DefaultCompilerAdapter + * + * @author Don Ferguson + */ +public class Sj extends DefaultCompilerAdapter +{ + + /** + * Performs a compile using the sj compiler from Symantec. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @author don@bea.com + */ + public boolean execute() + throws BuildException + { + attributes.log( "Using symantec java compiler", Project.MSG_VERBOSE ); + + Commandline cmd = setupJavacCommand(); + cmd.setExecutable( "sj" ); + + int firstFileName = cmd.size() - compileList.length; + + return executeExternalCompile( cmd.getCommandline(), firstFileName ) == 0; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/And.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/And.java new file mode 100644 index 000000000..44b9810ac --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/And.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.util.Enumeration; +import org.apache.tools.ant.BuildException; + +/** + * <and> condition container.

              + * + * Iterates over all conditions and returns false as soon as one evaluates to + * false.

              + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class And extends ConditionBase implements Condition +{ + + public boolean eval() + throws BuildException + { + Enumeration enum = getConditions(); + while( enum.hasMoreElements() ) + { + Condition c = ( Condition )enum.nextElement(); + if( !c.eval() ) + { + return false; + } + } + return true; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Condition.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Condition.java new file mode 100644 index 000000000..fc589867c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Condition.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; + +import org.apache.myrmidon.api.TaskException; + +/** + * Interface for conditions to use inside the <condition> task. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public interface Condition +{ + /** + * Is this condition true? + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + boolean eval() + throws TaskException; +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/ConditionBase.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/ConditionBase.java new file mode 100644 index 000000000..a8a13820d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/ConditionBase.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.util.Enumeration; +import java.util.NoSuchElementException; +import java.util.Vector; +import org.apache.tools.ant.ProjectComponent; +import org.apache.tools.ant.taskdefs.Available; +import org.apache.tools.ant.taskdefs.Checksum; +import org.apache.tools.ant.taskdefs.UpToDate; +import org.apache.myrmidon.framework.Os; + +/** + * Baseclass for the <condition> task as well as several conditions - + * ensures that the types of conditions inside the task and the "container" + * conditions are in sync. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public abstract class ConditionBase extends ProjectComponent +{ + private Vector conditions = new Vector(); + + /** + * Add an <and> condition "container". + * + * @param a The feature to be added to the And attribute + * @since 1.1 + */ + public void addAnd( And a ) + { + conditions.addElement( a ); + } + + /** + * Add an <available> condition. + * + * @param a The feature to be added to the Available attribute + * @since 1.1 + */ + public void addAvailable( Available a ) + { + conditions.addElement( a ); + } + + /** + * Add an <checksum> condition. + * + * @param c The feature to be added to the Checksum attribute + * @since 1.4 + */ + public void addChecksum( Checksum c ) + { + conditions.addElement( c ); + } + + /** + * Add an <equals> condition. + * + * @param e The feature to be added to the Equals attribute + * @since 1.1 + */ + public void addEquals( Equals e ) + { + conditions.addElement( e ); + } + + /** + * Add an <http> condition. + * + * @param h The feature to be added to the Http attribute + * @since 1.7 + */ + public void addHttp( Http h ) + { + conditions.addElement( h ); + } + + /** + * Add an <isset> condition. + * + * @param i The feature to be added to the IsSet attribute + * @since 1.1 + */ + public void addIsSet( IsSet i ) + { + conditions.addElement( i ); + } + + /** + * Add an <not> condition "container". + * + * @param n The feature to be added to the Not attribute + * @since 1.1 + */ + public void addNot( Not n ) + { + conditions.addElement( n ); + } + + /** + * Add an <or> condition "container". + * + * @param o The feature to be added to the Or attribute + * @since 1.1 + */ + public void addOr( Or o ) + { + conditions.addElement( o ); + } + + /** + * Add an <os> condition. + * + * @param o The feature to be added to the Os attribute + * @since 1.1 + */ + public void addOs( Os o ) + { + conditions.addElement( o ); + } + + /** + * Add a <socket> condition. + * + * @param s The feature to be added to the Socket attribute + * @since 1.7 + */ + public void addSocket( Socket s ) + { + conditions.addElement( s ); + } + + /** + * Add an <uptodate> condition. + * + * @param u The feature to be added to the Uptodate attribute + * @since 1.1 + */ + public void addUptodate( UpToDate u ) + { + conditions.addElement( u ); + } + + /** + * Iterate through all conditions. + * + * @return The Conditions value + * @since 1.1 + */ + protected final Enumeration getConditions() + { + return new ConditionEnumeration(); + } + + /** + * Count the conditions. + * + * @return Description of the Returned Value + * @since 1.1 + */ + protected int countConditions() + { + return conditions.size(); + } + + /** + * Inner class that configures those conditions with a project instance that + * need it. + * + * @author RT + * @since 1.1 + */ + private class ConditionEnumeration implements Enumeration + { + private int currentElement = 0; + + public boolean hasMoreElements() + { + return countConditions() > currentElement; + } + + public Object nextElement() + throws NoSuchElementException + { + Object o = null; + try + { + o = conditions.elementAt( currentElement++ ); + } + catch( ArrayIndexOutOfBoundsException e ) + { + throw new NoSuchElementException(); + } + + if( o instanceof ProjectComponent ) + { + ( ( ProjectComponent )o ).setProject( getProject() ); + } + return o; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Equals.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Equals.java new file mode 100644 index 000000000..306f7928f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Equals.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import org.apache.tools.ant.BuildException; + +/** + * Simple String comparison condition. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class Equals implements Condition +{ + + private String arg1, arg2; + + public void setArg1( String a1 ) + { + arg1 = a1; + } + + public void setArg2( String a2 ) + { + arg2 = a2; + } + + public boolean eval() + throws BuildException + { + if( arg1 == null || arg2 == null ) + { + throw new BuildException( "both arg1 and arg2 are required in equals" ); + } + return arg1.equals( arg2 ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Http.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Http.java new file mode 100644 index 000000000..b70aa228a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Http.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; + +/** + * Condition to wait for a HTTP request to succeed. Its attribute(s) are: url - + * the URL of the request. + * + * @author Denis Hennessy + */ +public class Http extends ProjectComponent implements Condition +{ + String spec = null; + + public void setUrl( String url ) + { + spec = url; + } + + public boolean eval() + throws BuildException + { + if( spec == null ) + { + throw new BuildException( "No url specified in HTTP task" ); + } + log( "Checking for " + spec, Project.MSG_VERBOSE ); + try + { + URL url = new URL( spec ); + try + { + URLConnection conn = url.openConnection(); + if( conn instanceof HttpURLConnection ) + { + HttpURLConnection http = ( HttpURLConnection )conn; + int code = http.getResponseCode(); + log( "Result code for " + spec + " was " + code, Project.MSG_VERBOSE ); + if( code > 0 && code < 500 ) + { + return true; + } + else + { + return false; + } + } + } + catch( java.io.IOException e ) + { + return false; + } + } + catch( MalformedURLException e ) + { + throw new BuildException( "Badly formed URL: " + spec, e ); + } + return true; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/IsSet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/IsSet.java new file mode 100644 index 000000000..e7a6a11be --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/IsSet.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ProjectComponent; + +/** + * Condition that tests whether a given property has been set. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class IsSet extends ProjectComponent implements Condition +{ + private String property; + + public void setProperty( String p ) + { + property = p; + } + + public boolean eval() + throws BuildException + { + if( property == null ) + { + throw new BuildException( "No property specified for isset condition" ); + } + + return getProject().getProperty( property ) != null; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Not.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Not.java new file mode 100644 index 000000000..1af3b0bdb --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Not.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import org.apache.tools.ant.BuildException; + +/** + * <not> condition. Evaluates to true if the single condition nested into + * it is false and vice versa. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class Not extends ConditionBase implements Condition +{ + + public boolean eval() + throws BuildException + { + if( countConditions() > 1 ) + { + throw new BuildException( "You must not nest more than one condition into " ); + } + if( countConditions() < 1 ) + { + throw new BuildException( "You must nest a condition into " ); + } + return !( ( Condition )getConditions().nextElement() ).eval(); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Or.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Or.java new file mode 100644 index 000000000..62ca4641c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Or.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.util.Enumeration; +import org.apache.tools.ant.BuildException; + +/** + * <or> condition container.

              + * + * Iterates over all conditions and returns true as soon as one evaluates to + * true.

              + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class Or extends ConditionBase implements Condition +{ + + public boolean eval() + throws BuildException + { + Enumeration enum = getConditions(); + while( enum.hasMoreElements() ) + { + Condition c = ( Condition )enum.nextElement(); + if( c.eval() ) + { + return true; + } + } + return false; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Socket.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Socket.java new file mode 100644 index 000000000..b1e47526a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/condition/Socket.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.condition; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectComponent; + +/** + * Condition to wait for a TCP/IP socket to have a listener. Its attribute(s) + * are: server - the name of the server. port - the port number of the socket. + * + * @author Denis Hennessy + */ +public class Socket extends ProjectComponent implements Condition +{ + String server = null; + int port = 0; + + public void setPort( int port ) + { + this.port = port; + } + + public void setServer( String server ) + { + this.server = server; + } + + public boolean eval() + throws BuildException + { + if( server == null ) + { + throw new BuildException( "No server specified in Socket task" ); + } + if( port == 0 ) + { + throw new BuildException( "No port specified in Socket task" ); + } + log( "Checking for listener at " + server + ":" + port, Project.MSG_VERBOSE ); + try + { + java.net.Socket socket = new java.net.Socket( server, port ); + } + catch( IOException e ) + { + return false; + } + return true; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/defaults.properties b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/defaults.properties new file mode 100644 index 000000000..c680c8f92 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/defaults.properties @@ -0,0 +1,141 @@ +# standard ant tasks +mkdir=org.apache.tools.ant.taskdefs.Mkdir +javac=org.apache.tools.ant.taskdefs.Javac +chmod=org.apache.tools.ant.taskdefs.Chmod +delete=org.apache.tools.ant.taskdefs.Delete +copy=org.apache.tools.ant.taskdefs.Copy +move=org.apache.tools.ant.taskdefs.Move +jar=org.apache.tools.ant.taskdefs.Jar +rmic=org.apache.tools.ant.taskdefs.Rmic +cvs=org.apache.tools.ant.taskdefs.Cvs +get=org.apache.tools.ant.taskdefs.Get +unzip=org.apache.tools.ant.taskdefs.Expand +unjar=org.apache.tools.ant.taskdefs.Expand +unwar=org.apache.tools.ant.taskdefs.Expand +echo=org.apache.tools.ant.taskdefs.Echo +javadoc=org.apache.tools.ant.taskdefs.Javadoc +zip=org.apache.tools.ant.taskdefs.Zip +gzip=org.apache.tools.ant.taskdefs.GZip +gunzip=org.apache.tools.ant.taskdefs.GUnzip +replace=org.apache.tools.ant.taskdefs.Replace +java=org.apache.tools.ant.taskdefs.Java +tstamp=org.apache.tools.ant.taskdefs.Tstamp +property=org.apache.tools.ant.taskdefs.Property +taskdef=org.apache.tools.ant.taskdefs.Taskdef +ant=org.apache.tools.ant.taskdefs.Ant +exec=org.apache.tools.ant.taskdefs.ExecTask +tar=org.apache.tools.ant.taskdefs.Tar +untar=org.apache.tools.ant.taskdefs.Untar +available=org.apache.tools.ant.taskdefs.Available +filter=org.apache.tools.ant.taskdefs.Filter +fixcrlf=org.apache.tools.ant.taskdefs.FixCRLF +patch=org.apache.tools.ant.taskdefs.Patch +style=org.apache.tools.ant.taskdefs.XSLTProcess +touch=org.apache.tools.ant.taskdefs.Touch +signjar=org.apache.tools.ant.taskdefs.SignJar +genkey=org.apache.tools.ant.taskdefs.GenerateKey +antstructure=org.apache.tools.ant.taskdefs.AntStructure +execon=org.apache.tools.ant.taskdefs.ExecuteOn +antcall=org.apache.tools.ant.taskdefs.CallTarget +sql=org.apache.tools.ant.taskdefs.SQLExec +mail=org.apache.tools.ant.taskdefs.SendEmail +fail=org.apache.tools.ant.taskdefs.Exit +war=org.apache.tools.ant.taskdefs.War +uptodate=org.apache.tools.ant.taskdefs.UpToDate +apply=org.apache.tools.ant.taskdefs.Transform +record=org.apache.tools.ant.taskdefs.Recorder +cvspass=org.apache.tools.ant.taskdefs.CVSPass +typedef=org.apache.tools.ant.taskdefs.Typedef +sleep=org.apache.tools.ant.taskdefs.Sleep +pathconvert=org.apache.tools.ant.taskdefs.PathConvert +ear=org.apache.tools.ant.taskdefs.Ear +parallel=org.apache.tools.ant.taskdefs.Parallel +sequential=org.apache.tools.ant.taskdefs.Sequential +condition=org.apache.tools.ant.taskdefs.ConditionTask +dependset=org.apache.tools.ant.taskdefs.DependSet +bzip2=org.apache.tools.ant.taskdefs.BZip2 +bunzip2=org.apache.tools.ant.taskdefs.BUnzip2 +checksum=org.apache.tools.ant.taskdefs.Checksum +waitfor=org.apache.tools.ant.taskdefs.WaitFor +input=org.apache.tools.ant.taskdefs.Input +manifest=org.apache.tools.ant.taskdefs.Manifest + +# optional tasks +script=org.apache.tools.ant.taskdefs.optional.Script +netrexxc=org.apache.tools.ant.taskdefs.optional.NetRexxC +renameext=org.apache.tools.ant.taskdefs.optional.RenameExtensions +ejbc=org.apache.tools.ant.taskdefs.optional.ejb.Ejbc +ddcreator=org.apache.tools.ant.taskdefs.optional.ejb.DDCreator +wlrun=org.apache.tools.ant.taskdefs.optional.ejb.WLRun +wlstop=org.apache.tools.ant.taskdefs.optional.ejb.WLStop +vssget=org.apache.tools.ant.taskdefs.optional.vss.MSVSSGET +ejbjar=org.apache.tools.ant.taskdefs.optional.ejb.EjbJar +mparse=org.apache.tools.ant.taskdefs.optional.metamata.MParse +mmetrics=org.apache.tools.ant.taskdefs.optional.metamata.MMetrics +maudit=org.apache.tools.ant.taskdefs.optional.metamata.MAudit +junit=org.apache.tools.ant.taskdefs.optional.junit.JUnitTask +cab=org.apache.tools.ant.taskdefs.optional.Cab +ftp=org.apache.tools.ant.taskdefs.optional.net.FTP +icontract=org.apache.tools.ant.taskdefs.optional.IContract +javacc=org.apache.tools.ant.taskdefs.optional.javacc.JavaCC +jjtree=org.apache.tools.ant.taskdefs.optional.javacc.JJTree +starteam=org.apache.tools.ant.taskdefs.optional.scm.AntStarTeamCheckOut +wljspc=org.apache.tools.ant.taskdefs.optional.jsp.WLJspc +jlink=org.apache.tools.ant.taskdefs.optional.jlink.JlinkTask +native2ascii=org.apache.tools.ant.taskdefs.optional.Native2Ascii +propertyfile=org.apache.tools.ant.taskdefs.optional.PropertyFile +depend=org.apache.tools.ant.taskdefs.optional.depend.Depend +antlr=org.apache.tools.ant.taskdefs.optional.ANTLR +vajload=org.apache.tools.ant.taskdefs.optional.ide.VAJLoadProjects +vajexport=org.apache.tools.ant.taskdefs.optional.ide.VAJExport +vajimport=org.apache.tools.ant.taskdefs.optional.ide.VAJImport +telnet=org.apache.tools.ant.taskdefs.optional.net.TelnetTask +csc=org.apache.tools.ant.taskdefs.optional.dotnet.CSharp +ilasm=org.apache.tools.ant.taskdefs.optional.dotnet.Ilasm +stylebook=org.apache.tools.ant.taskdefs.optional.StyleBook +test=org.apache.tools.ant.taskdefs.optional.Test +pvcs=org.apache.tools.ant.taskdefs.optional.pvcs.Pvcs +p4change=org.apache.tools.ant.taskdefs.optional.perforce.P4Change +p4label=org.apache.tools.ant.taskdefs.optional.perforce.P4Label +p4have=org.apache.tools.ant.taskdefs.optional.perforce.P4Have +p4sync=org.apache.tools.ant.taskdefs.optional.perforce.P4Sync +p4edit=org.apache.tools.ant.taskdefs.optional.perforce.P4Edit +p4submit=org.apache.tools.ant.taskdefs.optional.perforce.P4Submit +p4counter=org.apache.tools.ant.taskdefs.optional.perforce.P4Counter +javah=org.apache.tools.ant.taskdefs.optional.Javah +ccupdate=org.apache.tools.ant.taskdefs.optional.clearcase.CCUpdate +cccheckout=org.apache.tools.ant.taskdefs.optional.clearcase.CCCheckout +cccheckin=org.apache.tools.ant.taskdefs.optional.clearcase.CCCheckin +ccuncheckout=org.apache.tools.ant.taskdefs.optional.clearcase.CCUnCheckout +sound=org.apache.tools.ant.taskdefs.optional.sound.SoundTask +junitreport=org.apache.tools.ant.taskdefs.optional.junit.XMLResultAggregator +vsslabel=org.apache.tools.ant.taskdefs.optional.vss.MSVSSLABEL +vsshistory=org.apache.tools.ant.taskdefs.optional.vss.MSVSSHISTORY +blgenclient=org.apache.tools.ant.taskdefs.optional.ejb.BorlandGenerateClient +rpm=org.apache.tools.ant.taskdefs.optional.Rpm +xmlvalidate=org.apache.tools.ant.taskdefs.optional.XMLValidateTask +vsscheckin=org.apache.tools.ant.taskdefs.optional.vss.MSVSSCHECKIN +vsscheckout=org.apache.tools.ant.taskdefs.optional.vss.MSVSSCHECKOUT +iplanet-ejbc=org.apache.tools.ant.taskdefs.optional.ejb.IPlanetEjbcTask +jdepend=org.apache.tools.ant.taskdefs.optional.jdepend.JDependTask +mimemail=org.apache.tools.ant.taskdefs.optional.net.MimeMail +ccmcheckin=org.apache.tools.ant.taskdefs.optional.ccm.CCMCheckin +ccmcheckout=org.apache.tools.ant.taskdefs.optional.ccm.CCMCheckout +ccmcheckintask=org.apache.tools.ant.taskdefs.optional.ccm.CCMCheckinDefault +ccmreconfigure=org.apache.tools.ant.taskdefs.optional.ccm.CCMReconfigure +ccmcreatetask=org.apache.tools.ant.taskdefs.optional.ccm.CCMCreateTask +jpcoverage=org.apache.tools.ant.taskdefs.optional.sitraka.Coverage +jpcovmerge=org.apache.tools.ant.taskdefs.optional.sitraka.CovMerge +jpcovreport=org.apache.tools.ant.taskdefs.optional.sitraka.CovReport +p4add=org.apache.tools.ant.taskdefs.optional.perforce.P4Add +jspc=org.apache.tools.ant.taskdefs.optional.jsp.JspC +replaceregexp=org.apache.tools.ant.taskdefs.optional.ReplaceRegExp +translate=org.apache.tools.ant.taskdefs.optional.i18n.Translate + +# deprecated ant tasks (kept for back compatibility) +javadoc2=org.apache.tools.ant.taskdefs.Javadoc +#compileTask=org.apache.tools.ant.taskdefs.CompileTask +copydir=org.apache.tools.ant.taskdefs.Copydir +copyfile=org.apache.tools.ant.taskdefs.Copyfile +deltree=org.apache.tools.ant.taskdefs.Deltree +rename=org.apache.tools.ant.taskdefs.Rename diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ANTLR.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ANTLR.java new file mode 100644 index 000000000..7269bf2b2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ANTLR.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.URL; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.ExitException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteJava; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; + +/** + * ANTLR task. + * + * @author Erik Meade + * @author <classpath> allows classpath to be set because a + * directory might be given for Antlr debug... + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + return commandline.createClasspath( project ).createPath(); + } + + /** + * Create a new JVM argument. Ignored if no JVM is forked. + * + * @return create a new JVM argument so that any argument can be passed to + * the JVM. + * @see #setFork(boolean) + */ + public Commandline.Argument createJvmarg() + { + return commandline.createVmArgument(); + } + + public void execute() + throws BuildException + { + validateAttributes(); + + //TODO: use ANTLR to parse the grammer file to do this. + if( target.lastModified() > getGeneratedFile().lastModified() ) + { + commandline.createArgument().setValue( "-o" ); + commandline.createArgument().setValue( outputDirectory.toString() ); + commandline.createArgument().setValue( target.toString() ); + + if( fork ) + { + log( "Forking " + commandline.toString(), Project.MSG_VERBOSE ); + int err = run( commandline.getCommandline() ); + if( err == 1 ) + { + throw new BuildException( "ANTLR returned: " + err, location ); + } + } + else + { + ExecuteJava exe = new ExecuteJava(); + exe.setJavaCommand( commandline.getJavaCommand() ); + exe.setClasspath( commandline.getClasspath() ); + try + { + exe.execute( project ); + } + catch( ExitException e ) + { + if( e.getStatus() != 0 ) + { + throw new BuildException( "ANTLR returned: " + e.getStatus(), location ); + } + } + } + } + } + + /** + * Adds the jars or directories containing Antlr this should make the forked + * JVM work without having to specify it directly. + * + * @exception BuildException Description of Exception + */ + public void init() + throws BuildException + { + addClasspathEntry( "/antlr/Tool.class" ); + } + + /** + * Search for the given resource and add the directory or archive that + * contains it to the classpath.

              + * + * Doesn't work for archives in JDK 1.1 as the URL returned by getResource + * doesn't contain the name of the archive.

              + * + * @param resource The feature to be added to the ClasspathEntry attribute + */ + protected void addClasspathEntry( String resource ) + { + URL url = getClass().getResource( resource ); + if( url != null ) + { + String u = url.toString(); + if( u.startsWith( "jar:file:" ) ) + { + int pling = u.indexOf( "!" ); + String jarName = u.substring( 9, pling ); + log( "Implicitly adding " + jarName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( jarName ) ).getAbsolutePath() ) ); + } + else if( u.startsWith( "file:" ) ) + { + int tail = u.indexOf( resource ); + String dirName = u.substring( 5, tail ); + log( "Implicitly adding " + dirName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( dirName ) ).getAbsolutePath() ) ); + } + else + { + log( "Don\'t know how to handle resource URL " + u, + Project.MSG_DEBUG ); + } + } + else + { + log( "Couldn\'t find " + resource, Project.MSG_DEBUG ); + } + } + + private File getGeneratedFile() + throws BuildException + { + String generatedFileName = null; + try + { + BufferedReader in = new BufferedReader( new FileReader( target ) ); + String line; + while( ( line = in.readLine() ) != null ) + { + int extendsIndex = line.indexOf( " extends " ); + if( line.startsWith( "class " ) && extendsIndex > -1 ) + { + generatedFileName = line.substring( 6, extendsIndex ).trim(); + break; + } + } + in.close(); + } + catch( Exception e ) + { + throw new BuildException( "Unable to determine generated class", e ); + } + if( generatedFileName == null ) + { + throw new BuildException( "Unable to determine generated class" ); + } + return new File( outputDirectory, generatedFileName + ".java" ); + } + + /** + * execute in a forked VM + * + * @param command Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int run( String[] command ) + throws BuildException + { + Execute exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ), null ); + exe.setAntRun( project ); + if( workingdir != null ) + { + exe.setWorkingDirectory( workingdir ); + } + exe.setCommandline( command ); + try + { + return exe.execute(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + + private void validateAttributes() + throws BuildException + { + if( target == null || !target.isFile() ) + { + throw new BuildException( "Invalid target: " + target ); + } + + // if no output directory is specified, used the target's directory + if( outputDirectory == null ) + { + String fileName = target.toString(); + setOutputdirectory( new File( target.getParent() ) ); + } + if( !outputDirectory.isDirectory() ) + { + throw new BuildException( "Invalid output directory: " + outputDirectory ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java new file mode 100644 index 000000000..53d6d80ed --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/AdaptxLiaison.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.XSLTLiaison; +import org.exolab.adaptx.xslt.XSLTProcessor; +import org.exolab.adaptx.xslt.XSLTReader; +import org.exolab.adaptx.xslt.XSLTStylesheet; + +/** + * @author
              Arnaud Blandin + * @version $Revision$ $Date$ + */ +public class AdaptxLiaison implements XSLTLiaison +{ + + protected XSLTProcessor processor; + protected XSLTStylesheet xslSheet; + + public AdaptxLiaison() + { + processor = new XSLTProcessor(); + } + + public void setOutputtype( String type ) + throws Exception + { + if( !type.equals( "xml" ) ) + throw new BuildException( "Unsupported output type: " + type ); + } + + public void setStylesheet( File fileName ) + throws Exception + { + XSLTReader xslReader = new XSLTReader(); + xslSheet = xslReader.read( fileName.getAbsolutePath() ); + } + + public void addParam( String name, String expression ) + { + processor.setProperty( name, expression ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileOutputStream fos = new FileOutputStream( outfile ); + OutputStreamWriter out = new OutputStreamWriter( fos, "UTF8" ); + processor.process( infile.getAbsolutePath(), xslSheet, out ); + } + +}//-- AdaptxLiaison diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Cab.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Cab.java new file mode 100644 index 000000000..65510cf56 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Cab.java @@ -0,0 +1,349 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.ExecTask; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.myrmidon.framework.Os; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.FileUtils; + + +/** + * Create a CAB archive. + * + * @author Roger Vaughn + * rvaughn@seaconinc.com + */ + +public class Cab extends MatchingTask +{ + private Vector filesets = new Vector(); + private boolean doCompress = true; + private boolean doVerbose = false; + + protected String archiveType = "cab"; + + private FileUtils fileUtils = FileUtils.newFileUtils(); + private File baseDir; + + private File cabFile; + private String cmdOptions; + + /** + * This is the base directory to look in for things to cab. + * + * @param baseDir The new Basedir value + */ + public void setBasedir( File baseDir ) + { + this.baseDir = baseDir; + } + + /** + * This is the name/location of where to create the .cab file. + * + * @param cabFile The new Cabfile value + */ + public void setCabfile( File cabFile ) + { + this.cabFile = cabFile; + } + + /** + * Sets whether we want to compress the files or only store them. + * + * @param compress The new Compress value + */ + public void setCompress( boolean compress ) + { + doCompress = compress; + } + + /** + * Sets additional cabarc options that aren't supported directly. + * + * @param options The new Options value + */ + public void setOptions( String options ) + { + cmdOptions = options; + } + + /** + * Sets whether we want to see or suppress cabarc output. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + doVerbose = verbose; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + public void execute() + throws BuildException + { + + checkConfiguration(); + + Vector files = getFileList(); + + // quick exit if the target is up to date + if( isUpToDate( files ) ) + return; + + log( "Building " + archiveType + ": " + cabFile.getAbsolutePath() ); + + if( !Os.isFamily( "windows" ) ) + { + log( "Using listcab/libcabinet", Project.MSG_VERBOSE ); + + StringBuffer sb = new StringBuffer(); + + Enumeration fileEnum = files.elements(); + + while( fileEnum.hasMoreElements() ) + { + sb.append( fileEnum.nextElement() ).append( "\n" ); + } + sb.append( "\n" ).append( cabFile.getAbsolutePath() ).append( "\n" ); + + try + { + Process p = Runtime.getRuntime().exec( "listcab" ); + OutputStream out = p.getOutputStream(); + out.write( sb.toString().getBytes() ); + out.flush(); + out.close(); + } + catch( IOException ex ) + { + String msg = "Problem creating " + cabFile + " " + ex.getMessage(); + throw new BuildException( msg ); + } + } + else + { + try + { + File listFile = createListFile( files ); + ExecTask exec = createExec(); + File outFile = null; + + // die if cabarc fails + exec.setFailonerror( true ); + exec.setDir( baseDir ); + + if( !doVerbose ) + { + outFile = fileUtils.createTempFile( "ant", "", null ); + exec.setOutput( outFile ); + } + + exec.setCommand( createCommand( listFile ) ); + exec.execute(); + + if( outFile != null ) + { + outFile.delete(); + } + + listFile.delete(); + } + catch( IOException ioe ) + { + String msg = "Problem creating " + cabFile + " " + ioe.getMessage(); + throw new BuildException( msg ); + } + } + } + + /** + * Get the complete list of files to be included in the cab. Filenames are + * gathered from filesets if any have been added, otherwise from the + * traditional include parameters. + * + * @return The FileList value + * @exception BuildException Description of Exception + */ + protected Vector getFileList() + throws BuildException + { + Vector files = new Vector(); + + if( filesets.size() == 0 ) + { + // get files from old methods - includes and nested include + appendFiles( files, super.getDirectoryScanner( baseDir ) ); + } + else + { + // get files from filesets + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + if( fs != null ) + { + appendFiles( files, fs.getDirectoryScanner( project ) ); + } + } + } + + return files; + } + + /** + * Check to see if the target is up to date with respect to input files. + * + * @param files Description of Parameter + * @return true if the cab file is newer than its dependents. + */ + protected boolean isUpToDate( Vector files ) + { + boolean upToDate = true; + for( int i = 0; i < files.size() && upToDate; i++ ) + { + String file = files.elementAt( i ).toString(); + if( new File( baseDir, file ).lastModified() > + cabFile.lastModified() ) + upToDate = false; + } + return upToDate; + } + + /** + * Append all files found by a directory scanner to a vector. + * + * @param files Description of Parameter + * @param ds Description of Parameter + */ + protected void appendFiles( Vector files, DirectoryScanner ds ) + { + String[] dsfiles = ds.getIncludedFiles(); + + for( int i = 0; i < dsfiles.length; i++ ) + { + files.addElement( dsfiles[i] ); + } + } + + /* + * I'm not fond of this pattern: "sub-method expected to throw + * task-cancelling exceptions". It feels too much like programming + * for side-effects to me... + */ + protected void checkConfiguration() + throws BuildException + { + if( baseDir == null ) + { + throw new BuildException( "basedir attribute must be set!" ); + } + if( !baseDir.exists() ) + { + throw new BuildException( "basedir does not exist!" ); + } + if( cabFile == null ) + { + throw new BuildException( "cabfile attribute must be set!" ); + } + } + + /** + * Create the cabarc command line to use. + * + * @param listFile Description of Parameter + * @return Description of the Returned Value + */ + protected Commandline createCommand( File listFile ) + { + Commandline command = new Commandline(); + command.setExecutable( "cabarc" ); + command.createArgument().setValue( "-r" ); + command.createArgument().setValue( "-p" ); + + if( !doCompress ) + { + command.createArgument().setValue( "-m" ); + command.createArgument().setValue( "none" ); + } + + if( cmdOptions != null ) + { + command.createArgument().setLine( cmdOptions ); + } + + command.createArgument().setValue( "n" ); + command.createArgument().setFile( cabFile ); + command.createArgument().setValue( "@" + listFile.getAbsolutePath() ); + + return command; + } + + /** + * Create a new exec delegate. The delegate task is populated so that it + * appears in the logs to be the same task as this one. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + protected ExecTask createExec() + throws BuildException + { + ExecTask exec = ( ExecTask )project.createTask( "exec" ); + exec.setOwningTarget( this.getOwningTarget() ); + exec.setTaskName( this.getTaskName() ); + exec.setDescription( this.getDescription() ); + + return exec; + } + + /** + * Creates a list file. This temporary file contains a list of all files to + * be included in the cab, one file per line. + * + * @param files Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + protected File createListFile( Vector files ) + throws IOException + { + File listFile = fileUtils.createTempFile( "ant", "", null ); + + PrintWriter writer = new PrintWriter( new FileOutputStream( listFile ) ); + + for( int i = 0; i < files.size(); i++ ) + { + writer.println( files.elementAt( i ).toString() ); + } + writer.close(); + + return listFile; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/IContract.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/IContract.java new file mode 100644 index 000000000..cc52a4708 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/IContract.java @@ -0,0 +1,1105 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.Date; +import java.util.Properties; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.Javac; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.Mkdir; +import org.apache.tools.ant.taskdefs.compilers.DefaultCompilerAdapter; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Instruments Java classes with + * iContract DBC preprocessor.
              + * The task can generate a properties file for iControl , a graphical + * user interface that lets you turn on/off assertions. iControl generates a + * control file that you can refer to from this task using the controlfile + * attribute.

              + * + * Thanks to Rainer Schmitz for enhancements and comments. + * + * @author Aslak Hellesøy

              + * + * + * + * + * + * + * + * Attribute + * + * + * + * Description + * + * + * + * Required + * + * + * + * + * + * + * + * srcdir + * + * + * + * Location of the java files. + * + * + * + * Yes + * + * + * + * + * + * + * + * instrumentdir + * + * + * + * Indicates where the instrumented source files should go. + * + * + * + * Yes + * + * + * + * + * + * + * + * repositorydir + * + * + * + * Indicates where the repository source files should go. + * + * + * + * Yes + * + * + * + * + * + * + * + * builddir + * + * + * + * Indicates where the compiled instrumented classes should go. + * Defaults to the value of instrumentdir.

              NOTE: Don't + * use the same directory for compiled instrumented classes and + * uninstrumented classes. It will break the dependency checking. + * (Classes will not be reinstrumented if you change them). + * + * + * + * No + * + * + * + * + * + * + * + * repositorybuilddir + * + * + * + * Indicates where the compiled repository classes should go. + * Defaults to the value of repositorydir. + * + * + * + * No + * + * + * + * + * + * + * + * pre + * + * + * + * Indicates whether or not to instrument for preconditions. Defaults + * to true unless controlfile is specified, in which + * case it defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * post + * + * + * + * Indicates whether or not to instrument for postconditions. + * Defaults to true unless controlfile is specified, in + * which case it defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * invariant + * + * + * + * Indicates whether or not to instrument for invariants. Defaults to + * true unless controlfile is specified, in which case + * it defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * failthrowable + * + * + * + * The full name of the Throwable (Exception) that should be thrown + * when an assertion is violated. Defaults to java.lang.Error + * + * + * + * + * No + * + * + * + * + * + * + * + * verbosity + * + * + * + * Indicates the verbosity level of iContract. Any combination of + * error*,warning*,note*,info*,progress*,debug* (comma + * separated) can be used. Defaults to error* + * + * + * + * No + * + * + * + * + * + * + * + * quiet + * + * + * + * Indicates if iContract should be quiet. Turn it off if many your + * classes extend uninstrumented classes and you don't want warnings + * about this. Defaults to false + * + * + * + * No + * + * + * + * + * + * + * + * updateicontrol + * + * + * + * If set to true, it indicates that the properties file for iControl + * in the current directory should be updated (or created if it + * doesn't exist). Defaults to false. + * + * + * + * No + * + * + * + * + * + * + * + * controlfile + * + * + * + * The name of the control file to pass to iContract. Consider using + * iControl to generate the file. Default is not to pass a file. + * + * + * + * + * Only if updateicontrol=true + * + * + * + * + * + * + * + * classdir + * + * + * + * Indicates where compiled (unistrumented) classes are located. This + * is required in order to properly update the icontrol.properties + * file, not for instrumentation. + * + * + * + * Only if updateicontrol=true + * + * + * + * + * + * + * + * targets + * + * + * + * Name of the file that will be generated by this task, which lists + * all the classes that iContract will instrument. If specified, the + * file will not be deleted after execution. If not specified, a file + * will still be created, but it will be deleted after execution. + * + * + * + * + * No + * + * + * + * + * + *

              + * + * Note: iContract will use the java compiler indicated by the + * project's build.compiler property. See documentation of the + * Javac task for more information.

              + * + * Nested includes and excludes are also supported.

              + * + * Example:

              + * <icontract
              + *    srcdir="${build.src}"
              + *    instrumentdir="${build.instrument}"
              + *    repositorydir="${build.repository}"
              + *    builddir="${build.instrclasses}"
              + *    updateicontrol="true"
              + *    classdir="${build.classes}"
              + *    controlfile="control"
              + *    targets="targets"
              + *    verbosity="error*,warning*"
              + *    quiet="true"
              + * >
              + *    <classpath refid="compile-classpath"/>
              + * </icontract>
              + * 
              + */ +public class IContract extends MatchingTask +{ + + private final static String ICONTROL_PROPERTIES_HEADER = + " You might want to set classRoot to point to your normal compilation class root directory."; + + private final static String ICONTROL_PROPERTIES_MESSAGE = + "You should probably modify icontrol.properties' classRoot to where comiled (uninstrumented) classes go."; + + /** + * \ on windows, / on linux/unix + */ + private final static String ps = System.getProperty( "path.separator" ); + + /** + * compiler to use for instrumenation + */ + private String icCompiler = "javac"; + + /** + * temporary file with file names of all java files to be instrumented + */ + private File targets = null; + + /** + * will be set to true if any of the sourca files are newer than the + * instrumented files + */ + private boolean dirty = false; + + /** + * set to true if the iContract jar is missing + */ + private boolean iContractMissing = false; + + /** + * source file root + */ + private File srcDir = null; + + /** + * instrumentation src root + */ + private File instrumentDir = null; + + /** + * instrumentation build root + */ + private File buildDir = null; + + /** + * repository src root + */ + private File repositoryDir = null; + + /** + * repository build root + */ + private File repBuildDir = null; + + /** + * classpath + */ + private Path classpath = null; + + /** + * The class of the Throwable to be thrown on failed assertions + */ + private String failThrowable = "java.lang.Error"; + + /** + * The -v option + */ + private String verbosity = "error*"; + + /** + * The -q option + */ + private boolean quiet = false; + + /** + * Indicates whether or not to use internal compilation + */ + private boolean internalcompilation = false; + + /** + * The -m option + */ + private File controlFile = null; + + /** + * Indicates whether or not to instrument for preconditions + */ + private boolean pre = true; + private boolean preModified = false; + + /** + * Indicates whether or not to instrument for postconditions + */ + private boolean post = true; + private boolean postModified = false; + + /** + * Indicates whether or not to instrument for invariants + */ + private boolean invariant = true; + private boolean invariantModified = false; + + /** + * Indicates whether or not to instrument all files regardless of timestamp + */ + // can't be explicitly set, is set if control file exists and is newer than any source file + private boolean instrumentall = false; + + /** + * Indicates the name of a properties file (intentionally for iControl) + * where the classpath property should be updated. + */ + private boolean updateIcontrol = false; + + /** + * Regular compilation class root + */ + private File classDir = null; + + /** + * Sets the build directory for instrumented classes + * + * @param buildDir the build directory + */ + public void setBuilddir( File buildDir ) + { + this.buildDir = buildDir; + } + + /** + * Sets the class directory (uninstrumented classes) + * + * @param classDir The new Classdir value + */ + public void setClassdir( File classDir ) + { + this.classDir = classDir; + } + + /** + * Sets the classpath to be used for invocation of iContract. + * + * @param path The new Classpath value + * @path the classpath + */ + public void setClasspath( Path path ) + { + createClasspath().append( path ); + } + + /** + * Adds a reference to a classpath defined elsewhere. + * + * @param reference referenced classpath + */ + public void setClasspathRef( Reference reference ) + { + createClasspath().setRefid( reference ); + } + + /** + * Sets the control file to pass to iContract. + * + * @param controlFile the control file + */ + public void setControlfile( File controlFile ) + { + if( !controlFile.exists() ) + { + log( "WARNING: Control file " + controlFile.getAbsolutePath() + " doesn't exist. iContract will be run without control file." ); + } + this.controlFile = controlFile; + } + + /** + * Sets the Throwable (Exception) to be thrown on assertion violation + * + * @param clazz the fully qualified Throwable class name + */ + public void setFailthrowable( String clazz ) + { + this.failThrowable = clazz; + } + + /** + * Sets the instrumentation directory + * + * @param instrumentDir the source directory + */ + public void setInstrumentdir( File instrumentDir ) + { + this.instrumentDir = instrumentDir; + if( this.buildDir == null ) + { + setBuilddir( instrumentDir ); + } + } + + /** + * Turns on/off invariant instrumentation + * + * @param invariant true turns it on + */ + public void setInvariant( boolean invariant ) + { + this.invariant = invariant; + invariantModified = true; + } + + /** + * Turns on/off postcondition instrumentation + * + * @param post true turns it on + */ + public void setPost( boolean post ) + { + this.post = post; + postModified = true; + } + + /** + * Turns on/off precondition instrumentation + * + * @param pre true turns it on + */ + public void setPre( boolean pre ) + { + this.pre = pre; + preModified = true; + } + + /** + * Tells iContract to be quiet. + * + * @param quiet true if iContract should be quiet. + */ + public void setQuiet( boolean quiet ) + { + this.quiet = quiet; + } + + /** + * Sets the build directory for instrumented classes + * + * @param repBuildDir The new Repbuilddir value + */ + public void setRepbuilddir( File repBuildDir ) + { + this.repBuildDir = repBuildDir; + } + + /** + * Sets the build directory for repository classes + * + * @param repositoryDir the source directory + */ + public void setRepositorydir( File repositoryDir ) + { + this.repositoryDir = repositoryDir; + if( this.repBuildDir == null ) + { + setRepbuilddir( repositoryDir ); + } + } + + /** + * Sets the source directory + * + * @param srcDir the source directory + */ + public void setSrcdir( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * Sets the name of the file where targets will be written. That is the file + * that tells iContract what files to process. + * + * @param targets the targets file name + */ + public void setTargets( File targets ) + { + this.targets = targets; + } + + /** + * Decides whether or not to update iControl properties file + * + * @param updateIcontrol true if iControl properties file should be updated + */ + public void setUpdateicontrol( boolean updateIcontrol ) + { + this.updateIcontrol = updateIcontrol; + } + + /** + * Sets the verbosity level of iContract. Any combination of + * error*,warning*,note*,info*,progress*,debug* (comma separated) can be + * used. Defaults to error*,warning* + * + * @param verbosity verbosity level + */ + public void setVerbosity( String verbosity ) + { + this.verbosity = verbosity; + } + + /** + * Creates a nested classpath element + * + * @return the nested classpath element + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( getProject() ); + } + return classpath; + } + + /** + * Executes the task + * + * @exception BuildException if the instrumentation fails + */ + public void execute() + throws BuildException + { + preconditions(); + scan(); + if( dirty ) + { + + // turn off assertions if we're using controlfile, unless they are not explicitly set. + boolean useControlFile = ( controlFile != null ) && controlFile.exists(); + if( useControlFile && !preModified ) + { + pre = false; + } + if( useControlFile && !postModified ) + { + post = false; + } + if( useControlFile && !invariantModified ) + { + invariant = false; + } + // issue warning if pre,post or invariant is used together with controlfile + if( ( pre || post || invariant ) && controlFile != null ) + { + log( "WARNING: specifying pre,post or invariant will override control file settings" ); + } + + + // We want to be notified if iContract jar is missing. This makes life easier for the user + // who didn't understand that iContract is a separate library (duh!) + getProject().addBuildListener( new IContractPresenceDetector() ); + + // Prepare the directories for iContract. iContract will make them if they + // don't exist, but for some reason I don't know, it will complain about the REP files + // afterwards + Mkdir mkdir = ( Mkdir )project.createTask( "mkdir" ); + mkdir.setDir( instrumentDir ); + mkdir.execute(); + mkdir.setDir( buildDir ); + mkdir.execute(); + mkdir.setDir( repositoryDir ); + mkdir.execute(); + + // Set the classpath that is needed for regular Javac compilation + Path baseClasspath = createClasspath(); + + // Might need to add the core classes if we're not using Sun's Javac (like Jikes) + String compiler = project.getProperty( "build.compiler" ); + ClasspathHelper classpathHelper = new ClasspathHelper( compiler ); + classpathHelper.modify( baseClasspath ); + + // Create the classpath required to compile the sourcefiles BEFORE instrumentation + Path beforeInstrumentationClasspath = ( ( Path )baseClasspath.clone() ); + beforeInstrumentationClasspath.append( new Path( getProject(), srcDir.getAbsolutePath() ) ); + + // Create the classpath required to compile the sourcefiles AFTER instrumentation + Path afterInstrumentationClasspath = ( ( Path )baseClasspath.clone() ); + afterInstrumentationClasspath.append( new Path( getProject(), instrumentDir.getAbsolutePath() ) ); + afterInstrumentationClasspath.append( new Path( getProject(), repositoryDir.getAbsolutePath() ) ); + afterInstrumentationClasspath.append( new Path( getProject(), srcDir.getAbsolutePath() ) ); + afterInstrumentationClasspath.append( new Path( getProject(), buildDir.getAbsolutePath() ) ); + + // Create the classpath required to automatically compile the repository files + Path repositoryClasspath = ( ( Path )baseClasspath.clone() ); + repositoryClasspath.append( new Path( getProject(), instrumentDir.getAbsolutePath() ) ); + repositoryClasspath.append( new Path( getProject(), srcDir.getAbsolutePath() ) ); + repositoryClasspath.append( new Path( getProject(), repositoryDir.getAbsolutePath() ) ); + repositoryClasspath.append( new Path( getProject(), buildDir.getAbsolutePath() ) ); + + // Create the classpath required for iContract itself + Path iContractClasspath = ( ( Path )baseClasspath.clone() ); + iContractClasspath.append( new Path( getProject(), System.getProperty( "java.home" ) + File.separator + ".." + File.separator + "lib" + File.separator + "tools.jar" ) ); + iContractClasspath.append( new Path( getProject(), srcDir.getAbsolutePath() ) ); + iContractClasspath.append( new Path( getProject(), repositoryDir.getAbsolutePath() ) ); + iContractClasspath.append( new Path( getProject(), instrumentDir.getAbsolutePath() ) ); + iContractClasspath.append( new Path( getProject(), buildDir.getAbsolutePath() ) ); + + // Create a forked java process + Java iContract = ( Java )project.createTask( "java" ); + iContract.setTaskName( getTaskName() ); + iContract.setFork( true ); + iContract.setClassname( "com.reliablesystems.iContract.Tool" ); + iContract.setClasspath( iContractClasspath ); + + // Build the arguments to iContract + StringBuffer args = new StringBuffer(); + args.append( directiveString() ); + args.append( "-v" ).append( verbosity ).append( " " ); + args.append( "-b" ).append( "\"" ).append( icCompiler ).append( " -classpath " ).append( beforeInstrumentationClasspath ).append( "\" " ); + args.append( "-c" ).append( "\"" ).append( icCompiler ).append( " -classpath " ).append( afterInstrumentationClasspath ).append( " -d " ).append( buildDir ).append( "\" " ); + args.append( "-n" ).append( "\"" ).append( icCompiler ).append( " -classpath " ).append( repositoryClasspath ).append( "\" " ); + args.append( "-d" ).append( failThrowable ).append( " " ); + args.append( "-o" ).append( instrumentDir ).append( File.separator ).append( "@p" ).append( File.separator ).append( "@f.@e " ); + args.append( "-k" ).append( repositoryDir ).append( File.separator ).append( "@p " ); + args.append( quiet ? "-q " : "" ); + args.append( instrumentall ? "-a " : "" );// reinstrument everything if controlFile exists and is newer than any class + args.append( "@" ).append( targets.getAbsolutePath() ); + iContract.createArg().setLine( args.toString() ); + +//System.out.println( "JAVA -classpath " + iContractClasspath + " com.reliablesystems.iContract.Tool " + args.toString() ); + + // update iControlProperties if it's set. + if( updateIcontrol ) + { + Properties iControlProps = new Properties(); + try + {// to read existing propertiesfile + iControlProps.load( new FileInputStream( "icontrol.properties" ) ); + } + catch( IOException e ) + { + log( "File icontrol.properties not found. That's ok. Writing a default one." ); + } + iControlProps.setProperty( "sourceRoot", srcDir.getAbsolutePath() ); + iControlProps.setProperty( "classRoot", classDir.getAbsolutePath() ); + iControlProps.setProperty( "classpath", afterInstrumentationClasspath.toString() ); + iControlProps.setProperty( "controlFile", controlFile.getAbsolutePath() ); + iControlProps.setProperty( "targetsFile", targets.getAbsolutePath() ); + + try + {// to read existing propertiesfile + iControlProps.store( new FileOutputStream( "icontrol.properties" ), ICONTROL_PROPERTIES_HEADER ); + log( "Updated icontrol.properties" ); + } + catch( IOException e ) + { + log( "Couldn't write icontrol.properties." ); + } + } + + // do it! + int result = iContract.executeJava(); + if( result != 0 ) + { + if( iContractMissing ) + { + log( "iContract can't be found on your classpath. Your classpath is:" ); + log( classpath.toString() ); + log( "If you don't have the iContract jar, go get it at http://www.reliable-systems.com/tools/" ); + } + throw new BuildException( "iContract instrumentation failed. Code=" + result ); + } + + } + else + {// not dirty + //log( "Nothing to do. Everything up to date." ); + } + } + + + /** + * Creates the -m option based on the values of controlFile, pre, post and + * invariant. + * + * @return Description of the Returned Value + */ + private final String directiveString() + { + StringBuffer sb = new StringBuffer(); + boolean comma = false; + + boolean useControlFile = ( controlFile != null ) && controlFile.exists(); + if( useControlFile || pre || post || invariant ) + { + sb.append( "-m" ); + } + if( useControlFile ) + { + sb.append( "@" ).append( controlFile ); + comma = true; + } + if( pre ) + { + if( comma ) + { + sb.append( "," ); + } + sb.append( "pre" ); + comma = true; + } + if( post ) + { + if( comma ) + { + sb.append( "," ); + } + sb.append( "post" ); + comma = true; + } + if( invariant ) + { + if( comma ) + { + sb.append( "," ); + } + sb.append( "inv" ); + } + sb.append( " " ); + return sb.toString(); + } + + /** + * Checks that the required attributes are set. + * + * @exception BuildException Description of Exception + */ + private void preconditions() + throws BuildException + { + if( srcDir == null ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir \"" + srcDir.getPath() + "\" does not exist!", location ); + } + if( instrumentDir == null ) + { + throw new BuildException( "instrumentdir attribute must be set!", location ); + } + if( repositoryDir == null ) + { + throw new BuildException( "repositorydir attribute must be set!", location ); + } + if( updateIcontrol == true && classDir == null ) + { + throw new BuildException( "classdir attribute must be specified when updateicontrol=true!", location ); + } + if( updateIcontrol == true && controlFile == null ) + { + throw new BuildException( "controlfile attribute must be specified when updateicontrol=true!", location ); + } + } + + /** + * Verifies whether any of the source files have changed. Done by comparing + * date of source/class files. The whole lot is "dirty" if at least one + * source file or the control file is newer than the instrumented files. If + * not dirty, iContract will not be executed.
              + * Also creates a temporary file with a list of the source files, that will + * be deleted upon exit. + * + * @exception BuildException Description of Exception + */ + private void scan() + throws BuildException + { + long now = ( new Date() ).getTime(); + + DirectoryScanner ds = null; + + ds = getDirectoryScanner( srcDir ); + String[] files = ds.getIncludedFiles(); + + FileOutputStream targetOutputStream = null; + PrintStream targetPrinter = null; + boolean writeTargets = false; + try + { + if( targets == null ) + { + targets = new File( "targets" ); + log( "Warning: targets file not specified. generating file: " + targets.getName() ); + writeTargets = true; + } + else if( !targets.exists() ) + { + log( "Specified targets file doesn't exist. generating file: " + targets.getName() ); + writeTargets = true; + } + if( writeTargets ) + { + log( "You should consider using iControl to create a target file." ); + targetOutputStream = new FileOutputStream( targets ); + targetPrinter = new PrintStream( targetOutputStream ); + } + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".java" ) ) + { + // print the target, while we're at here. (Only if generatetarget=true). + if( targetPrinter != null ) + { + targetPrinter.println( srcFile.getAbsolutePath() ); + } + File classFile = new File( buildDir, files[i].substring( 0, files[i].indexOf( ".java" ) ) + ".class" ); + + if( srcFile.lastModified() > now ) + { + log( "Warning: file modified in the future: " + + files[i], Project.MSG_WARN ); + } + + if( !classFile.exists() || srcFile.lastModified() > classFile.lastModified() ) + { + //log( "Found a file newer than the instrumentDir class file: " + srcFile.getPath() + " newer than " + classFile.getPath() + ". Running iContract again..." ); + dirty = true; + } + } + } + if( targetPrinter != null ) + { + targetPrinter.flush(); + targetPrinter.close(); + } + } + catch( IOException e ) + { + throw new BuildException( "Could not create target file:" + e.getMessage() ); + } + + // also, check controlFile timestamp + long controlFileTime = -1; + try + { + if( controlFile != null ) + { + if( controlFile.exists() && buildDir.exists() ) + { + controlFileTime = controlFile.lastModified(); + ds = getDirectoryScanner( buildDir ); + files = ds.getIncludedFiles(); + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".class" ) ) + { + if( controlFileTime > srcFile.lastModified() ) + { + if( !dirty ) + { + log( "Control file " + controlFile.getAbsolutePath() + " has been updated. Instrumenting all files..." ); + } + dirty = true; + instrumentall = true; + } + } + } + } + } + } + catch( Throwable t ) + { + throw new BuildException( "Got an interesting exception:" + t.getMessage() ); + } + } + + /** + * This class is a helper to set correct classpath for other compilers, like + * Jikes. It reuses the logic from DefaultCompilerAdapter, which is + * protected, so we have to subclass it. + * + * @author RT + */ + private class ClasspathHelper extends DefaultCompilerAdapter + { + private final String compiler; + + public ClasspathHelper( String compiler ) + { + super(); + this.compiler = compiler; + } + + // dummy implementation. Never called + public void setJavac( Javac javac ) { } + + public boolean execute() + { + return true; + } + + // make it public + public void modify( Path path ) + { + // depending on what compiler to use, set the includeJavaRuntime flag + if( "jikes".equals( compiler ) ) + { + icCompiler = compiler; + includeJavaRuntime = true; + path.append( getCompileClasspath() ); + } + } + } + + /** + * BuildListener that sets the iContractMissing flag to true if a message + * about missing iContract is missing. Used to indicate a more verbose error + * to the user, with advice about how to solve the problem + * + * @author RT + */ + private class IContractPresenceDetector implements BuildListener + { + public void buildFinished( BuildEvent event ) { } + + public void buildStarted( BuildEvent event ) { } + + public void messageLogged( BuildEvent event ) + { + if( "java.lang.NoClassDefFoundError: com/reliablesystems/iContract/Tool".equals( event.getMessage() ) ) + { + iContractMissing = true; + } + } + + public void targetFinished( BuildEvent event ) { } + + public void targetStarted( BuildEvent event ) { } + + public void taskFinished( BuildEvent event ) { } + + public void taskStarted( BuildEvent event ) { } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Javah.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Javah.java new file mode 100644 index 000000000..76d48b24c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Javah.java @@ -0,0 +1,468 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Task to generate JNI header files using javah. This task can take the + * following arguments: + *
                + *
              • classname - the fully-qualified name of a class
              • + *
              • outputFile - Concatenates the resulting header or source files for all + * the classes listed into this file
              • + *
              • destdir - Sets the directory where javah saves the header files or the + * stub files
              • + *
              • classpath
              • + *
              • bootclasspath
              • + *
              • force - Specifies that output files should always be written (JDK1.2 + * only)
              • + *
              • old - Specifies that old JDK1.0-style header files should be generated + * (otherwise output file contain JNI-style native method function prototypes) + * (JDK1.2 only)
              • + *
              • stubs - generate C declarations from the Java object file (used with + * old)
              • + *
              • verbose - causes javah to print a message to stdout concerning the + * status of the generated files
              • + *
              • extdirs - Override location of installed extensions
              • + *
              + * Of these arguments, either outputFile or destdir is required, + * but not both. More than one classname may be specified, using a + * comma-separated list or by using <class name="xxx"> + * elements within the task.

              + * + * When this task executes, it will generate C header and source files that are + * needed to implement native methods. + * + * @author Rick Beton + * richard.beton@physics.org + */ + +public class Javah extends Task +{ + + private final static String FAIL_MSG = "Compile failed, messages should have been provided."; + //private Path extdirs; + private static String lSep = System.getProperty( "line.separator" ); + + private Vector classes = new Vector( 2 ); + private Path classpath = null; + private File outputFile = null; + private boolean verbose = false; + private boolean force = false; + private boolean old = false; + private boolean stubs = false; + private Path bootclasspath; + private String cls; + private File destDir; + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new BootClasspathRef value + */ + public void setBootClasspathRef( Reference r ) + { + createBootclasspath().setRefid( r ); + } + + public void setBootclasspath( Path src ) + { + if( bootclasspath == null ) + { + bootclasspath = src; + } + else + { + bootclasspath.append( src ); + } + } + + public void setClass( String cls ) + { + this.cls = cls; + } + + public void setClasspath( Path src ) + { + if( classpath == null ) + { + classpath = src; + } + else + { + classpath.append( src ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Set the destination directory into which the Java source files should be + * compiled. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Set the force-write flag. + * + * @param force The new Force value + */ + public void setForce( boolean force ) + { + this.force = force; + } + + /** + * Set the old flag. + * + * @param old The new Old value + */ + public void setOld( boolean old ) + { + this.old = old; + } + + ///** + // * Sets the extension directories that will be used during the + // * compilation. + // */ + //public void setExtdirs(Path extdirs) { + // if (this.extdirs == null) { + // this.extdirs = extdirs; + // } else { + // this.extdirs.append(extdirs); + // } + //} + + ///** + // * Maybe creates a nested classpath element. + // */ + //public Path createExtdirs() { + // if (extdirs == null) { + // extdirs = new Path(project); + // } + // return extdirs.createPath(); + //} + + /** + * Set the output file name. + * + * @param outputFile The new OutputFile value + */ + public void setOutputFile( File outputFile ) + { + this.outputFile = outputFile; + } + + /** + * Set the stubs flag. + * + * @param stubs The new Stubs value + */ + public void setStubs( boolean stubs ) + { + this.stubs = stubs; + } + + /** + * Set the verbose flag. + * + * @param verbose The new Verbose value + */ + public void setVerbose( boolean verbose ) + { + this.verbose = verbose; + } + + public Path createBootclasspath() + { + if( bootclasspath == null ) + { + bootclasspath = new Path( project ); + } + return bootclasspath.createPath(); + } + + public ClassArgument createClass() + { + ClassArgument ga = new ClassArgument(); + classes.addElement( ga ); + return ga; + } + + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Executes the task. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir + + if( ( cls == null ) && ( classes.size() == 0 ) ) + { + throw new BuildException( "class attribute must be set!", location ); + } + + if( ( cls != null ) && ( classes.size() > 0 ) ) + { + throw new BuildException( "set class attribute or class element, not both.", location ); + } + + if( destDir != null ) + { + if( !destDir.isDirectory() ) + { + throw new BuildException( "destination directory \"" + destDir + "\" does not exist or is not a directory", location ); + } + if( outputFile != null ) + { + throw new BuildException( "destdir and outputFile are mutually exclusive", location ); + } + } + + if( classpath == null ) + { + classpath = Path.systemClasspath; + } + + String compiler = project.getProperty( "build.compiler" ); + if( compiler == null ) + { + if( Project.getJavaVersion() != Project.JAVA_1_1 && + Project.getJavaVersion() != Project.JAVA_1_2 ) + { + compiler = "modern"; + } + else + { + compiler = "classic"; + } + } + + doClassicCompile(); + } + + /** + * Logs the compilation parameters, adds the files to compile and logs the + * &qout;niceSourceList" + * + * @param cmd Description of Parameter + */ + protected void logAndAddFilesToCompile( Commandline cmd ) + { + int n = 0; + log( "Compilation args: " + cmd.toString(), + Project.MSG_VERBOSE ); + + StringBuffer niceClassList = new StringBuffer(); + if( cls != null ) + { + StringTokenizer tok = new StringTokenizer( cls, ",", false ); + while( tok.hasMoreTokens() ) + { + String aClass = tok.nextToken().trim(); + cmd.createArgument().setValue( aClass ); + niceClassList.append( " " + aClass + lSep ); + n++; + } + } + + Enumeration enum = classes.elements(); + while( enum.hasMoreElements() ) + { + ClassArgument arg = ( ClassArgument )enum.nextElement(); + String aClass = arg.getName(); + cmd.createArgument().setValue( aClass ); + niceClassList.append( " " + aClass + lSep ); + n++; + } + + StringBuffer prefix = new StringBuffer( "Class" ); + if( n > 1 ) + { + prefix.append( "es" ); + } + prefix.append( " to be compiled:" ); + prefix.append( lSep ); + + log( prefix.toString() + niceClassList.toString(), Project.MSG_VERBOSE ); + } + + /** + * Does the command line argument processing common to classic and modern. + * + * @return Description of the Returned Value + */ + private Commandline setupJavahCommand() + { + Commandline cmd = new Commandline(); + + if( destDir != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( destDir ); + } + + if( outputFile != null ) + { + cmd.createArgument().setValue( "-o" ); + cmd.createArgument().setFile( outputFile ); + } + + if( classpath != null ) + { + cmd.createArgument().setValue( "-classpath" ); + cmd.createArgument().setPath( classpath ); + } + + // JDK1.1 is rather simpler than JDK1.2 + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + if( verbose ) + { + cmd.createArgument().setValue( "-v" ); + } + } + else + { + if( verbose ) + { + cmd.createArgument().setValue( "-verbose" ); + } + if( old ) + { + cmd.createArgument().setValue( "-old" ); + } + if( force ) + { + cmd.createArgument().setValue( "-force" ); + } + } + + if( stubs ) + { + if( !old ) + { + throw new BuildException( "stubs only available in old mode.", location ); + } + cmd.createArgument().setValue( "-stubs" ); + } + if( bootclasspath != null ) + { + cmd.createArgument().setValue( "-bootclasspath" ); + cmd.createArgument().setPath( bootclasspath ); + } + + logAndAddFilesToCompile( cmd ); + return cmd; + } + + // XXX + // we need a way to not use the current classpath. + + /** + * Peforms a compile using the classic compiler that shipped with JDK 1.1 + * and 1.2. + * + * @exception BuildException Description of Exception + */ + + private void doClassicCompile() + throws BuildException + { + Commandline cmd = setupJavahCommand(); + + // Use reflection to be able to build on all JDKs + /* + * / provide the compiler a different message sink - namely our own + * sun.tools.javac.Main compiler = + * new sun.tools.javac.Main(new LogOutputStream(this, Project.MSG_WARN), "javac"); + * if (!compiler.compile(cmd.getArguments())) { + * throw new BuildException("Compile failed"); + * } + */ + try + { + // Javac uses logstr to change the output stream and calls + // the constructor's invoke method to create a compiler instance + // dynamically. However, javah has a different interface and this + // makes it harder, so here's a simple alternative. + //------------------------------------------------------------------ + com.sun.tools.javah.Main main = new com.sun.tools.javah.Main( cmd.getArguments() ); + main.run(); + } + //catch (ClassNotFoundException ex) { + // throw new BuildException("Cannot use javah because it is not available"+ + // " A common solution is to set the environment variable"+ + // " JAVA_HOME to your jdk directory.", location); + //} + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error starting javah: ", ex, location ); + } + } + } + + public class ClassArgument + { + private String name; + + public ClassArgument() { } + + public void setName( String name ) + { + this.name = name; + log( "ClassArgument.name=" + name ); + } + + public String getName() + { + return name; + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ManifestFile.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ManifestFile.java new file mode 100644 index 000000000..4ef6878db --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ManifestFile.java @@ -0,0 +1,396 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.ListIterator; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +/** + * Task for creating a manifest file for a jar archiv. use:

              + *   
              + *   
              + *     
              + *         
              + *       
              + * + * @author Thomas Kerle + * @version 1.0 2001-10-11 + */ +public class ManifestFile extends Task +{ + + private final static String newLine = System.getProperty( "line.separator" ); + private final static String keyValueSeparator = ":"; + private final static String UPDATE_ = "update"; + private final static String REPLACEALL_ = "replaceAll"; + private EntryContainer container; + private String currentMethod; + private Vector entries; + + private File manifestFile; + + public ManifestFile() + { + entries = new Vector(); + container = new EntryContainer(); + } + + /** + * Setter for the file attribute + * + * @param f The new File value + */ + public void setFile( File f ) + { + manifestFile = f; + } + + /** + * Setter for the method attribute (update/replaceAll) + * + * @param method Method to set task + */ + public void setMethod( String method ) + { + currentMethod = method.toUpperCase(); + } + + /** + * creating entries by Ant + * + * @return Description of the Returned Value + */ + public Entry createEntry() + { + Entry entry = new Entry(); + entries.addElement( entry ); + return entry; + } + + /** + * execute task + * + * @exception BuildException : Failure in building + */ + public void execute() + throws BuildException + { + checkParameters(); + if( isUpdate( currentMethod ) ) + readFile(); + + executeOperation(); + writeFile(); + } + + private StringTokenizer getLineTokens( StringBuffer buffer ) + { + String manifests = buffer.toString(); + StringTokenizer strTokens = new StringTokenizer( manifests, newLine ); + return strTokens; + } + + private boolean isReplaceAll( String method ) + { + return method.equals( REPLACEALL_.toUpperCase() ); + } + + + private boolean isUpdate( String method ) + { + return method.equals( UPDATE_.toUpperCase() ); + } + + private void addLine( String line ) + { + Entry entry = new Entry(); + + entry.setValue( line ); + entry.addTo( container ); + } + + + private StringBuffer buildBuffer() + { + StringBuffer buffer = new StringBuffer(); + + ListIterator iterator = container.elements(); + + while( iterator.hasNext() ) + { + Entry entry = ( Entry )iterator.next(); + + String key = ( String )entry.getKey(); + String value = ( String )entry.getValue(); + String entry_string = key + keyValueSeparator + value; + + buffer.append( entry_string + this.newLine ); + } + + return buffer; + } + + private boolean checkParam( String param ) + { + return !( ( param == null ) || ( param.equals( "null" ) ) ); + } + + private boolean checkParam( File param ) + { + return !( param == null ); + } + + private void checkParameters() + throws BuildException + { + if( !checkParam( manifestFile ) ) + { + throw new BuildException( "file token must not be null.", location ); + } + } + + /** + * adding entries to a container + * + * @exception BuildException + */ + private void executeOperation() + throws BuildException + { + Enumeration enum = entries.elements(); + + while( enum.hasMoreElements() ) + { + Entry entry = ( Entry )enum.nextElement(); + entry.addTo( container ); + } + } + + private void readFile() + throws BuildException + { + + if( manifestFile.exists() ) + { + this.log( "update existing manifest file " + manifestFile.getAbsolutePath() ); + + if( container != null ) + { + try + { + FileInputStream fis = new FileInputStream( manifestFile ); + + int c; + StringBuffer buffer = new StringBuffer( "" ); + boolean stop = false; + while( !stop ) + { + c = fis.read(); + if( c == -1 ) + { + stop = true; + } + else + buffer.append( ( char )c ); + } + fis.close(); + StringTokenizer lineTokens = getLineTokens( buffer ); + while( lineTokens.hasMoreElements() ) + { + String currentLine = ( String )lineTokens.nextElement(); + addLine( currentLine ); + } + } + catch( FileNotFoundException fnfe ) + { + throw new BuildException( "File not found exception " + fnfe.toString() ); + } + catch( IOException ioe ) + { + throw new BuildException( "Unknown input/output exception " + ioe.toString() ); + } + } + } + + } + + + private void writeFile() + throws BuildException + { + try + { + manifestFile.delete(); + log( "Replacing or creating new manifest file " + manifestFile.getAbsolutePath() ); + if( manifestFile.createNewFile() ) + { + FileOutputStream fos = new FileOutputStream( manifestFile ); + + StringBuffer buffer = buildBuffer(); + + int size = buffer.length(); + + for( int i = 0; i < size; i++ ) + { + fos.write( ( char )buffer.charAt( i ) ); + } + + fos.flush(); + fos.close(); + } + else + { + throw new BuildException( "Can't create manifest file" ); + } + + } + catch( IOException ioe ) + { + throw new BuildException( "An input/ouput error occured" + ioe.toString() ); + } + } + + public class Entry implements Comparator + { + //extern format + private String value = null; + + //intern representation + private String val = null; + private String key = null; + + public Entry() { } + + public void setValue( String value ) + { + this.value = new String( value ); + } + + public String getKey() + { + return key; + } + + public String getValue() + { + return val; + } + + public int compare( Object o1, Object o2 ) + { + int result = -1; + + try + { + Entry e1 = ( Entry )o1; + Entry e2 = ( Entry )o2; + + String key_1 = e1.getKey(); + String key_2 = e2.getKey(); + + result = key_1.compareTo( key_2 ); + } + catch( Exception e ) + { + + } + return result; + } + + + public boolean equals( Object obj ) + { + Entry ent = new Entry(); + boolean result = false; + int res = ent.compare( this, ( Entry )obj ); + if( res == 0 ) + result = true; + + return result; + } + + + protected void addTo( EntryContainer container ) + throws BuildException + { + checkFormat(); + split(); + container.set( this ); + } + + private void checkFormat() + throws BuildException + { + + if( value == null ) + { + throw new BuildException( "no argument for value" ); + } + + StringTokenizer st = new StringTokenizer( value, ManifestFile.keyValueSeparator ); + int size = st.countTokens(); + + if( size < 2 ) + { + throw new BuildException( "value has not the format of a manifest entry" ); + } + } + + private void split() + { + StringTokenizer st = new StringTokenizer( value, ManifestFile.keyValueSeparator ); + key = ( String )st.nextElement(); + val = ( String )st.nextElement(); + } + + } + + public class EntryContainer + { + + private ArrayList list = null; + + public EntryContainer() + { + list = new ArrayList(); + } + + public void set( Entry entry ) + { + + if( list.contains( entry ) ) + { + int index = list.indexOf( entry ); + + list.remove( index ); + list.add( index, entry ); + } + else + { + list.add( entry ); + } + } + + public ListIterator elements() + { + ListIterator iterator = list.listIterator(); + return iterator; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java new file mode 100644 index 000000000..882335c60 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Native2Ascii.java @@ -0,0 +1,258 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Mapper; +import org.apache.tools.ant.util.FileNameMapper; +import org.apache.tools.ant.util.IdentityMapper; +import org.apache.tools.ant.util.SourceFileScanner; + +/** + * Convert files from native encodings to ascii. + * + * @author Drew Sudell + * @author Stefan Bodewig + */ +public class Native2Ascii extends MatchingTask +{ + + private boolean reverse = false;// convert from ascii back to native + private String encoding = null;// encoding to convert to/from + private File srcDir = null;// Where to find input files + private File destDir = null;// Where to put output files + private String extension = null;// Extension of output files if different + + private Mapper mapper; + + + /** + * Set the destination dirctory to place converted files into. + * + * @param destDir directory to place output file into. + */ + public void setDest( File destDir ) + { + this.destDir = destDir; + } + + /** + * Set the encoding to translate to/from. If unset, the default encoding for + * the JVM is used. + * + * @param encoding String containing the name of the Native encoding to + * convert from or to. + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Set the extension which converted files should have. If unset, files will + * not be renamed. + * + * @param ext File extension to use for converted files. + */ + public void setExt( String ext ) + { + this.extension = ext; + } + + /** + * Flag the conversion to run in the reverse sense, that is Ascii to Native + * encoding. + * + * @param reverse True if the conversion is to be reversed, otherwise false; + */ + public void setReverse( boolean reverse ) + { + this.reverse = reverse; + } + + /** + * Set the source directory in which to find files to convert. + * + * @param srcDir Direcrory to find input file in. + */ + public void setSrc( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * Defines the FileNameMapper to use (nested mapper element). + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Mapper createMapper() + throws BuildException + { + if( mapper != null ) + { + throw new BuildException( "Cannot define more than one mapper", + location ); + } + mapper = new Mapper( project ); + return mapper; + } + + public void execute() + throws BuildException + { + + Commandline baseCmd = null;// the common portion of our cmd line + DirectoryScanner scanner = null;// Scanner to find our inputs + String[] files;// list of files to process + + // default srcDir to basedir + if( srcDir == null ) + { + srcDir = project.resolveFile( "." ); + } + + // Require destDir + if( destDir == null ) + { + throw new BuildException( "The dest attribute must be set." ); + } + + // if src and dest dirs are the same, require the extension + // to be set, so we don't stomp every file. One could still + // include a file with the same extension, but .... + if( srcDir.equals( destDir ) && extension == null && mapper == null ) + { + throw new BuildException( "The ext attribute or a mapper must be set if" + + " src and dest dirs are the same." ); + } + + FileNameMapper m = null; + if( mapper == null ) + { + if( extension == null ) + { + m = new IdentityMapper(); + } + else + { + m = new ExtMapper(); + } + } + else + { + m = mapper.getImplementation(); + } + + scanner = getDirectoryScanner( srcDir ); + files = scanner.getIncludedFiles(); + SourceFileScanner sfs = new SourceFileScanner( this ); + files = sfs.restrict( files, srcDir, destDir, m ); + int count = files.length; + if( count == 0 ) + { + return; + } + String message = "Converting " + count + " file" + + ( count != 1 ? "s" : "" ) + " from "; + log( message + srcDir + " to " + destDir ); + for( int i = 0; i < files.length; i++ ) + { + convert( files[i], m.mapFileName( files[i] )[0] ); + } + } + + /** + * Convert a single file. + * + * @param srcName Description of Parameter + * @param destName Description of Parameter + * @exception BuildException Description of Exception + */ + private void convert( String srcName, String destName ) + throws BuildException + { + + Commandline cmd = new Commandline();// Command line to run + File srcFile;// File to convert + File destFile;// where to put the results + + // Set up the basic args (this could be done once, but + // it's cleaner here) + if( reverse ) + { + cmd.createArgument().setValue( "-reverse" ); + } + + if( encoding != null ) + { + cmd.createArgument().setValue( "-encoding" ); + cmd.createArgument().setValue( encoding ); + } + + // Build the full file names + srcFile = new File( srcDir, srcName ); + destFile = new File( destDir, destName ); + + cmd.createArgument().setFile( srcFile ); + cmd.createArgument().setFile( destFile ); + // Make sure we're not about to clobber something + if( srcFile.equals( destFile ) ) + { + throw new BuildException( "file " + srcFile + + " would overwrite its self" ); + } + + // Make intermediate directories if needed + // XXX JDK 1.1 dosen't have File.getParentFile, + String parentName = destFile.getParent(); + if( parentName != null ) + { + File parentFile = new File( parentName ); + + if( ( !parentFile.exists() ) && ( !parentFile.mkdirs() ) ) + { + throw new BuildException( "cannot create parent directory " + + parentName ); + } + } + + log( "converting " + srcName, Project.MSG_VERBOSE ); + sun.tools.native2ascii.Main n2a + = new sun.tools.native2ascii.Main(); + if( !n2a.convert( cmd.getArguments() ) ) + { + throw new BuildException( "conversion failed" ); + } + } + + private class ExtMapper implements FileNameMapper + { + + public void setFrom( String s ) { } + + public void setTo( String s ) { } + + public String[] mapFileName( String fileName ) + { + int lastDot = fileName.lastIndexOf( '.' ); + if( lastDot >= 0 ) + { + return new String[]{fileName.substring( 0, lastDot ) + extension}; + } + else + { + return new String[]{fileName + extension}; + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/NetRexxC.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/NetRexxC.java new file mode 100644 index 000000000..3b05253a1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/NetRexxC.java @@ -0,0 +1,766 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.StringTokenizer; +import java.util.Vector; +import netrexx.lang.Rexx; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; + +/** + * Task to compile NetRexx source files. This task can take the following + * arguments: + *
                + *
              • binary
              • + *
              • classpath
              • + *
              • comments
              • + *
              • compile
              • + *
              • console
              • + *
              • crossref
              • + *
              • decimal
              • + *
              • destdir
              • + *
              • diag
              • + *
              • explicit
              • + *
              • format
              • + *
              • keep
              • + *
              • logo
              • + *
              • replace
              • + *
              • savelog
              • + *
              • srcdir
              • + *
              • sourcedir
              • + *
              • strictargs
              • + *
              • strictassign
              • + *
              • strictcase
              • + *
              • strictimport
              • + *
              • symbols
              • + *
              • time
              • + *
              • trace
              • + *
              • utf8
              • + *
              • verbose
              • + *
              + * Of these arguments, the srcdir argument is required.

              + * + * When this task executes, it will recursively scan the srcdir looking for + * NetRexx source files to compile. This task makes its compile decision based + * on timestamp.

              + * + * Before files are compiled they and any other file in the srcdir will be + * copied to the destdir allowing support files to be located properly in the + * classpath. The reason for copying the source files before the compile is that + * NetRexxC has only two destinations for classfiles: + *

                + *
              1. The current directory, and,
              2. + *
              3. The directory the source is in (see sourcedir option) + *
              + * + * + * @author dIon Gillard + * dion@multitask.com.au + */ + +public class NetRexxC extends MatchingTask +{ + private boolean compile = true; + private boolean decimal = true; + private boolean logo = true; + private boolean sourcedir = true; + private String trace = "trace2"; + private String verbose = "verbose3"; + + // other implementation variables + private Vector compileList = new Vector(); + private Hashtable filecopyList = new Hashtable(); + private String oldClasspath = System.getProperty( "java.class.path" ); + + // variables to hold arguments + private boolean binary; + private String classpath; + private boolean comments; + private boolean compact; + private boolean console; + private boolean crossref; + private File destDir; + private boolean diag; + private boolean explicit; + private boolean format; + private boolean java; + private boolean keep; + private boolean replace; + private boolean savelog; + private File srcDir;// ?? Should this be the default for ant? + private boolean strictargs; + private boolean strictassign; + private boolean strictcase; + private boolean strictimport; + private boolean strictprops; + private boolean strictsignal; + private boolean symbols; + private boolean time; + private boolean utf8; + + + /** + * Set whether literals are treated as binary, rather than NetRexx types + * + * @param binary The new Binary value + */ + public void setBinary( boolean binary ) + { + this.binary = binary; + } + + /** + * Set the classpath used for NetRexx compilation + * + * @param classpath The new Classpath value + */ + public void setClasspath( String classpath ) + { + this.classpath = classpath; + } + + /** + * Set whether comments are passed through to the generated java source. + * Valid true values are "on" or "true". Anything else sets the flag to + * false. The default value is false + * + * @param comments The new Comments value + */ + public void setComments( boolean comments ) + { + this.comments = comments; + } + + /** + * Set whether error messages come out in compact or verbose format. Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false + * + * @param compact The new Compact value + */ + public void setCompact( boolean compact ) + { + this.compact = compact; + } + + /** + * Set whether the NetRexx compiler should compile the generated java code + * Valid true values are "on" or "true". Anything else sets the flag to + * false. The default value is true. Setting this flag to false, will + * automatically set the keep flag to true. + * + * @param compile The new Compile value + */ + public void setCompile( boolean compile ) + { + this.compile = compile; + if( !this.compile && !this.keep ) + this.keep = true; + } + + /** + * Set whether or not messages should be displayed on the 'console' Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is true. + * + * @param console The new Console value + */ + public void setConsole( boolean console ) + { + this.console = console; + } + + /** + * Whether variable cross references are generated + * + * @param crossref The new Crossref value + */ + public void setCrossref( boolean crossref ) + { + this.crossref = crossref; + } + + /** + * Set whether decimal arithmetic should be used for the netrexx code. + * Binary arithmetic is used when this flag is turned off. Valid true values + * are "on" or "true". Anything else sets the flag to false. The default + * value is true. + * + * @param decimal The new Decimal value + */ + public void setDecimal( boolean decimal ) + { + this.decimal = decimal; + } + + /** + * Set the destination directory into which the NetRexx source files should + * be copied and then compiled. + * + * @param destDirName The new DestDir value + */ + public void setDestDir( File destDirName ) + { + destDir = destDirName; + } + + /** + * Whether diagnostic information about the compile is generated + * + * @param diag The new Diag value + */ + public void setDiag( boolean diag ) + { + this.diag = diag; + } + + /** + * Sets whether variables must be declared explicitly before use. Valid true + * values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param explicit The new Explicit value + */ + public void setExplicit( boolean explicit ) + { + this.explicit = explicit; + } + + /** + * Whether the generated java code is formatted nicely or left to match + * NetRexx line numbers for call stack debugging + * + * @param format The new Format value + */ + public void setFormat( boolean format ) + { + this.format = format; + } + + /** + * Whether the generated java code is produced Valid true values are "on" or + * "true". Anything else sets the flag to false. The default value is false. + * + * @param java The new Java value + */ + public void setJava( boolean java ) + { + this.java = java; + } + + + /** + * Sets whether the generated java source file should be kept after + * compilation. The generated files will have an extension of .java.keep, + * not .java Valid true values are "on" or "true". Anything else sets + * the flag to false. The default value is false. + * + * @param keep The new Keep value + */ + public void setKeep( boolean keep ) + { + this.keep = keep; + } + + /** + * Whether the compiler text logo is displayed when compiling + * + * @param logo The new Logo value + */ + public void setLogo( boolean logo ) + { + this.logo = logo; + } + + /** + * Whether the generated .java file should be replaced when compiling Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param replace The new Replace value + */ + public void setReplace( boolean replace ) + { + this.replace = replace; + } + + /** + * Sets whether the compiler messages will be written to NetRexxC.log as + * well as to the console Valid true values are "on" or "true". Anything + * else sets the flag to false. The default value is false. + * + * @param savelog The new Savelog value + */ + public void setSavelog( boolean savelog ) + { + this.savelog = savelog; + } + + /** + * Tells the NetRexx compiler to store the class files in the same directory + * as the source files. The alternative is the working directory Valid true + * values are "on" or "true". Anything else sets the flag to false. The + * default value is true. + * + * @param sourcedir The new Sourcedir value + */ + public void setSourcedir( boolean sourcedir ) + { + this.sourcedir = sourcedir; + } + + /** + * Set the source dir to find the source Java files. + * + * @param srcDirName The new SrcDir value + */ + public void setSrcDir( File srcDirName ) + { + srcDir = srcDirName; + } + + /** + * Tells the NetRexx compiler that method calls always need parentheses, + * even if no arguments are needed, e.g. aStringVar.getBytes + * vs. aStringVar.getBytes() Valid true values are "on" or + * "true". Anything else sets the flag to false. The default value is false. + * + * @param strictargs The new Strictargs value + */ + public void setStrictargs( boolean strictargs ) + { + this.strictargs = strictargs; + } + + /** + * Tells the NetRexx compile that assignments must match exactly on type + * + * @param strictassign The new Strictassign value + */ + public void setStrictassign( boolean strictassign ) + { + this.strictassign = strictassign; + } + + /** + * Specifies whether the NetRexx compiler should be case sensitive or not + * + * @param strictcase The new Strictcase value + */ + public void setStrictcase( boolean strictcase ) + { + this.strictcase = strictcase; + } + + /** + * Sets whether classes need to be imported explicitly using an import + * statement. By default the NetRexx compiler will import certain packages + * automatically Valid true values are "on" or "true". Anything else sets + * the flag to false. The default value is false. + * + * @param strictimport The new Strictimport value + */ + public void setStrictimport( boolean strictimport ) + { + this.strictimport = strictimport; + } + + /** + * Sets whether local properties need to be qualified explicitly using + * this Valid true values are "on" or "true". Anything else + * sets the flag to false. The default value is false. + * + * @param strictprops The new Strictprops value + */ + public void setStrictprops( boolean strictprops ) + { + this.strictprops = strictprops; + } + + + /** + * Whether the compiler should force catching of exceptions by explicitly + * named types + * + * @param strictsignal The new Strictsignal value + */ + public void setStrictsignal( boolean strictsignal ) + { + this.strictsignal = strictsignal; + } + + /** + * Sets whether debug symbols should be generated into the class file Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param symbols The new Symbols value + */ + public void setSymbols( boolean symbols ) + { + this.symbols = symbols; + } + + /** + * Asks the NetRexx compiler to print compilation times to the console Valid + * true values are "on" or "true". Anything else sets the flag to false. The + * default value is false. + * + * @param time The new Time value + */ + public void setTime( boolean time ) + { + this.time = time; + } + + /** + * Turns on or off tracing and directs the resultant trace output Valid + * values are: "trace", "trace1", "trace2" and "notrace". "trace" and + * "trace2" + * + * @param trace The new Trace value + */ + public void setTrace( String trace ) + { + if( trace.equalsIgnoreCase( "trace" ) + || trace.equalsIgnoreCase( "trace1" ) + || trace.equalsIgnoreCase( "trace2" ) + || trace.equalsIgnoreCase( "notrace" ) ) + { + this.trace = trace; + } + else + { + throw new BuildException( "Unknown trace value specified: '" + trace + "'" ); + } + } + + /** + * Tells the NetRexx compiler that the source is in UTF8 Valid true values + * are "on" or "true". Anything else sets the flag to false. The default + * value is false. + * + * @param utf8 The new Utf8 value + */ + public void setUtf8( boolean utf8 ) + { + this.utf8 = utf8; + } + + /** + * Whether lots of warnings and error messages should be generated + * + * @param verbose The new Verbose value + */ + public void setVerbose( String verbose ) + { + this.verbose = verbose; + } + + /** + * Executes the task, i.e. does the actual compiler call + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + + // first off, make sure that we've got a srcdir and destdir + if( srcDir == null || destDir == null ) + { + throw new BuildException( "srcDir and destDir attributes must be set!" ); + } + + // scan source and dest dirs to build up both copy lists and + // compile lists + // scanDir(srcDir, destDir); + DirectoryScanner ds = getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + + scanDir( srcDir, destDir, files ); + + // copy the source and support files + copyFilesToDestination(); + + // compile the source files + if( compileList.size() > 0 ) + { + log( "Compiling " + compileList.size() + " source file" + + ( compileList.size() == 1 ? "" : "s" ) + + " to " + destDir ); + doNetRexxCompile(); + } + } + + /** + * Builds the compilation classpath. + * + * @return The CompileClasspath value + */ + private String getCompileClasspath() + { + StringBuffer classpath = new StringBuffer(); + + // add dest dir to classpath so that previously compiled and + // untouched classes are on classpath + classpath.append( destDir.getAbsolutePath() ); + + // add our classpath to the mix + if( this.classpath != null ) + { + addExistingToClasspath( classpath, this.classpath ); + } + + // add the system classpath + // addExistingToClasspath(classpath,System.getProperty("java.class.path")); + return classpath.toString(); + } + + /** + * This + * + * @return The CompileOptionsAsArray value + */ + private String[] getCompileOptionsAsArray() + { + Vector options = new Vector(); + options.addElement( binary ? "-binary" : "-nobinary" ); + options.addElement( comments ? "-comments" : "-nocomments" ); + options.addElement( compile ? "-compile" : "-nocompile" ); + options.addElement( compact ? "-compact" : "-nocompact" ); + options.addElement( console ? "-console" : "-noconsole" ); + options.addElement( crossref ? "-crossref" : "-nocrossref" ); + options.addElement( decimal ? "-decimal" : "-nodecimal" ); + options.addElement( diag ? "-diag" : "-nodiag" ); + options.addElement( explicit ? "-explicit" : "-noexplicit" ); + options.addElement( format ? "-format" : "-noformat" ); + options.addElement( keep ? "-keep" : "-nokeep" ); + options.addElement( logo ? "-logo" : "-nologo" ); + options.addElement( replace ? "-replace" : "-noreplace" ); + options.addElement( savelog ? "-savelog" : "-nosavelog" ); + options.addElement( sourcedir ? "-sourcedir" : "-nosourcedir" ); + options.addElement( strictargs ? "-strictargs" : "-nostrictargs" ); + options.addElement( strictassign ? "-strictassign" : "-nostrictassign" ); + options.addElement( strictcase ? "-strictcase" : "-nostrictcase" ); + options.addElement( strictimport ? "-strictimport" : "-nostrictimport" ); + options.addElement( strictprops ? "-strictprops" : "-nostrictprops" ); + options.addElement( strictsignal ? "-strictsignal" : "-nostrictsignal" ); + options.addElement( symbols ? "-symbols" : "-nosymbols" ); + options.addElement( time ? "-time" : "-notime" ); + options.addElement( "-" + trace ); + options.addElement( utf8 ? "-utf8" : "-noutf8" ); + options.addElement( "-" + verbose ); + String[] results = new String[options.size()]; + options.copyInto( results ); + return results; + } + + /** + * Takes a classpath-like string, and adds each element of this string to a + * new classpath, if the components exist. Components that don't exist, + * aren't added. We do this, because jikes issues warnings for non-existant + * files/dirs in his classpath, and these warnings are pretty annoying. + * + * @param target - target classpath + * @param source - source classpath to get file objects. + */ + private void addExistingToClasspath( StringBuffer target, String source ) + { + StringTokenizer tok = new StringTokenizer( source, + System.getProperty( "path.separator" ), false ); + while( tok.hasMoreTokens() ) + { + File f = project.resolveFile( tok.nextToken() ); + + if( f.exists() ) + { + target.append( File.pathSeparator ); + target.append( f.getAbsolutePath() ); + } + else + { + log( "Dropping from classpath: " + + f.getAbsolutePath(), Project.MSG_VERBOSE ); + } + } + + } + + /** + * Copy eligible files from the srcDir to destDir + */ + private void copyFilesToDestination() + { + if( filecopyList.size() > 0 ) + { + log( "Copying " + filecopyList.size() + " file" + + ( filecopyList.size() == 1 ? "" : "s" ) + + " to " + destDir.getAbsolutePath() ); + Enumeration enum = filecopyList.keys(); + while( enum.hasMoreElements() ) + { + String fromFile = ( String )enum.nextElement(); + String toFile = ( String )filecopyList.get( fromFile ); + try + { + project.copyFile( fromFile, toFile ); + } + catch( IOException ioe ) + { + String msg = "Failed to copy " + fromFile + " to " + toFile + + " due to " + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + } + } + } + + /** + * Peforms a copmile using the NetRexx 1.1.x compiler + * + * @exception BuildException Description of Exception + */ + private void doNetRexxCompile() + throws BuildException + { + log( "Using NetRexx compiler", Project.MSG_VERBOSE ); + String classpath = getCompileClasspath(); + StringBuffer compileOptions = new StringBuffer(); + StringBuffer fileList = new StringBuffer(); + + // create an array of strings for input to the compiler: one array + // comes from the compile options, the other from the compileList + String[] compileOptionsArray = getCompileOptionsAsArray(); + String[] fileListArray = new String[compileList.size()]; + Enumeration e = compileList.elements(); + int j = 0; + while( e.hasMoreElements() ) + { + fileListArray[j] = ( String )e.nextElement(); + j++; + } + // create a single array of arguments for the compiler + String compileArgs[] = new String[compileOptionsArray.length + fileListArray.length]; + for( int i = 0; i < compileOptionsArray.length; i++ ) + { + compileArgs[i] = compileOptionsArray[i]; + } + for( int i = 0; i < fileListArray.length; i++ ) + { + compileArgs[i + compileOptionsArray.length] = fileListArray[i]; + } + + // print nice output about what we are doing for the log + compileOptions.append( "Compilation args: " ); + for( int i = 0; i < compileOptionsArray.length; i++ ) + { + compileOptions.append( compileOptionsArray[i] ); + compileOptions.append( " " ); + } + log( compileOptions.toString(), Project.MSG_VERBOSE ); + + String eol = System.getProperty( "line.separator" ); + StringBuffer niceSourceList = new StringBuffer( "Files to be compiled:" + eol ); + + for( int i = 0; i < compileList.size(); i++ ) + { + niceSourceList.append( " " ); + niceSourceList.append( compileList.elementAt( i ).toString() ); + niceSourceList.append( eol ); + } + + log( niceSourceList.toString(), Project.MSG_VERBOSE ); + + // need to set java.class.path property and restore it later + // since the NetRexx compiler has no option for the classpath + String currentClassPath = System.getProperty( "java.class.path" ); + Properties currentProperties = System.getProperties(); + currentProperties.put( "java.class.path", classpath ); + + try + { + StringWriter out = new StringWriter(); + int rc = + COM.ibm.netrexx.process.NetRexxC.main( new Rexx( compileArgs ), new PrintWriter( out ) ); + + if( rc > 1 ) + {// 1 is warnings from real NetRexxC + log( out.toString(), Project.MSG_ERR ); + String msg = "Compile failed, messages should have been provided."; + throw new BuildException( msg ); + } + else if( rc == 1 ) + { + log( out.toString(), Project.MSG_WARN ); + } + else + { + log( out.toString(), Project.MSG_INFO ); + } + } + finally + { + // need to reset java.class.path property + // since the NetRexx compiler has no option for the classpath + currentProperties = System.getProperties(); + currentProperties.put( "java.class.path", currentClassPath ); + } + } + + /** + * Scans the directory looking for source files to be compiled and support + * files to be copied. + * + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param files Description of Parameter + */ + private void scanDir( File srcDir, File destDir, String[] files ) + { + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + File destFile = new File( destDir, files[i] ); + String filename = files[i]; + // if it's a non source file, copy it if a later date than the + // dest + // if it's a source file, see if the destination class file + // needs to be recreated via compilation + if( filename.toLowerCase().endsWith( ".nrx" ) ) + { + File classFile = + new File( destDir, + filename.substring( 0, filename.lastIndexOf( '.' ) ) + ".class" ); + + if( !compile || srcFile.lastModified() > classFile.lastModified() ) + { + filecopyList.put( srcFile.getAbsolutePath(), destFile.getAbsolutePath() ); + compileList.addElement( destFile.getAbsolutePath() ); + } + } + else + { + if( srcFile.lastModified() > destFile.lastModified() ) + { + filecopyList.put( srcFile.getAbsolutePath(), destFile.getAbsolutePath() ); + } + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/PropertyFile.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/PropertyFile.java new file mode 100644 index 000000000..ab7bfd7b1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/PropertyFile.java @@ -0,0 +1,791 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Enumeration; +import java.util.GregorianCalendar; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + * PropertyFile task uses java.util.Properties to modify integer, String and + * Date settings in a property file.

              + * + * The following is an example of its usage: + *

                <target name="setState">
                + * + *
                  <property
                  + * + *
                    name="header"
                    + * value="##Generated file - do not modify!"/>
                    + * <propertyfile file="apropfile.properties" comment="${header}"> + *
                    + * <entry key="product.version.major" type="int" value="5"/>
                    + * <entry key="product.version.minor" type="int" value="0"/>
                    + * <entry key="product.build.major" type="int" value="0" />
                    + * <entry key="product.build.minor" type="int" operation="+" />
                    + * <entry key="product.build.date" type="date" operation="now" /> + *
                    + * <entry key="intSet" type="int" operation="=" value="681"/>
                    + * <entry key="intDec" type="int" operation="-"/>
                    + * <entry key="NeverDate" type="date" operation="never"/>
                    + * <entry key="StringEquals" type="string" value="testValue"/>
                    + * <entry key="NowDate" type="date" operation="now"/>
                    + * + *
                  + * </propertyfile>
                  + * + *
                + * </target> + *
              + *

              + * + * The <propertyfile> task must have:
              + * + *

                + *
              • file
              • + *
              + * Other parameters are:
              + * + *
                + *
              • comment, key, operation, type and value (the final four being + * eliminated shortly)
              • + *
              + * The <entry> task must have:
              + * + *
                + *
              • key
              • + *
              + * Other parameters are:
              + * + *
                + *
              • operation
              • + *
              • type
              • + *
              • value
              • + *
              • offset
              • + *
              + * If type is unspecified, it defaults to string Parameter values:
              + * + *
                + *
              • operation:
              • + *
                  + *
                • "=" (set -- default)
                • + *
                • "-" (dec)
                • + *
                • "+" (inc)
                • + *
                • type:
                • + *
                    + *
                  • "int"
                  • + *
                  • "date"
                  • + *
                  • "string"
                  • + *
                  + * + *
                + * + *
              • value:
              • + *
                  + *
                • holds the default value, if the property was not found in property + * file
                • + *
                • "now" In case of type "date", the value "now" will be replaced by + * the current date/time and used even if a valid date was found in the + * property file.
                • + *
                + * + *
              • offset:
                + * valid for "-" or "+", the offset (default set to 1) will be added or + * subtracted from "int" or "date" type value.
              • + *
              + * String property types can only use the "=" operation. Date property types can + * only use the "never" or "now" operations. Int property types can only use the + * "=", "-" or "+" operations.

              + * + * The message property is used for the property file header, with "\\" being a + * newline delimiter charater. + * + * @author Thomas Christen chr@active.ch + * @author Jeremy Mawson + * jem@loftinspace.com.au + */ +public class PropertyFile extends Task +{ + + /* + * ======================================================================== + * + * Static variables. + */ + private final static String NEWLINE = System.getProperty( "line.separator" ); + + private Vector entries = new Vector(); + + /* + * ======================================================================== + * + * Instance variables. + */ + // Use this to prepend a message to the properties file + private String m_comment; + + private Properties m_properties; + private File m_propertyfile; + + public void setComment( String hdr ) + { + m_comment = hdr; + } + + public void setFile( File file ) + { + m_propertyfile = file; + } + + public Entry createEntry() + { + Entry e = new Entry(); + entries.addElement( e ); + return e; + } + + /* + * ======================================================================== + * + * Constructors + */ + /* + * ======================================================================== + * + * Methods + */ + public void execute() + throws BuildException + { + checkParameters(); + readFile(); + executeOperation(); + writeFile(); + } + + /* + * Returns whether the given parameter has been defined. + */ + private boolean checkParam( String param ) + { + return !( ( param == null ) || ( param.equals( "null" ) ) ); + } + + private boolean checkParam( File param ) + { + return !( param == null ); + } + + private void checkParameters() + throws BuildException + { + if( !checkParam( m_propertyfile ) ) + { + throw new BuildException( "file token must not be null.", location ); + } + } + + private void executeOperation() + throws BuildException + { + for( Enumeration e = entries.elements(); e.hasMoreElements(); ) + { + Entry entry = ( Entry )e.nextElement(); + entry.executeOn( m_properties ); + } + } + + private void readFile() + throws BuildException + { + // Create the PropertyFile + m_properties = new Properties(); + try + { + if( m_propertyfile.exists() ) + { + log( "Updating property file: " + m_propertyfile.getAbsolutePath() ); + FileInputStream fis = null; + try + { + fis = new FileInputStream( m_propertyfile ); + BufferedInputStream bis = new BufferedInputStream( fis ); + m_properties.load( bis ); + } + finally + { + if( fis != null ) + { + fis.close(); + } + } + } + else + { + log( "Creating new property file: " + + m_propertyfile.getAbsolutePath() ); + FileOutputStream out = null; + try + { + out = new FileOutputStream( m_propertyfile.getAbsolutePath() ); + out.flush(); + } + finally + { + if( out != null ) + { + out.close(); + } + } + } + } + catch( IOException ioe ) + { + throw new BuildException( ioe.toString() ); + } + } + + private void writeFile() + throws BuildException + { + BufferedOutputStream bos = null; + try + { + bos = new BufferedOutputStream( new FileOutputStream( m_propertyfile ) ); + + // Properties.store is not available in JDK 1.1 + Method m = + Properties.class.getMethod( "store", + new Class[]{ + OutputStream.class, + String.class} + ); + m.invoke( m_properties, new Object[]{bos, m_comment} ); + + } + catch( NoSuchMethodException nsme ) + { + m_properties.save( bos, m_comment ); + } + catch( InvocationTargetException ite ) + { + Throwable t = ite.getTargetException(); + throw new BuildException( t ); + } + catch( IllegalAccessException iae ) + { + // impossible + throw new BuildException( iae ); + } + catch( IOException ioe ) + { + throw new BuildException( ioe ); + } + finally + { + if( bos != null ) + { + try + { + bos.close(); + } + catch( IOException ioex ) + {} + } + } + } + + /** + * Instance of this class represents nested elements of a task propertyfile. + * + * @author RT + */ + public static class Entry + { + + final static String NOW_VALUE_ = "now"; + final static String NULL_VALUE_ = "never"; + + private final static int DEFAULT_INT_VALUE = 1; + private final static GregorianCalendar + DEFAULT_DATE_VALUE = new GregorianCalendar(); + + private String m_key = null; + private int m_type = Type.STRING_TYPE; + private int m_operation = Operation.EQUALS_OPER; + private String m_value = ""; + private String m_default = null; + private String m_pattern = null; + + public void setDefault( String value ) + { + this.m_default = value; + } + + public void setKey( String value ) + { + this.m_key = value; + } + + public void setOperation( Operation value ) + { + int newOperation = Operation.toOperation( value.getValue() ); + if( newOperation == Operation.NOW_VALUE ) + { + this.m_operation = Operation.EQUALS_OPER; + this.setValue( this.NOW_VALUE_ ); + } + else if( newOperation == Operation.NULL_VALUE ) + { + this.m_operation = Operation.EQUALS_OPER; + this.setValue( this.NULL_VALUE_ ); + } + else + { + this.m_operation = newOperation; + } + } + + public void setPattern( String value ) + { + this.m_pattern = value; + } + + public void setType( Type value ) + { + this.m_type = Type.toType( value.getValue() ); + } + + public void setValue( String value ) + { + this.m_value = value; + } + + protected void executeOn( Properties props ) + throws BuildException + { + checkParameters(); + + // m_type may be null because it wasn't set + try + { + if( m_type == Type.INTEGER_TYPE ) + { + executeInteger( ( String )props.get( m_key ) ); + } + else if( m_type == Type.DATE_TYPE ) + { + executeDate( ( String )props.get( m_key ) ); + } + else if( m_type == Type.STRING_TYPE ) + { + executeString( ( String )props.get( m_key ) ); + } + else + { + throw new BuildException( "Unknown operation type: " + m_type + "" ); + } + } + catch( NullPointerException npe ) + { + // Default to string type + // which means do nothing + npe.printStackTrace(); + } + // Insert as a string by default + props.put( m_key, m_value ); + + } + + /** + * Check if parameter combinations can be supported + * + * @exception BuildException Description of Exception + */ + private void checkParameters() + throws BuildException + { + if( m_type == Type.STRING_TYPE && + m_operation == Operation.DECREMENT_OPER ) + { + throw new BuildException( "- is not suported for string properties (key:" + m_key + ")" ); + } + if( m_value == null && m_default == null ) + { + throw new BuildException( "value and/or default must be specified (key:" + m_key + ")" ); + } + if( m_key == null ) + { + throw new BuildException( "key is mandatory" ); + } + if( m_type == Type.STRING_TYPE && + m_pattern != null ) + { + throw new BuildException( "pattern is not suported for string properties (key:" + m_key + ")" ); + } + } + + /** + * Handle operations for type date. + * + * @param oldValue the current value read from the property file or + * null if the key was not contained in + * the property file. + * @exception BuildException Description of Exception + */ + private void executeDate( String oldValue ) + throws BuildException + { + GregorianCalendar value = new GregorianCalendar(); + GregorianCalendar newValue = new GregorianCalendar(); + + if( m_pattern == null ) + m_pattern = "yyyy/MM/dd HH:mm"; + DateFormat fmt = new SimpleDateFormat( m_pattern ); + + // special case + if( m_default != null && + NOW_VALUE_.equals( m_default.toLowerCase() ) && + ( m_operation == Operation.INCREMENT_OPER || + m_operation == Operation.DECREMENT_OPER ) ) + { + oldValue = null; + } + + if( oldValue != null ) + { + try + { + value.setTime( fmt.parse( oldValue ) ); + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + + if( m_value != null ) + { + if( NOW_VALUE_.equals( m_value.toLowerCase() ) ) + { + value.setTime( new Date() ); + } + else if( NULL_VALUE_.equals( m_value.toLowerCase() ) ) + { + value = null; + } + else + { + try + { + value.setTime( fmt.parse( m_value ) ); + } + catch( Exception ex ) + { + // obviously not a date, try a simple int + try + { + int offset = Integer.parseInt( m_value ); + value.clear(); + value.set( Calendar.DAY_OF_YEAR, offset ); + } + catch( Exception ex_ ) + { + value.clear(); + value.set( Calendar.DAY_OF_YEAR, 1 ); + } + } + + } + } + + if( m_default != null && oldValue == null ) + { + if( NOW_VALUE_.equals( m_default.toLowerCase() ) ) + { + value.setTime( new Date() ); + } + else if( NULL_VALUE_.equals( m_default.toLowerCase() ) ) + { + value = null; + } + else + { + try + { + value.setTime( fmt.parse( m_default ) ); + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + } + + if( m_operation == Operation.EQUALS_OPER ) + { + newValue = value; + } + else if( m_operation == Operation.INCREMENT_OPER ) + { + newValue.add( Calendar.SECOND, value.get( Calendar.SECOND ) ); + newValue.add( Calendar.MINUTE, value.get( Calendar.MINUTE ) ); + newValue.add( Calendar.HOUR_OF_DAY, value.get( Calendar.HOUR_OF_DAY ) ); + newValue.add( Calendar.DAY_OF_YEAR, value.get( Calendar.DAY_OF_YEAR ) ); + } + else if( m_operation == Operation.DECREMENT_OPER ) + { + newValue.add( Calendar.SECOND, -1 * value.get( Calendar.SECOND ) ); + newValue.add( Calendar.MINUTE, -1 * value.get( Calendar.MINUTE ) ); + newValue.add( Calendar.HOUR_OF_DAY, -1 * value.get( Calendar.HOUR_OF_DAY ) ); + newValue.add( Calendar.DAY_OF_YEAR, -1 * value.get( Calendar.DAY_OF_YEAR ) ); + } + if( newValue != null ) + { + m_value = fmt.format( newValue.getTime() ); + } + else + { + m_value = ""; + } + } + + + /** + * Handle operations for type int. + * + * @param oldValue the current value read from the property file or + * null if the key was not contained in + * the property file. + * @exception BuildException Description of Exception + */ + private void executeInteger( String oldValue ) + throws BuildException + { + int value = 0; + int newValue = 0; + + DecimalFormat fmt = ( m_pattern != null ) ? new DecimalFormat( m_pattern ) + : new DecimalFormat(); + + if( oldValue != null ) + { + try + { + value = fmt.parse( oldValue ).intValue(); + } + catch( NumberFormatException nfe ) + { + /* + * swollow + */ + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + if( m_value != null ) + { + try + { + value = fmt.parse( m_value ).intValue(); + } + catch( NumberFormatException nfe ) + { + /* + * swollow + */ + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + if( m_default != null && oldValue == null ) + { + try + { + value = fmt.parse( m_default ).intValue(); + } + catch( NumberFormatException nfe ) + { + /* + * swollow + */ + } + catch( ParseException pe ) + { + /* + * swollow + */ + } + } + + if( m_operation == Operation.EQUALS_OPER ) + { + newValue = value; + } + else if( m_operation == Operation.INCREMENT_OPER ) + { + newValue = ++value; + } + else if( m_operation == Operation.DECREMENT_OPER ) + { + newValue = --value; + } + m_value = fmt.format( newValue ); + } + + /** + * Handle operations for type string. + * + * @param oldValue the current value read from the property file or + * null if the key was not contained in + * the property file. + * @exception BuildException Description of Exception + */ + private void executeString( String oldValue ) + throws BuildException + { + String value = ""; + String newValue = ""; + + // the order of events is, of course, very important here + // default initially to the old value + if( oldValue != null ) + { + value = oldValue; + } + // but if a value is specified, use it + if( m_value != null ) + { + value = m_value; + } + // even if value is specified, ignore it and set to the default + // value if it is specified and there is no previous value + if( m_default != null && oldValue == null ) + { + value = m_default; + } + + if( m_operation == Operation.EQUALS_OPER ) + { + newValue = value; + } + else if( m_operation == Operation.INCREMENT_OPER ) + { + newValue += value; + } + m_value = newValue; + } + + /** + * Enumerated attribute with the values "+", "-", "=", "now" and + * "never". + * + * @author RT + */ + public static class Operation extends EnumeratedAttribute + { + + // Property type operations + public final static int INCREMENT_OPER = 0; + public final static int DECREMENT_OPER = 1; + public final static int EQUALS_OPER = 2; + + // Special values + public final static int NOW_VALUE = 3; + public final static int NULL_VALUE = 4; + + public static int toOperation( String oper ) + { + if( "+".equals( oper ) ) + { + return INCREMENT_OPER; + } + else if( "-".equals( oper ) ) + { + return DECREMENT_OPER; + } + else if( NOW_VALUE_.equals( oper ) ) + { + return NOW_VALUE; + } + else if( NULL_VALUE_.equals( oper ) ) + { + return NULL_VALUE; + } + return EQUALS_OPER; + } + + public String[] getValues() + { + return new String[]{"+", "-", "=", NOW_VALUE_, NULL_VALUE_}; + } + } + + /** + * Enumerated attribute with the values "int", "date" and "string". + * + * @author RT + */ + public static class Type extends EnumeratedAttribute + { + + // Property types + public final static int INTEGER_TYPE = 0; + public final static int DATE_TYPE = 1; + public final static int STRING_TYPE = 2; + + public static int toType( String type ) + { + if( "int".equals( type ) ) + { + return INTEGER_TYPE; + } + else if( "date".equals( type ) ) + { + return DATE_TYPE; + } + return STRING_TYPE; + } + + public String[] getValues() + { + return new String[]{"int", "date", "string"}; + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java new file mode 100644 index 000000000..1aa5ab674 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/RenameExtensions.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.Move; +import org.apache.tools.ant.types.Mapper; + +/** + * @author dIon Gillard + * dion@multitask.com.au + * @author Stefan Bodewig + * @version 1.2 + */ +public class RenameExtensions extends MatchingTask +{ + + private String fromExtension = ""; + private String toExtension = ""; + private boolean replace = false; + + private Mapper.MapperType globType; + private File srcDir; + + + /** + * Creates new RenameExtensions + */ + public RenameExtensions() + { + super(); + globType = new Mapper.MapperType(); + globType.setValue( "glob" ); + } + + /** + * store fromExtension * + * + * @param from The new FromExtension value + */ + public void setFromExtension( String from ) + { + fromExtension = from; + } + + /** + * store replace attribute - this determines whether the target file should + * be overwritten if present + * + * @param replace The new Replace value + */ + public void setReplace( boolean replace ) + { + this.replace = replace; + } + + /** + * Set the source dir to find the files to be renamed. + * + * @param srcDir The new SrcDir value + */ + public void setSrcDir( File srcDir ) + { + this.srcDir = srcDir; + } + + /** + * store toExtension * + * + * @param to The new ToExtension value + */ + public void setToExtension( String to ) + { + toExtension = to; + } + + /** + * Executes the task, i.e. does the actual compiler call + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + + // first off, make sure that we've got a from and to extension + if( fromExtension == null || toExtension == null || srcDir == null ) + { + throw new BuildException( "srcDir, fromExtension and toExtension " + + "attributes must be set!" ); + } + + log( "DEPRECATED - The renameext task is deprecated. Use move instead.", + Project.MSG_WARN ); + log( "Replace this with:", Project.MSG_INFO ); + log( "", + Project.MSG_INFO ); + log( " ", Project.MSG_INFO ); + log( " ", Project.MSG_INFO ); + log( "", Project.MSG_INFO ); + log( "using the same patterns on as you\'ve used here", + Project.MSG_INFO ); + + Move move = ( Move )project.createTask( "move" ); + move.setOwningTarget( target ); + move.setTaskName( getTaskName() ); + move.setLocation( getLocation() ); + move.setTodir( srcDir ); + move.setOverwrite( replace ); + + fileset.setDir( srcDir ); + move.addFileset( fileset ); + + Mapper me = move.createMapper(); + me.setType( globType ); + me.setFrom( "*" + fromExtension ); + me.setTo( "*" + toExtension ); + + move.execute(); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java new file mode 100644 index 000000000..ece6ed56e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ReplaceRegExp.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.PrintWriter; +import java.util.Random; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.RegularExpression; +import org.apache.tools.ant.types.Substitution; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.regexp.Regexp; + +/** + *

              + * Task to do regular expression string replacements in a text
              + * file.  The input file(s) must be able to be properly processed by
              + * a Reader instance.  That is, they must be text only, no binary.
              + *
              + * The syntax of the regular expression depends on the implemtation that
              + * you choose to use. The system property ant.regexp.regexpimpl
              + * will be the classname of the implementation that will be used (the default is
              + * org.apache.tools.ant.util.regexp.JakartaOroRegexp and requires
              + * the Jakarta Oro Package). 
              + * For jdk  <= 1.3, there are two available implementations:
              + *   org.apache.tools.ant.util.regexp.JakartaOroRegexp (the default)
              + *        Requires  the jakarta-oro package
              + *
              + *   org.apache.tools.ant.util.regexp.JakartaRegexpRegexp
              + *        Requires the jakarta-regexp package
              + *
              + * For jdk >= 1.4 an additional implementation is available:
              + *   org.apache.tools.ant.util.regexp.Jdk14RegexpRegexp
              + *        Requires the jdk 1.4 built in regular expression package.
              + * 
              Usage: Call Syntax: <replaceregexp file="file" match="pattern" + * replace="pattern" flags="options"? byline="true|false"? > + * regularexpression? substitution? fileset* </replaceregexp> NOTE: You + * must have either the file attribute specified, or at least one fileset + * subelement to operation on. You may not have the file attribute specified if + * you nest fileset elements inside this task. Also, you cannot specify both + * match and a regular expression subelement at the same time, nor can you + * specify the replace attribute and the substitution subelement at the same + * time. Attributes: file --> A single file to operation on (mutually + * exclusive with the fileset subelements) match --> The Regular expression + * to match replace --> The Expression replacement string flags --> The + * options to give to the replacement g = Substitute all occurrences. default is + * to replace only the first one i = Case insensitive match byline --> Should + * this file be processed a single line at a time (default is false) "true" + * indicates to perform replacement on a line by line basis "false" indicates to + * perform replacement on the whole file at once. Example: The following call + * could be used to replace an old property name in a ".properties" file with a + * new name. In the replace attribute, you can refer to any part of the match + * expression in parenthesis using backslash followed by a number like '\1'. + * <replaceregexp file="test.properties" match="MyProperty=(.*)" + * replace="NewProperty=\1" byline="true" />
              + * + * @author Matthew Inger + */ +public class ReplaceRegExp extends Task +{ + + private FileUtils fileUtils = FileUtils.newFileUtils(); + private boolean byline; + + private File file; + private Vector filesets; + private String flags;// Keep jdk 1.1 compliant so others can use this + private RegularExpression regex; + private Substitution subs; + + /** + * Default Constructor + */ + public ReplaceRegExp() + { + super(); + this.file = null; + this.filesets = new Vector(); + this.flags = ""; + this.byline = false; + + this.regex = null; + this.subs = null; + } + + public void setByLine( String byline ) + { + Boolean res = Boolean.valueOf( byline ); + if( res == null ) + res = Boolean.FALSE; + this.byline = res.booleanValue(); + } + + public void setFile( File file ) + { + this.file = file; + } + + public void setFlags( String flags ) + { + this.flags = flags; + } + + public void setMatch( String match ) + { + if( regex != null ) + throw new BuildException( "Only one regular expression is allowed" ); + + regex = new RegularExpression(); + regex.setPattern( match ); + } + + public void setReplace( String replace ) + { + if( subs != null ) + throw new BuildException( "Only one substitution expression is allowed" ); + + subs = new Substitution(); + subs.setExpression( replace ); + } + + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + public RegularExpression createRegularExpression() + { + if( regex != null ) + throw new BuildException( "Only one regular expression is allowed." ); + + regex = new RegularExpression(); + return regex; + } + + public Substitution createSubstitution() + { + if( subs != null ) + throw new BuildException( "Only one substitution expression is allowed" ); + + subs = new Substitution(); + return subs; + } + + public void execute() + throws BuildException + { + if( regex == null ) + throw new BuildException( "No expression to match." ); + if( subs == null ) + throw new BuildException( "Nothing to replace expression with." ); + + if( file != null && filesets.size() > 0 ) + throw new BuildException( "You cannot supply the 'file' attribute and filesets at the same time." ); + + int options = 0; + + if( flags.indexOf( 'g' ) != -1 ) + options |= Regexp.REPLACE_ALL; + + if( flags.indexOf( 'i' ) != -1 ) + options |= Regexp.MATCH_CASE_INSENSITIVE; + + if( flags.indexOf( 'm' ) != -1 ) + options |= Regexp.MATCH_MULTILINE; + + if( flags.indexOf( 's' ) != -1 ) + options |= Regexp.MATCH_SINGLELINE; + + if( file != null && file.exists() ) + { + try + { + doReplace( file, options ); + } + catch( IOException e ) + { + log( "An error occurred processing file: '" + file.getAbsolutePath() + "': " + e.toString(), + Project.MSG_ERR ); + } + } + else if( file != null ) + { + log( "The following file is missing: '" + file.getAbsolutePath() + "'", + Project.MSG_ERR ); + } + + int sz = filesets.size(); + for( int i = 0; i < sz; i++ ) + { + FileSet fs = ( FileSet )( filesets.elementAt( i ) ); + DirectoryScanner ds = fs.getDirectoryScanner( getProject() ); + + String files[] = ds.getIncludedFiles(); + for( int j = 0; j < files.length; j++ ) + { + File f = new File( files[j] ); + if( f.exists() ) + { + try + { + doReplace( f, options ); + } + catch( Exception e ) + { + log( "An error occurred processing file: '" + f.getAbsolutePath() + "': " + e.toString(), + Project.MSG_ERR ); + } + } + else + { + log( "The following file is missing: '" + file.getAbsolutePath() + "'", + Project.MSG_ERR ); + } + } + } + } + + + protected String doReplace( RegularExpression r, + Substitution s, + String input, + int options ) + { + String res = input; + Regexp regexp = r.getRegexp( project ); + + if( regexp.matches( input, options ) ) + { + res = regexp.substitute( input, s.getExpression( project ), options ); + } + + return res; + } + + /** + * Perform the replace on the entire file + * + * @param f Description of Parameter + * @param options Description of Parameter + * @exception IOException Description of Exception + */ + protected void doReplace( File f, int options ) + throws IOException + { + File parentDir = new File( new File( f.getAbsolutePath() ).getParent() ); + File temp = fileUtils.createTempFile( "replace", ".txt", parentDir ); + + FileReader r = null; + FileWriter w = null; + + try + { + r = new FileReader( f ); + w = new FileWriter( temp ); + + BufferedReader br = new BufferedReader( r ); + BufferedWriter bw = new BufferedWriter( w ); + PrintWriter pw = new PrintWriter( bw ); + + boolean changes = false; + + log( "Replacing pattern '" + regex.getPattern( project ) + "' with '" + subs.getExpression( project ) + + "' in '" + f.getPath() + "'" + + ( byline ? " by line" : "" ) + + ( flags.length() > 0 ? " with flags: '" + flags + "'" : "" ) + + ".", + Project.MSG_WARN ); + + if( byline ) + { + LineNumberReader lnr = new LineNumberReader( br ); + String line = null; + + while( ( line = lnr.readLine() ) != null ) + { + String res = doReplace( regex, subs, line, options ); + if( !res.equals( line ) ) + changes = true; + + pw.println( res ); + } + pw.flush(); + } + else + { + int flen = ( int )( f.length() ); + char tmpBuf[] = new char[flen]; + int numread = 0; + int totread = 0; + while( numread != -1 && totread < flen ) + { + numread = br.read( tmpBuf, totread, flen ); + totread += numread; + } + + String buf = new String( tmpBuf ); + + String res = doReplace( regex, subs, buf, options ); + if( !res.equals( buf ) ) + changes = true; + + pw.println( res ); + pw.flush(); + } + + r.close(); + r = null; + w.close(); + w = null; + + if( changes ) + { + f.delete(); + temp.renameTo( f ); + } + else + { + temp.delete(); + } + } + finally + { + try + { + if( r != null ) + r.close(); + } + catch( Exception e ) + {} + ; + + try + { + if( w != null ) + r.close(); + } + catch( Exception e ) + {} + ; + } + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Rpm.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Rpm.java new file mode 100644 index 000000000..3811e701b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Rpm.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.taskdefs.PumpStreamHandler; +import org.apache.tools.ant.types.Commandline; + +/** + * @author lucas@collab.net + */ +public class Rpm extends Task +{ + + /** + * the rpm command to use + */ + private String command = "-bb"; + + /** + * clean BUILD directory + */ + private boolean cleanBuildDir = false; + + /** + * remove spec file + */ + private boolean removeSpec = false; + + /** + * remove sources + */ + private boolean removeSource = false; + + /** + * the file to direct standard error from the command + */ + private File error; + + /** + * the file to direct standard output from the command + */ + private File output; + + /** + * the spec file + */ + private String specFile; + + /** + * the rpm top dir + */ + private File topDir; + + public void setCleanBuildDir( boolean cbd ) + { + cleanBuildDir = cbd; + } + + public void setCommand( String c ) + { + this.command = c; + } + + public void setError( File error ) + { + this.error = error; + } + + public void setOutput( File output ) + { + this.output = output; + } + + public void setRemoveSource( boolean rs ) + { + removeSource = rs; + } + + public void setRemoveSpec( boolean rs ) + { + removeSpec = rs; + } + + public void setSpecFile( String sf ) + { + if( ( sf == null ) || ( sf.trim().equals( "" ) ) ) + { + throw new BuildException( "You must specify a spec file", location ); + } + this.specFile = sf; + } + + public void setTopDir( File td ) + { + this.topDir = td; + } + + public void execute() + throws BuildException + { + + Commandline toExecute = new Commandline(); + + toExecute.setExecutable( "rpm" ); + if( topDir != null ) + { + toExecute.createArgument().setValue( "--define" ); + toExecute.createArgument().setValue( "_topdir" + topDir ); + } + + toExecute.createArgument().setLine( command ); + + if( cleanBuildDir ) + { + toExecute.createArgument().setValue( "--clean" ); + } + if( removeSpec ) + { + toExecute.createArgument().setValue( "--rmspec" ); + } + if( removeSource ) + { + toExecute.createArgument().setValue( "--rmsource" ); + } + + toExecute.createArgument().setValue( "SPECS/" + specFile ); + + ExecuteStreamHandler streamhandler = null; + OutputStream outputstream = null; + OutputStream errorstream = null; + if( error == null && output == null ) + { + streamhandler = new LogStreamHandler( this, Project.MSG_INFO, + Project.MSG_WARN ); + } + else + { + if( output != null ) + { + try + { + outputstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( output ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + outputstream = new LogOutputStream( this, Project.MSG_INFO ); + } + if( error != null ) + { + try + { + errorstream = new PrintStream( new BufferedOutputStream( new FileOutputStream( error ) ) ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + } + else + { + errorstream = new LogOutputStream( this, Project.MSG_WARN ); + } + streamhandler = new PumpStreamHandler( outputstream, errorstream ); + } + + Execute exe = new Execute( streamhandler, null ); + + exe.setAntRun( project ); + if( topDir == null ) + topDir = project.getBaseDir(); + exe.setWorkingDirectory( topDir ); + + exe.setCommandline( toExecute.getCommandline() ); + try + { + exe.execute(); + log( "Building the RPM based on the " + specFile + " file" ); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + finally + { + if( output != null ) + { + try + { + outputstream.close(); + } + catch( IOException e ) + {} + } + if( error != null ) + { + try + { + errorstream.close(); + } + catch( IOException e ) + {} + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Script.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Script.java new file mode 100644 index 000000000..43a545ec1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Script.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import com.ibm.bsf.BSFException; +import com.ibm.bsf.BSFManager; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Execute a script + * + * @author Sam Ruby rubys@us.ibm.com + */ +public class Script extends Task +{ + private String script = ""; + private Hashtable beans = new Hashtable(); + private String language; + + /** + * Defines the language (required). + * + * @param language The new Language value + */ + public void setLanguage( String language ) + { + this.language = language; + } + + /** + * Load the script from an external file + * + * @param fileName The new Src value + */ + public void setSrc( String fileName ) + { + File file = new File( fileName ); + if( !file.exists() ) + throw new BuildException( "file " + fileName + " not found." ); + + int count = ( int )file.length(); + byte data[] = new byte[count]; + + try + { + FileInputStream inStream = new FileInputStream( file ); + inStream.read( data ); + inStream.close(); + } + catch( IOException e ) + { + throw new BuildException( e ); + } + + script += new String( data ); + } + + /** + * Defines the script. + * + * @param text The feature to be added to the Text attribute + */ + public void addText( String text ) + { + this.script += text; + } + + /** + * Do the work. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + try + { + addBeans( project.getProperties() ); + addBeans( project.getUserProperties() ); + addBeans( project.getTargets() ); + addBeans( project.getReferences() ); + + beans.put( "project", getProject() ); + + beans.put( "self", this ); + + BSFManager manager = new BSFManager(); + + for( Enumeration e = beans.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + Object value = beans.get( key ); + manager.declareBean( key, value, value.getClass() ); + } + + // execute the script + manager.exec( language, "", 0, 0, script ); + } + catch( BSFException be ) + { + Throwable t = be; + Throwable te = be.getTargetException(); + if( te != null ) + { + if( te instanceof BuildException ) + { + throw ( BuildException )te; + } + else + { + t = te; + } + } + throw new BuildException( t ); + } + } + + /** + * Add a list of named objects to the list to be exported to the script + * + * @param dictionary The feature to be added to the Beans attribute + */ + private void addBeans( Hashtable dictionary ) + { + for( Enumeration e = dictionary.keys(); e.hasMoreElements(); ) + { + String key = ( String )e.nextElement(); + + boolean isValid = key.length() > 0 && + Character.isJavaIdentifierStart( key.charAt( 0 ) ); + + for( int i = 1; isValid && i < key.length(); i++ ) + isValid = Character.isJavaIdentifierPart( key.charAt( i ) ); + + if( isValid ) + beans.put( key, dictionary.get( key ) ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/StyleBook.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/StyleBook.java new file mode 100644 index 000000000..0f37fc687 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/StyleBook.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Java; + +/** + * Basic task for apache stylebook. + * + * @author Peter Donald + * @author Marcus + * Börger + */ +public class StyleBook + extends Java +{ + protected File m_book; + protected String m_loaderConfig; + protected File m_skinDirectory; + protected File m_targetDirectory; + + public StyleBook() + { + setClassname( "org.apache.stylebook.StyleBook" ); + setFork( true ); + setFailonerror( true ); + } + + public void setBook( final File book ) + { + m_book = book; + } + + public void setLoaderConfig( final String loaderConfig ) + { + m_loaderConfig = loaderConfig; + } + + public void setSkinDirectory( final File skinDirectory ) + { + m_skinDirectory = skinDirectory; + } + + public void setTargetDirectory( final File targetDirectory ) + { + m_targetDirectory = targetDirectory; + } + + public void execute() + throws BuildException + { + + if( null == m_targetDirectory ) + { + throw new BuildException( "TargetDirectory attribute not set." ); + } + + if( null == m_skinDirectory ) + { + throw new BuildException( "SkinDirectory attribute not set." ); + } + + if( null == m_book ) + { + throw new BuildException( "book attribute not set." ); + } + + createArg().setValue( "targetDirectory=" + m_targetDirectory ); + createArg().setValue( m_book.toString() ); + createArg().setValue( m_skinDirectory.toString() ); + if( null != m_loaderConfig ) + { + createArg().setValue( "loaderConfig=" + m_loaderConfig ); + } + + super.execute(); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Test.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Test.java new file mode 100644 index 000000000..3f248fdc4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/Test.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.Java; + +/** + * @author Peter Donald + */ +public class Test + extends Java +{ + + protected Vector m_tests = new Vector(); + + public Test() + { + setClassname( "org.apache.testlet.engine.TextTestEngine" ); + } + + public void setForceShowTrace( final boolean forceShowTrace ) + { + createArg().setValue( "-f=" + forceShowTrace ); + } + + public void setShowBanner( final String showBanner ) + { + createArg().setValue( "-b=" + showBanner ); + } + + public void setShowSuccess( final boolean showSuccess ) + { + createArg().setValue( "-s=" + showSuccess ); + } + + public void setShowTrace( final boolean showTrace ) + { + createArg().setValue( "-t=" + showTrace ); + } + + public TestletEntry createTestlet() + { + final TestletEntry entry = new TestletEntry(); + m_tests.addElement( entry ); + return entry; + } + + public void execute() + throws BuildException + { + + final int size = m_tests.size(); + + for( int i = 0; i < size; i++ ) + { + createArg().setValue( m_tests.elementAt( i ).toString() ); + } + + super.execute(); + } + + protected final static class TestletEntry + { + + protected String m_testname = ""; + + public void addText( final String testname ) + { + m_testname += testname; + } + + public String toString() + { + return m_testname; + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java new file mode 100644 index 000000000..e608bfcd2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/TraXLiaison.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import javax.xml.transform.ErrorListener; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import org.apache.tools.ant.taskdefs.XSLTLiaison; +import org.apache.tools.ant.taskdefs.XSLTLogger; +import org.apache.tools.ant.taskdefs.XSLTLoggerAware; + +/** + * Concrete liaison for XSLT processor implementing TraX. (ie JAXP 1.1) + * + * @author Sam Ruby + * @author Davanum Srinivas + * @author Stephane Bailliez + */ +public class TraXLiaison implements XSLTLiaison, ErrorListener, XSLTLoggerAware +{ + + /** + * The trax TransformerFactory + */ + private TransformerFactory tfactory = null; + + /** + * stylesheet stream, close it asap + */ + private FileInputStream xslStream = null; + + /** + * Stylesheet template + */ + private Templates templates = null; + + /** + * transformer + */ + private Transformer transformer = null; + + private XSLTLogger logger; + + public TraXLiaison() + throws Exception + { + tfactory = TransformerFactory.newInstance(); + tfactory.setErrorListener( this ); + } + + public void setLogger( XSLTLogger l ) + { + logger = l; + } + + public void setOutputtype( String type ) + throws Exception + { + transformer.setOutputProperty( OutputKeys.METHOD, type ); + } + +//------------------- IMPORTANT + // 1) Don't use the StreamSource(File) ctor. It won't work with + // xalan prior to 2.2 because of systemid bugs. + + // 2) Use a stream so that you can close it yourself quickly + // and avoid keeping the handle until the object is garbaged. + // (always keep control), otherwise you won't be able to delete + // the file quickly on windows. + + // 3) Always set the systemid to the source for imports, includes... + // in xsl and xml... + + public void setStylesheet( File stylesheet ) + throws Exception + { + xslStream = new FileInputStream( stylesheet ); + StreamSource src = new StreamSource( xslStream ); + src.setSystemId( getSystemId( stylesheet ) ); + templates = tfactory.newTemplates( src ); + transformer = templates.newTransformer(); + transformer.setErrorListener( this ); + } + + public void addParam( String name, String value ) + { + transformer.setParameter( name, value ); + } + + public void error( TransformerException e ) + { + logError( e, "Error" ); + } + + public void fatalError( TransformerException e ) + { + logError( e, "Fatal Error" ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileInputStream fis = null; + FileOutputStream fos = null; + try + { + fis = new FileInputStream( infile ); + fos = new FileOutputStream( outfile ); + StreamSource src = new StreamSource( fis ); + src.setSystemId( getSystemId( infile ) ); + StreamResult res = new StreamResult( fos ); + // not sure what could be the need of this... + res.setSystemId( getSystemId( outfile ) ); + + transformer.transform( src, res ); + } + finally + { + // make sure to close all handles, otherwise the garbage + // collector will close them...whenever possible and + // Windows may complain about not being able to delete files. + try + { + if( xslStream != null ) + { + xslStream.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fis != null ) + { + fis.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fos != null ) + { + fos.close(); + } + } + catch( IOException ignored ) + {} + } + } + + public void warning( TransformerException e ) + { + logError( e, "Warning" ); + } + + // make sure that the systemid is made of '/' and not '\' otherwise + // crimson will complain that it cannot resolve relative entities + // because it grabs the base uri via lastIndexOf('/') without + // making sure it is really a /'ed path + protected String getSystemId( File file ) + { + String path = file.getAbsolutePath(); + path = path.replace( '\\', '/' ); + return FILE_PROTOCOL_PREFIX + path; + } + + private void logError( TransformerException e, String type ) + { + StringBuffer msg = new StringBuffer(); + if( e.getLocator() != null ) + { + if( e.getLocator().getSystemId() != null ) + { + String url = e.getLocator().getSystemId(); + if( url.startsWith( "file:///" ) ) + url = url.substring( 8 ); + msg.append( url ); + } + else + { + msg.append( "Unknown file" ); + } + if( e.getLocator().getLineNumber() != -1 ) + { + msg.append( ":" + e.getLocator().getLineNumber() ); + if( e.getLocator().getColumnNumber() != -1 ) + { + msg.append( ":" + e.getLocator().getColumnNumber() ); + } + } + } + msg.append( ": " + type + "! " ); + msg.append( e.getMessage() ); + if( e.getCause() != null ) + { + msg.append( " Cause: " + e.getCause() ); + } + + logger.log( msg.toString() ); + } + +}//-- TraXLiaison diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java new file mode 100644 index 000000000..02abefc30 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XMLValidateTask.java @@ -0,0 +1,648 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Parser; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.ParserAdapter; + +/** + * The XMLValidateTask checks that an XML document is valid, with a + * SAX validating parser. + * + * @author Raphael Pierquin + * raphael.pierquin@agisphere.com + */ +public class XMLValidateTask extends Task +{ + + /** + * The default implementation parser classname used by the task to process + * validation. + */ + // The crimson implementation is shipped with ant. + public static String DEFAULT_XML_READER_CLASSNAME = "org.apache.crimson.parser.XMLReaderImpl"; + + protected static String INIT_FAILED_MSG = "Could not start xml validation: "; + + // ant task properties + // defaults + protected boolean failOnError = true; + protected boolean warn = true; + protected boolean lenient = false; + protected String readerClassName = DEFAULT_XML_READER_CLASSNAME; + + protected File file = null;// file to be validated + protected Vector filesets = new Vector(); + + /** + * the parser is viewed as a SAX2 XMLReader. If a SAX1 parser is specified, + * it's wrapped in an adapter that make it behave as a XMLReader. a more + * 'standard' way of doing this would be to use the JAXP1.1 SAXParser + * interface. + */ + protected XMLReader xmlReader = null;// XMLReader used to validation process + protected ValidatorErrorHandler errorHandler + = new ValidatorErrorHandler();// to report sax parsing errors + protected Hashtable features = new Hashtable(); + + /** + * The list of configured DTD locations + */ + public Vector dtdLocations = new Vector();// sets of file to be validated + protected Path classpath; + + /** + * Specify the class name of the SAX parser to be used. (optional) + * + * @param className should be an implementation of SAX2 org.xml.sax.XMLReader + * or SAX2 org.xml.sax.Parser.

              + * + * if className is an implementation of org.xml.sax.Parser + * , {@link #setLenient(boolean)}, will be ignored.

              + * + * if not set, the default {@link #DEFAULT_XML_READER_CLASSNAME} will + * be used. + * @see org.xml.sax.XMLReader + * @see org.xml.sax.Parser + */ + public void setClassName( String className ) + { + + readerClassName = className; + } + + + /** + * Specify the classpath to be searched to load the parser (optional) + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + /** + * @param r The new ClasspathRef value + * @see #setClasspath + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * Specify how parser error are to be handled.

              + * + * If set to true (default), throw a buildException if the + * parser yields an error. + * + * @param fail The new FailOnError value + */ + public void setFailOnError( boolean fail ) + { + + failOnError = fail; + } + + /** + * specifify the file to be checked + * + * @param file The new File value + */ + public void setFile( File file ) + { + this.file = file; + } + + /** + * Specify whether the parser should be validating. Default is true + * .

              + * + * If set to false, the validation will fail only if the parsed document is + * not well formed XML.

              + * + * this option is ignored if the specified class with {@link + * #setClassName(String)} is not a SAX2 XMLReader. + * + * @param bool The new Lenient value + */ + public void setLenient( boolean bool ) + { + + lenient = bool; + } + + /** + * Specify how parser error are to be handled.

              + * + * If set to true + * + *(default), log a warn message for each SAX warn event. + * + * @param bool The new Warn value + */ + public void setWarn( boolean bool ) + { + + warn = bool; + } + + /** + * specifify a set of file to be checked + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * @return Description of the Returned Value + * @see #setClasspath + */ + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + /** + * Create a DTD location record. This stores the location of a DTD. The DTD + * is identified by its public Id. The location may either be a file + * location or a resource location. + * + * @return Description of the Returned Value + */ + public DTDLocation createDTD() + { + DTDLocation dtdLocation = new DTDLocation(); + dtdLocations.addElement( dtdLocation ); + + return dtdLocation; + } + + public void execute() + throws BuildException + { + + int fileProcessed = 0; + if( file == null && ( filesets.size() == 0 ) ) + { + throw new BuildException( "Specify at least one source - a file or a fileset." ); + } + + initValidator(); + + if( file != null ) + { + if( file.exists() && file.canRead() && file.isFile() ) + { + doValidate( file ); + fileProcessed++; + } + else + { + String errorMsg = "File " + file + " cannot be read"; + if( failOnError ) + throw new BuildException( errorMsg ); + else + log( errorMsg, Project.MSG_ERR ); + } + } + + for( int i = 0; i < filesets.size(); i++ ) + { + + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] files = ds.getIncludedFiles(); + + for( int j = 0; j < files.length; j++ ) + { + File srcFile = new File( fs.getDir( project ), files[j] ); + doValidate( srcFile ); + fileProcessed++; + } + } + log( fileProcessed + " file(s) have been successfully validated." ); + } + + protected EntityResolver getEntityResolver() + { + LocalResolver resolver = new LocalResolver(); + + for( Enumeration i = dtdLocations.elements(); i.hasMoreElements(); ) + { + DTDLocation location = ( DTDLocation )i.nextElement(); + resolver.registerDTD( location ); + } + return resolver; + } + + /* + * set a feature on the parser. + * TODO: find a way to set any feature from build.xml + */ + private boolean setFeature( String feature, boolean value, boolean warn ) + { + + boolean toReturn = false; + try + { + xmlReader.setFeature( feature, value ); + toReturn = true; + } + catch( SAXNotRecognizedException e ) + { + if( warn ) + log( "Could not set feature '" + + feature + + "' because the parser doesn't recognize it", + Project.MSG_WARN ); + } + catch( SAXNotSupportedException e ) + { + if( warn ) + log( "Could not set feature '" + + feature + + "' because the parser doesn't support it", + Project.MSG_WARN ); + } + return toReturn; + } + + /* + * parse the file + */ + private void doValidate( File afile ) + { + try + { + log( "Validating " + afile.getName() + "... ", Project.MSG_VERBOSE ); + errorHandler.init( afile ); + InputSource is = new InputSource( new FileReader( afile ) ); + String uri = "file:" + afile.getAbsolutePath().replace( '\\', '/' ); + for( int index = uri.indexOf( '#' ); index != -1; + index = uri.indexOf( '#' ) ) + { + uri = uri.substring( 0, index ) + "%23" + uri.substring( index + 1 ); + } + is.setSystemId( uri ); + xmlReader.parse( is ); + } + catch( SAXException ex ) + { + if( failOnError ) + throw new BuildException( "Could not validate document " + afile ); + } + catch( IOException ex ) + { + throw new BuildException( "Could not validate document " + afile, ex ); + } + + if( errorHandler.getFailure() ) + { + if( failOnError ) + throw new BuildException( afile + " is not a valid XML document." ); + else + log( afile + " is not a valid XML document", Project.MSG_ERR ); + } + } + + /** + * init the parser : load the parser class, and set features if necessary + */ + private void initValidator() + { + + try + { + // load the parser class + // with JAXP, we would use a SAXParser factory + Class readerClass = null; + //Class readerImpl = null; + //Class parserImpl = null; + if( classpath != null ) + { + AntClassLoader loader = new AntClassLoader( project, classpath ); +// loader.addSystemPackageRoot("org.xml"); // needed to avoid conflict + readerClass = loader.loadClass( readerClassName ); + AntClassLoader.initializeClass( readerClass ); + } + else + readerClass = Class.forName( readerClassName ); + + // then check it implements XMLReader + if( XMLReader.class.isAssignableFrom( readerClass ) ) + { + + xmlReader = ( XMLReader )readerClass.newInstance(); + log( "Using SAX2 reader " + readerClassName, Project.MSG_VERBOSE ); + } + else + { + + // see if it is a SAX1 Parser + if( Parser.class.isAssignableFrom( readerClass ) ) + { + Parser parser = ( Parser )readerClass.newInstance(); + xmlReader = new ParserAdapter( parser ); + log( "Using SAX1 parser " + readerClassName, Project.MSG_VERBOSE ); + } + else + { + throw new BuildException( INIT_FAILED_MSG + + readerClassName + + " implements nor SAX1 Parser nor SAX2 XMLReader." ); + } + } + } + catch( ClassNotFoundException e ) + { + throw new BuildException( INIT_FAILED_MSG + readerClassName, e ); + } + catch( InstantiationException e ) + { + throw new BuildException( INIT_FAILED_MSG + readerClassName, e ); + } + catch( IllegalAccessException e ) + { + throw new BuildException( INIT_FAILED_MSG + readerClassName, e ); + } + + xmlReader.setEntityResolver( getEntityResolver() ); + xmlReader.setErrorHandler( errorHandler ); + + if( !( xmlReader instanceof ParserAdapter ) ) + { + // turn validation on + if( !lenient ) + { + boolean ok = setFeature( "http://xml.org/sax/features/validation", true, true ); + if( !ok ) + { + throw new BuildException( INIT_FAILED_MSG + + readerClassName + + " doesn't provide validation" ); + } + } + // set other features + Enumeration enum = features.keys(); + while( enum.hasMoreElements() ) + { + String featureId = ( String )enum.nextElement(); + setFeature( featureId, ( ( Boolean )features.get( featureId ) ).booleanValue(), true ); + } + } + } + + public static class DTDLocation + { + private String publicId = null; + private String location = null; + + public void setLocation( String location ) + { + this.location = location; + } + + public void setPublicId( String publicId ) + { + this.publicId = publicId; + } + + public String getLocation() + { + return location; + } + + public String getPublicId() + { + return publicId; + } + } + + /* + * ValidatorErrorHandler role : + *

                + *
              • log SAX parse exceptions, + *
              • remember if an error occured + *
              + */ + protected class ValidatorErrorHandler implements ErrorHandler + { + + protected File currentFile = null; + protected String lastErrorMessage = null; + protected boolean failed = false; + + // did an error happen during last parsing ? + public boolean getFailure() + { + + return failed; + } + + public void error( SAXParseException exception ) + { + failed = true; + doLog( exception, Project.MSG_ERR ); + } + + public void fatalError( SAXParseException exception ) + { + failed = true; + doLog( exception, Project.MSG_ERR ); + } + + public void init( File file ) + { + currentFile = file; + failed = false; + } + + public void warning( SAXParseException exception ) + { + // depending on implementation, XMLReader can yield hips of warning, + // only output then if user explicitely asked for it + if( warn ) + doLog( exception, Project.MSG_WARN ); + } + + private String getMessage( SAXParseException e ) + { + String sysID = e.getSystemId(); + if( sysID != null ) + { + try + { + int line = e.getLineNumber(); + int col = e.getColumnNumber(); + return new URL( sysID ).getFile() + + ( line == -1 ? "" : ( ":" + line + + ( col == -1 ? "" : ( ":" + col ) ) ) ) + + ": " + e.getMessage(); + } + catch( MalformedURLException mfue ) + { + } + } + return e.getMessage(); + } + + private void doLog( SAXParseException e, int logLevel ) + { + + log( getMessage( e ), logLevel ); + } + } + + private class LocalResolver + implements EntityResolver + { + private Hashtable fileDTDs = new Hashtable(); + private Hashtable resourceDTDs = new Hashtable(); + private Hashtable urlDTDs = new Hashtable(); + + public LocalResolver() { } + + public void registerDTD( String publicId, String location ) + { + if( location == null ) + { + return; + } + + File fileDTD = new File( location ); + if( fileDTD.exists() ) + { + if( publicId != null ) + { + fileDTDs.put( publicId, fileDTD ); + log( "Mapped publicId " + publicId + " to file " + fileDTD, Project.MSG_VERBOSE ); + } + return; + } + + if( LocalResolver.this.getClass().getResource( location ) != null ) + { + if( publicId != null ) + { + resourceDTDs.put( publicId, location ); + log( "Mapped publicId " + publicId + " to resource " + location, Project.MSG_VERBOSE ); + } + } + + try + { + if( publicId != null ) + { + URL urldtd = new URL( location ); + urlDTDs.put( publicId, urldtd ); + } + } + catch( java.net.MalformedURLException e ) + { + //ignored + } + } + + public void registerDTD( DTDLocation location ) + { + registerDTD( location.getPublicId(), location.getLocation() ); + } + + public InputSource resolveEntity( String publicId, String systemId ) + throws SAXException + { + File dtdFile = ( File )fileDTDs.get( publicId ); + if( dtdFile != null ) + { + try + { + log( "Resolved " + publicId + " to local file " + dtdFile, Project.MSG_VERBOSE ); + return new InputSource( new FileInputStream( dtdFile ) ); + } + catch( FileNotFoundException ex ) + { + // ignore + } + } + + String dtdResourceName = ( String )resourceDTDs.get( publicId ); + if( dtdResourceName != null ) + { + InputStream is = this.getClass().getResourceAsStream( dtdResourceName ); + if( is != null ) + { + log( "Resolved " + publicId + " to local resource " + dtdResourceName, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + } + + URL dtdUrl = ( URL )urlDTDs.get( publicId ); + if( dtdUrl != null ) + { + try + { + InputStream is = dtdUrl.openStream(); + log( "Resolved " + publicId + " to url " + dtdUrl, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + catch( IOException ioe ) + { + //ignore + } + } + + log( "Could not resolve ( publicId: " + publicId + ", systemId: " + systemId + ") to a local entity", + Project.MSG_INFO ); + + return null; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java new file mode 100644 index 000000000..3798629fc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XalanLiaison.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.XSLTLiaison; +import org.apache.xalan.xslt.XSLTInputSource; +import org.apache.xalan.xslt.XSLTProcessor; +import org.apache.xalan.xslt.XSLTProcessorFactory; +import org.apache.xalan.xslt.XSLTResultTarget; + +/** + * Concrete liaison for Xalan 1.x API. + * + * @author Sam Ruby + * @author Stephane Bailliez + */ +public class XalanLiaison implements XSLTLiaison +{ + + protected XSLTProcessor processor; + protected File stylesheet; + + public XalanLiaison() + throws Exception + { + processor = XSLTProcessorFactory.getProcessor(); + } + + public void setOutputtype( String type ) + throws Exception + { + if( !type.equals( "xml" ) ) + throw new BuildException( "Unsupported output type: " + type ); + } + + public void setStylesheet( File stylesheet ) + throws Exception + { + this.stylesheet = stylesheet; + } + + public void addParam( String name, String value ) + { + processor.setStylesheetParam( name, value ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileInputStream fis = null; + FileOutputStream fos = null; + FileInputStream xslStream = null; + try + { + xslStream = new FileInputStream( stylesheet ); + fis = new FileInputStream( infile ); + fos = new FileOutputStream( outfile ); + // systemid such as file:/// + getAbsolutePath() are considered + // invalid here... + XSLTInputSource xslSheet = new XSLTInputSource( xslStream ); + xslSheet.setSystemId( stylesheet.getAbsolutePath() ); + XSLTInputSource src = new XSLTInputSource( fis ); + src.setSystemId( infile.getAbsolutePath() ); + XSLTResultTarget res = new XSLTResultTarget( fos ); + processor.process( src, xslSheet, res ); + } + finally + { + // make sure to close all handles, otherwise the garbage + // collector will close them...whenever possible and + // Windows may complain about not being able to delete files. + try + { + if( xslStream != null ) + { + xslStream.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fis != null ) + { + fis.close(); + } + } + catch( IOException ignored ) + {} + try + { + if( fos != null ) + { + fos.close(); + } + } + catch( IOException ignored ) + {} + } + } +}//-- XalanLiaison diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java new file mode 100644 index 000000000..7ae71c808 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/XslpLiaison.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional; +import com.kvisco.xsl.XSLProcessor; +import com.kvisco.xsl.XSLReader; +import com.kvisco.xsl.XSLStylesheet; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStreamWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.XSLTLiaison; + + + +/** + * Concrete liaison for XSLP + * + * @author Sam Ruby + * @author Stephane Bailliez + */ +public class XslpLiaison implements XSLTLiaison +{ + + protected XSLProcessor processor; + protected XSLStylesheet xslSheet; + + public XslpLiaison() + { + processor = new XSLProcessor(); + // uh ?! I'm forced to do that otherwise a setProperty crashes with NPE ! + // I don't understand why the property map is static though... + // how can we do multithreading w/ multiple identical parameters ? + processor.getProperty( "dummy-to-init-properties-map" ); + } + + public void setOutputtype( String type ) + throws Exception + { + if( !type.equals( "xml" ) ) + throw new BuildException( "Unsupported output type: " + type ); + } + + public void setStylesheet( File fileName ) + throws Exception + { + XSLReader xslReader = new XSLReader(); + // a file:/// + getAbsolutePath() does not work here + // it is really the pathname + xslSheet = xslReader.read( fileName.getAbsolutePath() ); + } + + public void addParam( String name, String expression ) + { + processor.setProperty( name, expression ); + } + + public void transform( File infile, File outfile ) + throws Exception + { + FileOutputStream fos = new FileOutputStream( outfile ); + // XSLP does not support encoding...we're in hot water. + OutputStreamWriter out = new OutputStreamWriter( fos, "UTF8" ); + processor.process( infile.getAbsolutePath(), xslSheet, out ); + } + +}//-- XSLPLiaison diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java new file mode 100644 index 000000000..974e06ce5 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheck.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + +/** + * Class common to all check commands (checkout, checkin,checkin default task); + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheck extends Continuus +{ + + /** + * -comment flag -- comment to attach to the file + */ + public final static String FLAG_COMMENT = "/comment"; + + /** + * -task flag -- associate checckout task with task + */ + public final static String FLAG_TASK = "/task"; + + private File _file = null; + private String _comment = null; + private String _task = null; + + public CCMCheck() + { + super(); + } + + /** + * Set the value of comment. + * + * @param v Value to assign to comment. + */ + public void setComment( String v ) + { + this._comment = v; + } + + /** + * Set the value of file. + * + * @param v Value to assign to file. + */ + public void setFile( File v ) + { + this._file = v; + } + + /** + * Set the value of task. + * + * @param v Value to assign to task. + */ + public void setTask( String v ) + { + this._task = v; + } + + /** + * Get the value of comment. + * + * @return value of comment. + */ + public String getComment() + { + return _comment; + } + + /** + * Get the value of file. + * + * @return value of file. + */ + public File getFile() + { + return _file; + } + + + /** + * Get the value of task. + * + * @return value of task. + */ + public String getTask() + { + return _task; + } + + + /** + * Executes the task.

              + * + * Builds a command line to execute ccm and then calls Exec's run method to + * execute the command line.

              + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // build the command line from what we got the format is + // ccm co /t .. files + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getCcmCommand() ); + commandLine.createArgument().setValue( getCcmAction() ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + if( getComment() != null ) + { + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( getComment() ); + } + + if( getTask() != null ) + { + cmd.createArgument().setValue( FLAG_TASK ); + cmd.createArgument().setValue( getTask() ); + }// end of if () + + if( getFile() != null ) + { + cmd.createArgument().setValue( _file.getAbsolutePath() ); + }// end of if () + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java new file mode 100644 index 000000000..ed7d1d1f1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckin.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import java.util.Date; + +/** + * Task to perform Checkin command to Continuus + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheckin extends CCMCheck +{ + + public CCMCheckin() + { + super(); + setCcmAction( COMMAND_CHECKIN ); + setComment( "Checkin " + new Date() ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java new file mode 100644 index 000000000..de5fb324d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckinDefault.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; + +/** + * Task to perform Checkin Default task command to Continuus + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheckinDefault extends CCMCheck +{ + + public final static String DEFAULT_TASK = "default"; + + public CCMCheckinDefault() + { + super(); + setCcmAction( COMMAND_CHECKIN ); + setTask( DEFAULT_TASK ); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java new file mode 100644 index 000000000..a01d72e68 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCheckout.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; + +/** + * Task to perform Checkout command to Continuus + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCheckout extends CCMCheck +{ + + public CCMCheckout() + { + super(); + setCcmAction( COMMAND_CHECKOUT ); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java new file mode 100644 index 000000000..f4ed8a11a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMCreateTask.java @@ -0,0 +1,356 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * Task allows to create new ccm task and set it as the default + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMCreateTask extends Continuus implements ExecuteStreamHandler +{ + + /** + * /comment -- comments associated to the task + */ + public final static String FLAG_COMMENT = "/synopsis"; + + /** + * /platform flag -- target platform + */ + public final static String FLAG_PLATFORM = "/plat"; + + /** + * /resolver flag + */ + public final static String FLAG_RESOLVER = "/resolver"; + + /** + * /release flag + */ + public final static String FLAG_RELEASE = "/release"; + + /** + * /release flag + */ + public final static String FLAG_SUBSYSTEM = "/subsystem"; + + /** + * -task flag -- associate checckout task with task + */ + public final static String FLAG_TASK = "/task"; + + private String comment = null; + private String platform = null; + private String resolver = null; + private String release = null; + private String subSystem = null; + private String task = null; + + public CCMCreateTask() + { + super(); + setCcmAction( COMMAND_CREATE_TASK ); + } + + /** + * Set the value of comment. + * + * @param v Value to assign to comment. + */ + public void setComment( String v ) + { + this.comment = v; + } + + /** + * Set the value of platform. + * + * @param v Value to assign to platform. + */ + public void setPlatform( String v ) + { + this.platform = v; + } + + /** + * @param is The new ProcessErrorStream value + * @exception IOException Description of Exception + */ + public void setProcessErrorStream( InputStream is ) + throws IOException + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + String s = reader.readLine(); + if( s != null ) + { + log( "err " + s, Project.MSG_DEBUG ); + }// end of if () + } + + /** + * @param param1 + * @exception IOException Description of Exception + */ + public void setProcessInputStream( OutputStream param1 ) + throws IOException { } + + /** + * read the output stream to retrieve the new task number. + * + * @param is InputStream + * @exception IOException Description of Exception + */ + public void setProcessOutputStream( InputStream is ) + throws IOException + { + + String buffer = ""; + try + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + buffer = reader.readLine(); + if( buffer != null ) + { + log( "buffer:" + buffer, Project.MSG_DEBUG ); + String taskstring = buffer.substring( buffer.indexOf( ' ' ) ).trim(); + taskstring = taskstring.substring( 0, taskstring.lastIndexOf( ' ' ) ).trim(); + setTask( taskstring ); + log( "task is " + getTask(), Project.MSG_DEBUG ); + }// end of if () + } + catch( NullPointerException npe ) + { + log( "error procession stream , null pointer exception", Project.MSG_ERR ); + npe.printStackTrace(); + throw new BuildException( npe.getClass().getName() ); + }// end of catch + catch( Exception e ) + { + log( "error procession stream " + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( e.getMessage() ); + }// end of try-catch + + } + + /** + * Set the value of release. + * + * @param v Value to assign to release. + */ + public void setRelease( String v ) + { + this.release = v; + } + + /** + * Set the value of resolver. + * + * @param v Value to assign to resolver. + */ + public void setResolver( String v ) + { + this.resolver = v; + } + + /** + * Set the value of subSystem. + * + * @param v Value to assign to subSystem. + */ + public void setSubSystem( String v ) + { + this.subSystem = v; + } + + /** + * Set the value of task. + * + * @param v Value to assign to task. + */ + public void setTask( String v ) + { + this.task = v; + } + + + /** + * Get the value of comment. + * + * @return value of comment. + */ + public String getComment() + { + return comment; + } + + + /** + * Get the value of platform. + * + * @return value of platform. + */ + public String getPlatform() + { + return platform; + } + + + /** + * Get the value of release. + * + * @return value of release. + */ + public String getRelease() + { + return release; + } + + + /** + * Get the value of resolver. + * + * @return value of resolver. + */ + public String getResolver() + { + return resolver; + } + + /** + * Get the value of subSystem. + * + * @return value of subSystem. + */ + public String getSubSystem() + { + return subSystem; + } + + + /** + * Get the value of task. + * + * @return value of task. + */ + public String getTask() + { + return task; + } + + + /** + * Executes the task.

              + * + * Builds a command line to execute ccm and then calls Exec's run method to + * execute the command line.

              + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // build the command line from what we got the format + // as specified in the CCM.EXE help + commandLine.setExecutable( getCcmCommand() ); + commandLine.createArgument().setValue( getCcmAction() ); + + checkOptions( commandLine ); + + result = run( commandLine, this ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + + //create task ok, set this task as the default one + Commandline commandLine2 = new Commandline(); + commandLine2.setExecutable( getCcmCommand() ); + commandLine2.createArgument().setValue( COMMAND_DEFAULT_TASK ); + commandLine2.createArgument().setValue( getTask() ); + + log( commandLine.toString(), Project.MSG_DEBUG ); + + result = run( commandLine2 ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine2.toString(); + throw new BuildException( msg, location ); + } + + } + + + // implementation of org.apache.tools.ant.taskdefs.ExecuteStreamHandler interface + + /** + * @exception IOException Description of Exception + */ + public void start() + throws IOException { } + + /** + */ + public void stop() { } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + if( getComment() != null ) + { + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( "\"" + getComment() + "\"" ); + } + + if( getPlatform() != null ) + { + cmd.createArgument().setValue( FLAG_PLATFORM ); + cmd.createArgument().setValue( getPlatform() ); + }// end of if () + + if( getResolver() != null ) + { + cmd.createArgument().setValue( FLAG_RESOLVER ); + cmd.createArgument().setValue( getResolver() ); + }// end of if () + + if( getSubSystem() != null ) + { + cmd.createArgument().setValue( FLAG_SUBSYSTEM ); + cmd.createArgument().setValue( "\"" + getSubSystem() + "\"" ); + }// end of if () + + if( getRelease() != null ) + { + cmd.createArgument().setValue( FLAG_RELEASE ); + cmd.createArgument().setValue( getRelease() ); + }// end of if () + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java new file mode 100644 index 000000000..d079f2ae7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/CCMReconfigure.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + +/** + * Task allows to reconfigure a project, recurcively or not + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public class CCMReconfigure extends Continuus +{ + + /** + * /recurse -- + */ + public final static String FLAG_RECURSE = "/recurse"; + + /** + * /recurse -- + */ + public final static String FLAG_VERBOSE = "/verbose"; + + /** + * /project flag -- target project + */ + public final static String FLAG_PROJECT = "/project"; + + private String project = null; + private boolean recurse = false; + private boolean verbose = false; + + public CCMReconfigure() + { + super(); + setCcmAction( COMMAND_RECONFIGURE ); + } + + /** + * Set the value of project. + * + * @param v Value to assign to project. + */ + public void setCcmProject( String v ) + { + this.project = v; + } + + /** + * Set the value of recurse. + * + * @param v Value to assign to recurse. + */ + public void setRecurse( boolean v ) + { + this.recurse = v; + } + + /** + * Set the value of verbose. + * + * @param v Value to assign to verbose. + */ + public void setVerbose( boolean v ) + { + this.verbose = v; + } + + /** + * Get the value of project. + * + * @return value of project. + */ + public String getCcmProject() + { + return project; + } + + + /** + * Get the value of recurse. + * + * @return value of recurse. + */ + public boolean isRecurse() + { + return recurse; + } + + + /** + * Get the value of verbose. + * + * @return value of verbose. + */ + public boolean isVerbose() + { + return verbose; + } + + + /** + * Executes the task.

              + * + * Builds a command line to execute ccm and then calls Exec's run method to + * execute the command line.

              + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // build the command line from what we got the format + // as specified in the CCM.EXE help + commandLine.setExecutable( getCcmCommand() ); + commandLine.createArgument().setValue( getCcmAction() ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + + if( isRecurse() == true ) + { + cmd.createArgument().setValue( FLAG_RECURSE ); + }// end of if () + + if( isVerbose() == true ) + { + cmd.createArgument().setValue( FLAG_VERBOSE ); + }// end of if () + + if( getCcmProject() != null ) + { + cmd.createArgument().setValue( FLAG_PROJECT ); + cmd.createArgument().setValue( getCcmProject() ); + } + + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java new file mode 100644 index 000000000..efc30650d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ccm/Continuus.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ccm; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * A base class for creating tasks for executing commands on Continuus 5.1

              + * + * The class extends the task as it operates by executing the ccm.exe program + * supplied with Continuus/Synergy. By default the task expects the ccm + * executable to be in the path, you can override this be specifying the ccmdir + * attribute.

              + * + * @author Benoit Moussaud benoit.moussaud@criltelecom.com + */ +public abstract class Continuus extends Task +{ + + /** + * Constant for the thing to execute + */ + private final static String CCM_EXE = "ccm"; + + /** + * The 'CreateTask' command + */ + public final static String COMMAND_CREATE_TASK = "create_task"; + /** + * The 'Checkout' command + */ + public final static String COMMAND_CHECKOUT = "co"; + /** + * The 'Checkin' command + */ + public final static String COMMAND_CHECKIN = "ci"; + /** + * The 'Reconfigure' command + */ + public final static String COMMAND_RECONFIGURE = "reconfigure"; + + /** + * The 'Reconfigure' command + */ + public final static String COMMAND_DEFAULT_TASK = "default_task"; + + private String _ccmDir = ""; + private String _ccmAction = ""; + + + /** + * Set the directory where the ccm executable is located + * + * @param dir the directory containing the ccm executable + */ + public final void setCcmDir( String dir ) + { + _ccmDir = project.translatePath( dir ); + } + + /** + * Set the value of ccmAction. + * + * @param v Value to assign to ccmAction. + */ + public void setCcmAction( String v ) + { + this._ccmAction = v; + } + + /** + * Get the value of ccmAction. + * + * @return value of ccmAction. + */ + public String getCcmAction() + { + return _ccmAction; + } + + /** + * Builds and returns the command string to execute ccm + * + * @return String containing path to the executable + */ + protected final String getCcmCommand() + { + String toReturn = _ccmDir; + if( !toReturn.equals( "" ) && !toReturn.endsWith( "/" ) ) + { + toReturn += "/"; + } + + toReturn += CCM_EXE; + + return toReturn; + } + + + protected int run( Commandline cmd, ExecuteStreamHandler handler ) + { + try + { + Execute exe = new Execute( handler ); + exe.setAntRun( getProject() ); + exe.setWorkingDirectory( getProject().getBaseDir() ); + exe.setCommandline( cmd.getCommandline() ); + return exe.execute(); + } + catch( java.io.IOException e ) + { + throw new BuildException( e ); + } + } + + protected int run( Commandline cmd ) + { + return run( cmd, new LogStreamHandler( this, Project.MSG_VERBOSE, Project.MSG_WARN ) ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java new file mode 100644 index 000000000..e4104295a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckin.java @@ -0,0 +1,451 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform Checkin command to ClearCase.

              + * + * The following attributes are interpreted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * comment + * + * + * + * Specify a comment. Only one of comment or cfile may be used. + * + * + * + * No + * + * + * + * + * + * + * + * commentfile + * + * + * + * Specify a file containing a comment. Only one of comment or + * cfile may be used. + * + * + * + * No + * + * + * + * + * + * + * + * nowarn + * + * + * + * Suppress warning messages + * + * + * + * No + * + * + * + * + * + * + * + * preservetime + * + * + * + * Preserve the modification time + * + * + * + * No + * + * + * + * + * + * + * + * keepcopy + * + * + * + * Keeps a copy of the file with a .keep extension + * + * + * + * + * No + * + * + * + * + * + * + * + * identical + * + * + * + * Allows the file to be checked in even if it is + * identical to the original + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCCheckin extends ClearCase +{ + + /** + * -c flag -- comment to attach to the file + */ + public final static String FLAG_COMMENT = "-c"; + /** + * -cfile flag -- file containing a comment to attach to the file + */ + public final static String FLAG_COMMENTFILE = "-cfile"; + /** + * -nc flag -- no comment is specified + */ + public final static String FLAG_NOCOMMENT = "-nc"; + /** + * -nwarn flag -- suppresses warning messages + */ + public final static String FLAG_NOWARN = "-nwarn"; + /** + * -ptime flag -- preserves the modification time + */ + public final static String FLAG_PRESERVETIME = "-ptime"; + /** + * -keep flag -- keeps a copy of the file with a .keep extension + */ + public final static String FLAG_KEEPCOPY = "-keep"; + /** + * -identical flag -- allows the file to be checked in even if it is + * identical to the original + */ + public final static String FLAG_IDENTICAL = "-identical"; + private String m_Comment = null; + private String m_Cfile = null; + private boolean m_Nwarn = false; + private boolean m_Ptime = false; + private boolean m_Keep = false; + private boolean m_Identical = true; + + + /** + * Set comment string + * + * @param comment the comment string + */ + public void setComment( String comment ) + { + m_Comment = comment; + } + + /** + * Set comment file + * + * @param cfile the path to the comment file + */ + public void setCommentFile( String cfile ) + { + m_Cfile = cfile; + } + + /** + * Set the identical flag + * + * @param identical the status to set the flag to + */ + public void setIdentical( boolean identical ) + { + m_Identical = identical; + } + + /** + * Set the keepcopy flag + * + * @param keep the status to set the flag to + */ + public void setKeepCopy( boolean keep ) + { + m_Keep = keep; + } + + /** + * Set the nowarn flag + * + * @param nwarn the status to set the flag to + */ + public void setNoWarn( boolean nwarn ) + { + m_Nwarn = nwarn; + } + + /** + * Set preservetime flag + * + * @param ptime the status to set the flag to + */ + public void setPreserveTime( boolean ptime ) + { + m_Ptime = ptime; + } + + /** + * Get comment string + * + * @return String containing the comment + */ + public String getComment() + { + return m_Comment; + } + + /** + * Get comment file + * + * @return String containing the path to the comment file + */ + public String getCommentFile() + { + return m_Cfile; + } + + /** + * Get identical flag status + * + * @return boolean containing status of identical flag + */ + public boolean getIdentical() + { + return m_Identical; + } + + /** + * Get keepcopy flag status + * + * @return boolean containing status of keepcopy flag + */ + public boolean getKeepCopy() + { + return m_Keep; + } + + /** + * Get nowarn flag status + * + * @return boolean containing status of nwarn flag + */ + public boolean getNoWarn() + { + return m_Nwarn; + } + + /** + * Get preservetime flag status + * + * @return boolean containing status of preservetime flag + */ + public boolean getPreserveTime() + { + return m_Ptime; + } + + /** + * Executes the task.

              + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got. the format is + // cleartool checkin [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_CHECKIN ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Get the 'comment' command + * + * @param cmd Description of Parameter + */ + private void getCommentCommand( Commandline cmd ) + { + if( getComment() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( getComment() ); + } + } + + /** + * Get the 'commentfile' command + * + * @param cmd Description of Parameter + */ + private void getCommentFileCommand( Commandline cmd ) + { + if( getCommentFile() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENTFILE ); + cmd.createArgument().setValue( getCommentFile() ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + if( getComment() != null ) + { + // -c + getCommentCommand( cmd ); + } + else + { + if( getCommentFile() != null ) + { + // -cfile + getCommentFileCommand( cmd ); + } + else + { + cmd.createArgument().setValue( FLAG_NOCOMMENT ); + } + } + + if( getNoWarn() ) + { + // -nwarn + cmd.createArgument().setValue( FLAG_NOWARN ); + } + + if( getPreserveTime() ) + { + // -ptime + cmd.createArgument().setValue( FLAG_PRESERVETIME ); + } + + if( getKeepCopy() ) + { + // -keep + cmd.createArgument().setValue( FLAG_KEEPCOPY ); + } + + if( getIdentical() ) + { + // -identical + cmd.createArgument().setValue( FLAG_IDENTICAL ); + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java new file mode 100644 index 000000000..7c649bc5b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCCheckout.java @@ -0,0 +1,602 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform Checkout command to ClearCase.

              + * + * The following attributes are interpretted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * reserved + * + * + * + * Specifies whether to check out the file as reserved or not + * + * + * + * Yes + * + * + * + * + * + * + * + * out + * + * + * + * Creates a writable file under a different filename + * + * + * + * No + * + * + * + * + * + * + * + * nodata + * + * + * + * Checks out the file but does not create an editable file + * containing its data + * + * + * + * No + * + * + * + * + * + * + * + * branch + * + * + * + * Specify a branch to check out the file to + * + * + * + * No + * + * + * + * + * + * + * + * version + * + * + * + * Allows checkout of a version other than main latest + * + * + * + * + * No + * + * + * + * + * + * + * + * nowarn + * + * + * + * Suppress warning messages + * + * + * + * No + * + * + * + * + * + * + * + * comment + * + * + * + * Specify a comment. Only one of comment or + * cfile may be used. + * + * + * + * No + * + * + * + * + * + * + * + * commentfile + * + * + * + * Specify a file containing a comment. + * Only one of comment or cfile may be + * used. + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCCheckout extends ClearCase +{ + + /** + * -reserved flag -- check out the file as reserved + */ + public final static String FLAG_RESERVED = "-reserved"; + /** + * -reserved flag -- check out the file as unreserved + */ + public final static String FLAG_UNRESERVED = "-unreserved"; + /** + * -out flag -- create a writable file under a different filename + */ + public final static String FLAG_OUT = "-out"; + /** + * -ndata flag -- checks out the file but does not create an editable file + * containing its data + */ + public final static String FLAG_NODATA = "-ndata"; + /** + * -branch flag -- checks out the file on a specified branch + */ + public final static String FLAG_BRANCH = "-branch"; + /** + * -version flag -- allows checkout of a version that is not main latest + */ + public final static String FLAG_VERSION = "-version"; + /** + * -nwarn flag -- suppresses warning messages + */ + public final static String FLAG_NOWARN = "-nwarn"; + /** + * -c flag -- comment to attach to the file + */ + public final static String FLAG_COMMENT = "-c"; + /** + * -cfile flag -- file containing a comment to attach to the file + */ + public final static String FLAG_COMMENTFILE = "-cfile"; + /** + * -nc flag -- no comment is specified + */ + public final static String FLAG_NOCOMMENT = "-nc"; + private boolean m_Reserved = true; + private String m_Out = null; + private boolean m_Ndata = false; + private String m_Branch = null; + private boolean m_Version = false; + private boolean m_Nwarn = false; + private String m_Comment = null; + private String m_Cfile = null; + + /** + * Set branch name + * + * @param branch the name of the branch + */ + public void setBranch( String branch ) + { + m_Branch = branch; + } + + /** + * Set comment string + * + * @param comment the comment string + */ + public void setComment( String comment ) + { + m_Comment = comment; + } + + /** + * Set comment file + * + * @param cfile the path to the comment file + */ + public void setCommentFile( String cfile ) + { + m_Cfile = cfile; + } + + /** + * Set the nodata flag + * + * @param ndata the status to set the flag to + */ + public void setNoData( boolean ndata ) + { + m_Ndata = ndata; + } + + /** + * Set the nowarn flag + * + * @param nwarn the status to set the flag to + */ + public void setNoWarn( boolean nwarn ) + { + m_Nwarn = nwarn; + } + + /** + * Set out file + * + * @param outf the path to the out file + */ + public void setOut( String outf ) + { + m_Out = outf; + } + + /** + * Set reserved flag status + * + * @param reserved the status to set the flag to + */ + public void setReserved( boolean reserved ) + { + m_Reserved = reserved; + } + + /** + * Set the version flag + * + * @param version the status to set the flag to + */ + public void setVersion( boolean version ) + { + m_Version = version; + } + + /** + * Get branch name + * + * @return String containing the name of the branch + */ + public String getBranch() + { + return m_Branch; + } + + /** + * Get comment string + * + * @return String containing the comment + */ + public String getComment() + { + return m_Comment; + } + + /** + * Get comment file + * + * @return String containing the path to the comment file + */ + public String getCommentFile() + { + return m_Cfile; + } + + /** + * Get nodata flag status + * + * @return boolean containing status of ndata flag + */ + public boolean getNoData() + { + return m_Ndata; + } + + /** + * Get nowarn flag status + * + * @return boolean containing status of nwarn flag + */ + public boolean getNoWarn() + { + return m_Nwarn; + } + + /** + * Get out file + * + * @return String containing the path to the out file + */ + public String getOut() + { + return m_Out; + } + + /** + * Get reserved flag status + * + * @return boolean containing status of reserved flag + */ + public boolean getReserved() + { + return m_Reserved; + } + + /** + * Get version flag status + * + * @return boolean containing status of version flag + */ + public boolean getVersion() + { + return m_Version; + } + + /** + * Executes the task.

              + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got the format is + // cleartool checkout [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_CHECKOUT ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + /** + * Get the 'branch' command + * + * @param cmd Description of Parameter + */ + private void getBranchCommand( Commandline cmd ) + { + if( getBranch() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_BRANCH ); + cmd.createArgument().setValue( getBranch() ); + } + } + + + /** + * Get the 'comment' command + * + * @param cmd Description of Parameter + */ + private void getCommentCommand( Commandline cmd ) + { + if( getComment() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENT ); + cmd.createArgument().setValue( getComment() ); + } + } + + /** + * Get the 'cfile' command + * + * @param cmd Description of Parameter + */ + private void getCommentFileCommand( Commandline cmd ) + { + if( getCommentFile() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_COMMENTFILE ); + cmd.createArgument().setValue( getCommentFile() ); + } + } + + /** + * Get the 'out' command + * + * @param cmd Description of Parameter + */ + private void getOutCommand( Commandline cmd ) + { + if( getOut() != null ) + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_OUT ); + cmd.createArgument().setValue( getOut() ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + // ClearCase items + if( getReserved() ) + { + // -reserved + cmd.createArgument().setValue( FLAG_RESERVED ); + } + else + { + // -unreserved + cmd.createArgument().setValue( FLAG_UNRESERVED ); + } + + if( getOut() != null ) + { + // -out + getOutCommand( cmd ); + } + else + { + if( getNoData() ) + { + // -ndata + cmd.createArgument().setValue( FLAG_NODATA ); + } + + } + + if( getBranch() != null ) + { + // -branch + getBranchCommand( cmd ); + } + else + { + if( getVersion() ) + { + // -version + cmd.createArgument().setValue( FLAG_VERSION ); + } + + } + + if( getNoWarn() ) + { + // -nwarn + cmd.createArgument().setValue( FLAG_NOWARN ); + } + + if( getComment() != null ) + { + // -c + getCommentCommand( cmd ); + } + else + { + if( getCommentFile() != null ) + { + // -cfile + getCommentFileCommand( cmd ); + } + else + { + cmd.createArgument().setValue( FLAG_NOCOMMENT ); + } + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java new file mode 100644 index 000000000..02a442f2c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUnCheckout.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform UnCheckout command to ClearCase.

              + * + * The following attributes are interpretted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * keepcopy + * + * + * + * Specifies whether to keep a copy of the file with a .keep extension + * or not + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCUnCheckout extends ClearCase +{ + + /** + * -keep flag -- keep a copy of the file with .keep extension + */ + public final static String FLAG_KEEPCOPY = "-keep"; + /** + * -rm flag -- remove the copy of the file + */ + public final static String FLAG_RM = "-rm"; + private boolean m_Keep = false; + + /** + * Set keepcopy flag status + * + * @param keep the status to set the flag to + */ + public void setKeepCopy( boolean keep ) + { + m_Keep = keep; + } + + /** + * Get keepcopy flag status + * + * @return boolean containing status of keep flag + */ + public boolean getKeepCopy() + { + return m_Keep; + } + + /** + * Executes the task.

              + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got the format is + // cleartool uncheckout [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_UNCHECKOUT ); + + checkOptions( commandLine ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + // ClearCase items + if( getKeepCopy() ) + { + // -keep + cmd.createArgument().setValue( FLAG_KEEPCOPY ); + } + else + { + // -rm + cmd.createArgument().setValue( FLAG_RM ); + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java new file mode 100644 index 000000000..2d88f71c4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/CCUpdate.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.Commandline; + + + +/** + * Task to perform an Update command to ClearCase.

              + * + * The following attributes are interpretted: + * + * + * + * + * + * Attribute + * + * + * + * Values + * + * + * + * Required + * + * + * + * + * + * + * + * viewpath + * + * + * + * Path to the ClearCase view file or directory that the command will + * operate on + * + * + * + * No + * + * + * + * + * + * + * + * graphical + * + * + * + * Displays a graphical dialog during the update + * + * + * + * No + * + * + * + * + * + * + * + * log + * + * + * + * Specifies a log file for ClearCase to write to + * + * + * + * No + * + * + * + * + * + * + * + * overwrite + * + * + * + * Specifies whether to overwrite hijacked files or not + * + * + * + * No + * + * + * + * + * + * + * + * rename + * + * + * + * Specifies that hijacked files should be renamed with a + * .keep extension + * + * + * + * No + * + * + * + * + * + * + * + * currenttime + * + * + * + * Specifies that modification time should be written + * as the current time. Either currenttime or + * preservetime can be specified. + * + * + * + * No + * + * + * + * + * + * + * + * preservetime + * + * + * + * Specifies that modification time should + * preserved from the VOB time. Either currenttime + * or preservetime can be specified. + * + * + * + * No + * + * + * + * + * + * + * + * @author Curtis White + */ +public class CCUpdate extends ClearCase +{ + + /** + * -graphical flag -- display graphical dialog during update operation + */ + public final static String FLAG_GRAPHICAL = "-graphical"; + /** + * -log flag -- file to log status to + */ + public final static String FLAG_LOG = "-log"; + /** + * -overwrite flag -- overwrite hijacked files + */ + public final static String FLAG_OVERWRITE = "-overwrite"; + /** + * -noverwrite flag -- do not overwrite hijacked files + */ + public final static String FLAG_NOVERWRITE = "-noverwrite"; + /** + * -rename flag -- rename hijacked files with .keep extension + */ + public final static String FLAG_RENAME = "-rename"; + /** + * -ctime flag -- modified time is written as the current time + */ + public final static String FLAG_CURRENTTIME = "-ctime"; + /** + * -ptime flag -- modified time is written as the VOB time + */ + public final static String FLAG_PRESERVETIME = "-ptime"; + private boolean m_Graphical = false; + private boolean m_Overwrite = false; + private boolean m_Rename = false; + private boolean m_Ctime = false; + private boolean m_Ptime = false; + private String m_Log = null; + + /** + * Set modified time based on current time + * + * @param ct the status to set the flag to + */ + public void setCurrentTime( boolean ct ) + { + m_Ctime = ct; + } + + /** + * Set graphical flag status + * + * @param graphical the status to set the flag to + */ + public void setGraphical( boolean graphical ) + { + m_Graphical = graphical; + } + + /** + * Set log file where cleartool can record the status of the command + * + * @param log the path to the log file + */ + public void setLog( String log ) + { + m_Log = log; + } + + /** + * Set overwrite hijacked files status + * + * @param ow the status to set the flag to + */ + public void setOverwrite( boolean ow ) + { + m_Overwrite = ow; + } + + /** + * Preserve modified time from the VOB time + * + * @param pt the status to set the flag to + */ + public void setPreserveTime( boolean pt ) + { + m_Ptime = pt; + } + + /** + * Set rename hijacked files status + * + * @param ren the status to set the flag to + */ + public void setRename( boolean ren ) + { + m_Rename = ren; + } + + /** + * Get current time status + * + * @return boolean containing status of current time flag + */ + public boolean getCurrentTime() + { + return m_Ctime; + } + + /** + * Get graphical flag status + * + * @return boolean containing status of graphical flag + */ + public boolean getGraphical() + { + return m_Graphical; + } + + /** + * Get log file + * + * @return String containing the path to the log file + */ + public String getLog() + { + return m_Log; + } + + /** + * Get overwrite hijacked files status + * + * @return boolean containing status of overwrite flag + */ + public boolean getOverwrite() + { + return m_Overwrite; + } + + /** + * Get preserve time status + * + * @return boolean containing status of preserve time flag + */ + public boolean getPreserveTime() + { + return m_Ptime; + } + + /** + * Get rename hijacked files status + * + * @return boolean containing status of rename flag + */ + public boolean getRename() + { + return m_Rename; + } + + /** + * Executes the task.

              + * + * Builds a command line to execute cleartool and then calls Exec's run + * method to execute the command line. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Commandline commandLine = new Commandline(); + Project aProj = getProject(); + int result = 0; + + // Default the viewpath to basedir if it is not specified + if( getViewPath() == null ) + { + setViewPath( aProj.getBaseDir().getPath() ); + } + + // build the command line from what we got the format is + // cleartool update [options...] [viewpath ...] + // as specified in the CLEARTOOL.EXE help + commandLine.setExecutable( getClearToolCommand() ); + commandLine.createArgument().setValue( COMMAND_UPDATE ); + + // Check the command line options + checkOptions( commandLine ); + + // For debugging + System.out.println( commandLine.toString() ); + + result = run( commandLine ); + if( result != 0 ) + { + String msg = "Failed executing: " + commandLine.toString(); + throw new BuildException( msg, location ); + } + } + + /** + * Get the 'log' command + * + * @param cmd Description of Parameter + */ + private void getLogCommand( Commandline cmd ) + { + if( getLog() == null ) + { + return; + } + else + { + /* + * Had to make two separate commands here because if a space is + * inserted between the flag and the value, it is treated as a + * Windows filename with a space and it is enclosed in double + * quotes ("). This breaks clearcase. + */ + cmd.createArgument().setValue( FLAG_LOG ); + cmd.createArgument().setValue( getLog() ); + } + } + + /** + * Check the command line options. + * + * @param cmd Description of Parameter + */ + private void checkOptions( Commandline cmd ) + { + // ClearCase items + if( getGraphical() ) + { + // -graphical + cmd.createArgument().setValue( FLAG_GRAPHICAL ); + } + else + { + if( getOverwrite() ) + { + // -overwrite + cmd.createArgument().setValue( FLAG_OVERWRITE ); + } + else + { + if( getRename() ) + { + // -rename + cmd.createArgument().setValue( FLAG_RENAME ); + } + else + { + // -noverwrite + cmd.createArgument().setValue( FLAG_NOVERWRITE ); + } + } + + if( getCurrentTime() ) + { + // -ctime + cmd.createArgument().setValue( FLAG_CURRENTTIME ); + } + else + { + if( getPreserveTime() ) + { + // -ptime + cmd.createArgument().setValue( FLAG_PRESERVETIME ); + } + } + + // -log logname + getLogCommand( cmd ); + } + + // viewpath + cmd.createArgument().setValue( getViewPath() ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java new file mode 100644 index 000000000..917152f39 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/clearcase/ClearCase.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.clearcase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * A base class for creating tasks for executing commands on ClearCase.

              + * + * The class extends the 'exec' task as it operates by executing the cleartool + * program supplied with ClearCase. By default the task expects the cleartool + * executable to be in the path, * you can override this be specifying the + * cleartooldir attribute.

              + * + * This class provides set and get methods for the 'viewpath' attribute. It also + * contains constants for the flags that can be passed to cleartool.

              + * + * @author Curtis White + */ +public abstract class ClearCase extends Task +{ + + /** + * Constant for the thing to execute + */ + private final static String CLEARTOOL_EXE = "cleartool"; + + /** + * The 'Update' command + */ + public final static String COMMAND_UPDATE = "update"; + /** + * The 'Checkout' command + */ + public final static String COMMAND_CHECKOUT = "checkout"; + /** + * The 'Checkin' command + */ + public final static String COMMAND_CHECKIN = "checkin"; + /** + * The 'UndoCheckout' command + */ + public final static String COMMAND_UNCHECKOUT = "uncheckout"; + private String m_ClearToolDir = ""; + private String m_viewPath = null; + + /** + * Set the directory where the cleartool executable is located + * + * @param dir the directory containing the cleartool executable + */ + public final void setClearToolDir( String dir ) + { + m_ClearToolDir = project.translatePath( dir ); + } + + /** + * Set the path to the item in a clearcase view to operate on + * + * @param viewPath Path to the view directory or file + */ + public final void setViewPath( String viewPath ) + { + m_viewPath = viewPath; + } + + /** + * Get the path to the item in a clearcase view + * + * @return m_viewPath + */ + public String getViewPath() + { + return m_viewPath; + } + + /** + * Builds and returns the command string to execute cleartool + * + * @return String containing path to the executable + */ + protected final String getClearToolCommand() + { + String toReturn = m_ClearToolDir; + if( !toReturn.equals( "" ) && !toReturn.endsWith( "/" ) ) + { + toReturn += "/"; + } + + toReturn += CLEARTOOL_EXE; + + return toReturn; + } + + + protected int run( Commandline cmd ) + { + try + { + Project aProj = getProject(); + Execute exe = new Execute( new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ) ); + exe.setAntRun( aProj ); + exe.setWorkingDirectory( aProj.getBaseDir() ); + exe.setCommandline( cmd.getCommandline() ); + return exe.execute(); + } + catch( java.io.IOException e ) + { + throw new BuildException( e ); + } + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java new file mode 100644 index 000000000..f0479ae8c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFile.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; +import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ClassCPInfo; +import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ConstantPool; +import org.apache.tools.ant.taskdefs.optional.depend.constantpool.ConstantPoolEntry; + +/** + * A ClassFile object stores information about a Java class. The class may be + * read from a DataInputStream.and written to a DataOutputStream. These are + * usually streams from a Java class file or a class file component of a Jar + * file. + * + * @author Conor MacNeill + */ +public class ClassFile +{ + + /** + * The Magic Value that marks the start of a Java class file + */ + private final static int CLASS_MAGIC = 0xCAFEBABE; + + /** + * The class name for this class. + */ + private String className; + + /** + * This class' constant pool. + */ + private ConstantPool constantPool; + + + /** + * Get the classes which this class references. + * + * @return The ClassRefs value + */ + public Vector getClassRefs() + { + + Vector classRefs = new Vector(); + + for( int i = 0; i < constantPool.size(); ++i ) + { + ConstantPoolEntry entry = constantPool.getEntry( i ); + + if( entry != null && entry.getTag() == ConstantPoolEntry.CONSTANT_Class ) + { + ClassCPInfo classEntry = ( ClassCPInfo )entry; + + if( !classEntry.getClassName().equals( className ) ) + { + classRefs.addElement( ClassFileUtils.convertSlashName( classEntry.getClassName() ) ); + } + } + } + + return classRefs; + } + + /** + * Get the class' fully qualified name in dot format. + * + * @return the class name in dot format (eg. java.lang.Object) + */ + public String getFullClassName() + { + return ClassFileUtils.convertSlashName( className ); + } + + /** + * Read the class from a data stream. This method takes an InputStream as + * input and parses the class from the stream.

              + * + * + * + * @param stream an InputStream from which the class will be read + * @throws IOException if there is a problem reading from the given stream. + * @throws ClassFormatError if the class cannot be parsed correctly + */ + public void read( InputStream stream ) + throws IOException, ClassFormatError + { + DataInputStream classStream = new DataInputStream( stream ); + + if( classStream.readInt() != CLASS_MAGIC ) + { + throw new ClassFormatError( "No Magic Code Found - probably not a Java class file." ); + } + + // right we have a good looking class file. + int minorVersion = classStream.readUnsignedShort(); + int majorVersion = classStream.readUnsignedShort(); + + // read the constant pool in and resolve it + constantPool = new ConstantPool(); + + constantPool.read( classStream ); + constantPool.resolve(); + + int accessFlags = classStream.readUnsignedShort(); + int thisClassIndex = classStream.readUnsignedShort(); + int superClassIndex = classStream.readUnsignedShort(); + className = ( ( ClassCPInfo )constantPool.getEntry( thisClassIndex ) ).getClassName(); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java new file mode 100644 index 000000000..de4ad9499 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileIterator.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; + + +public interface ClassFileIterator +{ + + ClassFile getNextClassFile(); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java new file mode 100644 index 000000000..054ef68f8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/ClassFileUtils.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; + +/** + * Utility class file routines. This class porovides a number of static utility + * methods to convert between the formats used in the Java class file format and + * those commonly used in Java programming. + * + * @author Conor MacNeill + */ +public class ClassFileUtils +{ + + /** + * Convert a class name from java source file dot notation to class file + * slash notation.. + * + * @param dotName the class name in dot notation (eg. java.lang.Object). + * @return the class name in slash notation (eg. java/lang/Object). + */ + public static String convertDotName( String dotName ) + { + return dotName.replace( '.', '/' ); + } + + /** + * Convert a class name from class file slash notation to java source file + * dot notation. + * + * @param name Description of Parameter + * @return the class name in dot notation (eg. java.lang.Object). + */ + public static String convertSlashName( String name ) + { + return name.replace( '\\', '.' ).replace( '/', '.' ); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/Depend.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/Depend.java new file mode 100644 index 000000000..62ec1812e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/Depend.java @@ -0,0 +1,764 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + + +/** + * Generate a dependency file for a given set of classes + * + * @author Conor MacNeill + */ +public class Depend extends MatchingTask +{ + + /** + * constants used with the cache file + */ + private final static String CACHE_FILE_NAME = "dependencies.txt"; + private final static String CLASSNAME_PREPEND = "||:"; + + /** + * indicates that the dependency relationships should be extended beyond + * direct dependencies to include all classes. So if A directly affects B + * abd B directly affects C, then A indirectly affects C. + */ + private boolean closure = false; + + /** + * Flag which controls whether the reversed dependencies should be dumped to + * the log + */ + private boolean dump = false; + + /** + * A map which gives for every class a list of te class which it affects. + */ + private Hashtable affectedClassMap; + + /** + * The directory which contains the dependency cache. + */ + private File cache; + + /** + * A map which gives information about a class + */ + private Hashtable classFileInfoMap; + + /** + * A map which gives the list of jars and classes from the classpath that a + * class depends upon + */ + private Hashtable classpathDependencies; + + /** + * The classpath to look for additional dependencies + */ + private Path dependClasspath; + + /** + * The path where compiled class files exist. + */ + private Path destPath; + + /** + * The list of classes which are out of date. + */ + private Hashtable outOfDateClasses; + + /** + * The path where source files exist + */ + private Path srcPath; + + public void setCache( File cache ) + { + this.cache = cache; + } + + /** + * Set the classpath to be used for this dependency check. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( dependClasspath == null ) + { + dependClasspath = classpath; + } + else + { + dependClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setClosure( boolean closure ) + { + this.closure = closure; + } + + /** + * Set the destination directory where the compiled java files exist. + * + * @param destPath The new DestDir value + */ + public void setDestDir( Path destPath ) + { + this.destPath = destPath; + } + + /** + * Flag to indicate whether the reverse dependency list should be dumped to + * debug + * + * @param dump The new Dump value + */ + public void setDump( boolean dump ) + { + this.dump = dump; + } + + + /** + * Set the source dirs to find the source Java files. + * + * @param srcPath The new Srcdir value + */ + public void setSrcdir( Path srcPath ) + { + this.srcPath = srcPath; + } + + /** + * Gets the classpath to be used for this dependency check. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return dependClasspath; + } + + /** + * Creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( dependClasspath == null ) + { + dependClasspath = new Path( project ); + } + return dependClasspath.createPath(); + } + + /** + * Does the work. + * + * @exception BuildException Thrown in unrecovrable error. + */ + public void execute() + throws BuildException + { + try + { + long start = System.currentTimeMillis(); + String[] srcPathList = srcPath.list(); + if( srcPathList.length == 0 ) + { + throw new BuildException( "srcdir attribute must be set!", location ); + } + + if( destPath == null ) + { + destPath = srcPath; + } + + if( cache != null && cache.exists() && !cache.isDirectory() ) + { + throw new BuildException( "The cache, if specified, must point to a directory" ); + } + + if( cache != null && !cache.exists() ) + { + cache.mkdirs(); + } + + determineDependencies(); + + if( dump ) + { + log( "Reverse Dependency Dump for " + affectedClassMap.size() + + " classes:", Project.MSG_DEBUG ); + for( Enumeration e = affectedClassMap.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + log( " Class " + className + " affects:", Project.MSG_DEBUG ); + Hashtable affectedClasses = ( Hashtable )affectedClassMap.get( className ); + for( Enumeration e2 = affectedClasses.keys(); e2.hasMoreElements(); ) + { + String affectedClass = ( String )e2.nextElement(); + ClassFileInfo info = ( ClassFileInfo )affectedClasses.get( affectedClass ); + log( " " + affectedClass + " in " + info.absoluteFile.getPath(), Project.MSG_DEBUG ); + } + } + + if( classpathDependencies != null ) + { + log( "Classpath file dependencies (Forward):", Project.MSG_DEBUG ); + for( Enumeration e = classpathDependencies.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + log( " Class " + className + " depends on:", Project.MSG_DEBUG ); + Hashtable dependencies = ( Hashtable )classpathDependencies.get( className ); + for( Enumeration e2 = dependencies.elements(); e2.hasMoreElements(); ) + { + File classpathFile = ( File )e2.nextElement(); + log( " " + classpathFile.getPath(), Project.MSG_DEBUG ); + } + } + } + + } + + // we now need to scan for out of date files. When we have the list + // we go through and delete all class files which are affected by these files. + outOfDateClasses = new Hashtable(); + for( int i = 0; i < srcPathList.length; i++ ) + { + File srcDir = ( File )project.resolveFile( srcPathList[i] ); + if( srcDir.exists() ) + { + DirectoryScanner ds = this.getDirectoryScanner( srcDir ); + String[] files = ds.getIncludedFiles(); + scanDir( srcDir, files ); + } + } + + // now check classpath file dependencies + if( classpathDependencies != null ) + { + for( Enumeration e = classpathDependencies.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + if( !outOfDateClasses.containsKey( className ) ) + { + ClassFileInfo info = ( ClassFileInfo )classFileInfoMap.get( className ); + + // if we have no info about the class - it may have been deleted already and we + // are using cached info. + if( info != null ) + { + Hashtable dependencies = ( Hashtable )classpathDependencies.get( className ); + for( Enumeration e2 = dependencies.elements(); e2.hasMoreElements(); ) + { + File classpathFile = ( File )e2.nextElement(); + if( classpathFile.lastModified() > info.absoluteFile.lastModified() ) + { + log( "Class " + className + + " is out of date with respect to " + classpathFile, Project.MSG_DEBUG ); + outOfDateClasses.put( className, className ); + break; + } + } + } + } + } + } + + // we now have a complete list of classes which are out of date + // We scan through the affected classes, deleting any affected classes. + int count = deleteAllAffectedFiles(); + + long duration = ( System.currentTimeMillis() - start ) / 1000; + log( "Deleted " + count + " out of date files in " + duration + " seconds" ); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + /** + * Scans the directory looking for source files that are newer than their + * class files. The results are returned in the class variable compileList + * + * @param srcDir Description of Parameter + * @param files Description of Parameter + */ + protected void scanDir( File srcDir, String files[] ) + { + + long now = System.currentTimeMillis(); + + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".java" ) ) + { + String filePath = srcFile.getPath(); + String className = filePath.substring( srcDir.getPath().length() + 1, + filePath.length() - ".java".length() ); + className = ClassFileUtils.convertSlashName( className ); + ClassFileInfo info = ( ClassFileInfo )classFileInfoMap.get( className ); + if( info == null ) + { + // there was no class file. add this class to the list + outOfDateClasses.put( className, className ); + } + else + { + if( srcFile.lastModified() > info.absoluteFile.lastModified() ) + { + outOfDateClasses.put( className, className ); + } + } + } + } + } + + + /** + * Get the list of class files we are going to analyse. + * + * @param classLocations a path structure containing all the directories + * where classes can be found. + * @return a vector containing the classes to analyse. + */ + private Vector getClassFiles( Path classLocations ) + { + // break the classLocations into its components. + String[] classLocationsList = classLocations.list(); + + Vector classFileList = new Vector(); + + for( int i = 0; i < classLocationsList.length; ++i ) + { + File dir = new File( classLocationsList[i] ); + if( dir.isDirectory() ) + { + addClassFiles( classFileList, dir, dir ); + } + } + + return classFileList; + } + + /** + * Add the list of class files from the given directory to the class file + * vector, including any subdirectories. + * + * @param classFileList The feature to be added to the ClassFiles attribute + * @param dir The feature to be added to the ClassFiles attribute + * @param root The feature to be added to the ClassFiles attribute + */ + private void addClassFiles( Vector classFileList, File dir, File root ) + { + String[] filesInDir = dir.list(); + + if( filesInDir != null ) + { + int length = filesInDir.length; + + for( int i = 0; i < length; ++i ) + { + File file = new File( dir, filesInDir[i] ); + if( file.isDirectory() ) + { + addClassFiles( classFileList, file, root ); + } + else if( file.getName().endsWith( ".class" ) ) + { + ClassFileInfo info = new ClassFileInfo(); + info.absoluteFile = file; + info.relativeName = file.getPath().substring( root.getPath().length() + 1, + file.getPath().length() - 6 ); + info.className = ClassFileUtils.convertSlashName( info.relativeName ); + classFileList.addElement( info ); + } + } + } + } + + private int deleteAffectedFiles( String className ) + { + int count = 0; + + Hashtable affectedClasses = ( Hashtable )affectedClassMap.get( className ); + if( affectedClasses != null ) + { + for( Enumeration e = affectedClasses.keys(); e.hasMoreElements(); ) + { + String affectedClassName = ( String )e.nextElement(); + ClassFileInfo affectedClassInfo = ( ClassFileInfo )affectedClasses.get( affectedClassName ); + if( affectedClassInfo.absoluteFile.exists() ) + { + log( "Deleting file " + affectedClassInfo.absoluteFile.getPath() + " since " + + className + " out of date", Project.MSG_VERBOSE ); + affectedClassInfo.absoluteFile.delete(); + count++; + if( closure ) + { + count += deleteAffectedFiles( affectedClassName ); + } + else + { + // without closure we may delete an inner class but not the + // top level class which would not trigger a recompile. + + if( affectedClassName.indexOf( "$" ) != -1 ) + { + // need to delete the main class + String topLevelClassName + = affectedClassName.substring( 0, affectedClassName.indexOf( "$" ) ); + log( "Top level class = " + topLevelClassName, Project.MSG_VERBOSE ); + ClassFileInfo topLevelClassInfo + = ( ClassFileInfo )classFileInfoMap.get( topLevelClassName ); + if( topLevelClassInfo != null && + topLevelClassInfo.absoluteFile.exists() ) + { + log( "Deleting file " + topLevelClassInfo.absoluteFile.getPath() + " since " + + "one of its inner classes was removed", Project.MSG_VERBOSE ); + topLevelClassInfo.absoluteFile.delete(); + count++; + if( closure ) + { + count += deleteAffectedFiles( topLevelClassName ); + } + } + } + } + } + } + } + return count; + } + + private int deleteAllAffectedFiles() + { + int count = 0; + for( Enumeration e = outOfDateClasses.elements(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + count += deleteAffectedFiles( className ); + ClassFileInfo classInfo = ( ClassFileInfo )classFileInfoMap.get( className ); + if( classInfo != null && classInfo.absoluteFile.exists() ) + { + classInfo.absoluteFile.delete(); + count++; + } + } + return count; + } + + + /** + * Determine the dependencies between classes. Class dependencies are + * determined by examining the class references in a class file to other + * classes + * + * @exception IOException Description of Exception + */ + private void determineDependencies() + throws IOException + { + affectedClassMap = new Hashtable(); + classFileInfoMap = new Hashtable(); + boolean cacheDirty = false; + + Hashtable dependencyMap = new Hashtable(); + File depCacheFile = null; + boolean depCacheFileExists = true; + long depCacheFileLastModified = Long.MAX_VALUE; + + // read the dependency cache from the disk + if( cache != null ) + { + dependencyMap = readCachedDependencies(); + depCacheFile = new File( cache, CACHE_FILE_NAME ); + depCacheFileExists = depCacheFile.exists(); + depCacheFileLastModified = depCacheFile.lastModified(); + } + for( Enumeration e = getClassFiles( destPath ).elements(); e.hasMoreElements(); ) + { + ClassFileInfo info = ( ClassFileInfo )e.nextElement(); + log( "Adding class info for " + info.className, Project.MSG_DEBUG ); + classFileInfoMap.put( info.className, info ); + + Vector dependencyList = null; + + if( cache != null ) + { + // try to read the dependency info from the map if it is not out of date + if( depCacheFileExists && depCacheFileLastModified > info.absoluteFile.lastModified() ) + { + // depFile exists and is newer than the class file + // need to get dependency list from the map. + dependencyList = ( Vector )dependencyMap.get( info.className ); + } + } + + if( dependencyList == null ) + { + // not cached - so need to read directly from the class file + FileInputStream inFileStream = null; + try + { + inFileStream = new FileInputStream( info.absoluteFile ); + ClassFile classFile = new ClassFile(); + classFile.read( inFileStream ); + + dependencyList = classFile.getClassRefs(); + if( dependencyList != null ) + { + cacheDirty = true; + dependencyMap.put( info.className, dependencyList ); + } + + } + finally + { + if( inFileStream != null ) + { + inFileStream.close(); + } + } + } + + // This class depends on each class in the dependency list. For each + // one of those, add this class into their affected classes list + for( Enumeration depEnum = dependencyList.elements(); depEnum.hasMoreElements(); ) + { + String dependentClass = ( String )depEnum.nextElement(); + + Hashtable affectedClasses = ( Hashtable )affectedClassMap.get( dependentClass ); + if( affectedClasses == null ) + { + affectedClasses = new Hashtable(); + affectedClassMap.put( dependentClass, affectedClasses ); + } + + affectedClasses.put( info.className, info ); + } + } + + classpathDependencies = null; + if( dependClasspath != null ) + { + // now determine which jars each class depends upon + classpathDependencies = new Hashtable(); + AntClassLoader loader = new AntClassLoader( getProject(), dependClasspath ); + + Hashtable classpathFileCache = new Hashtable(); + Object nullFileMarker = new Object(); + for( Enumeration e = dependencyMap.keys(); e.hasMoreElements(); ) + { + String className = ( String )e.nextElement(); + Vector dependencyList = ( Vector )dependencyMap.get( className ); + Hashtable dependencies = new Hashtable(); + classpathDependencies.put( className, dependencies ); + for( Enumeration e2 = dependencyList.elements(); e2.hasMoreElements(); ) + { + String dependency = ( String )e2.nextElement(); + Object classpathFileObject = classpathFileCache.get( dependency ); + if( classpathFileObject == null ) + { + classpathFileObject = nullFileMarker; + + if( !dependency.startsWith( "java." ) && !dependency.startsWith( "javax." ) ) + { + URL classURL = loader.getResource( dependency.replace( '.', '/' ) + ".class" ); + if( classURL != null ) + { + if( classURL.getProtocol().equals( "jar" ) ) + { + String jarFilePath = classURL.getFile(); + if( jarFilePath.startsWith( "file:" ) ) + { + int classMarker = jarFilePath.indexOf( '!' ); + jarFilePath = jarFilePath.substring( 5, classMarker ); + } + classpathFileObject = new File( jarFilePath ); + } + else if( classURL.getProtocol().equals( "file" ) ) + { + String classFilePath = classURL.getFile(); + classpathFileObject = new File( classFilePath ); + } + log( "Class " + className + + " depends on " + classpathFileObject + + " due to " + dependency, Project.MSG_DEBUG ); + } + } + classpathFileCache.put( dependency, classpathFileObject ); + } + if( classpathFileObject != null && classpathFileObject != nullFileMarker ) + { + // we need to add this jar to the list for this class. + File jarFile = ( File )classpathFileObject; + dependencies.put( jarFile, jarFile ); + } + } + } + } + + // write the dependency cache to the disk + if( cache != null && cacheDirty ) + { + writeCachedDependencies( dependencyMap ); + } + } + + /** + * Read the dependencies from cache file + * + * @return Description of the Returned Value + * @exception IOException Description of Exception + */ + private Hashtable readCachedDependencies() + throws IOException + { + Hashtable dependencyMap = new Hashtable(); + + if( cache != null ) + { + File depFile = new File( cache, CACHE_FILE_NAME ); + BufferedReader in = null; + if( depFile.exists() ) + { + try + { + in = new BufferedReader( new FileReader( depFile ) ); + String line = null; + Vector dependencyList = null; + String className = null; + int prependLength = CLASSNAME_PREPEND.length(); + while( ( line = in.readLine() ) != null ) + { + if( line.startsWith( CLASSNAME_PREPEND ) ) + { + dependencyList = new Vector(); + className = line.substring( prependLength ); + dependencyMap.put( className, dependencyList ); + } + else + { + dependencyList.addElement( line ); + } + } + } + finally + { + if( in != null ) + { + in.close(); + } + } + } + } + + return dependencyMap; + } + + /** + * Write the dependencies to cache file + * + * @param dependencyMap Description of Parameter + * @exception IOException Description of Exception + */ + private void writeCachedDependencies( Hashtable dependencyMap ) + throws IOException + { + if( cache != null ) + { + PrintWriter pw = null; + try + { + cache.mkdirs(); + File depFile = new File( cache, CACHE_FILE_NAME ); + + pw = new PrintWriter( new FileWriter( depFile ) ); + for( Enumeration deps = dependencyMap.keys(); deps.hasMoreElements(); ) + { + String className = ( String )deps.nextElement(); + + pw.println( CLASSNAME_PREPEND + className ); + + Vector dependencyList = ( Vector )dependencyMap.get( className ); + int size = dependencyList.size(); + for( int x = 0; x < size; x++ ) + { + pw.println( dependencyList.elementAt( x ) ); + } + } + } + finally + { + if( pw != null ) + { + pw.close(); + } + } + } + } + + /** + * A class (struct) user to manage information about a class + * + * @author RT + */ + private static class ClassFileInfo + { + /** + * The file where the class file is stored in the file system + */ + public File absoluteFile; + + /** + * The Java class name of this class + */ + public String className; + + /** + * The location of the file relative to its base directory - the root of + * the package namespace + */ + public String relativeName; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java new file mode 100644 index 000000000..e003411d4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/DirectoryIterator.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Stack; +import java.util.Vector; + +/** + * An iterator which iterates through the contents of a java directory. The + * iterator should be created with the directory at the root of the Java + * namespace. + * + * @author Conor MacNeill + */ +public class DirectoryIterator implements ClassFileIterator +{ + + /** + * The length of the root directory. This is used to remove the root + * directory from full paths. + */ + int rootLength; + + /** + * The current directory iterator. As directories encounter lower level + * directories, the current iterator is pushed onto the iterator stack and a + * new iterator over the sub directory becomes the current directory. This + * implements a depth first traversal of the directory namespace. + */ + private Enumeration currentEnum; + + /** + * This is a stack of current iterators supporting the depth first traversal + * of the directory tree. + */ + private Stack enumStack; + + /** + * Creates a directory iterator. The directory iterator is created to scan + * the root directory. If the changeInto flag is given, then the entries + * returned will be relative to this directory and not the current + * directory. + * + * @param rootDirectory the root if the directory namespace which is to be + * iterated over + * @param changeInto if true then the returned entries will be relative to + * the rootDirectory and not the current directory. + * @exception IOException Description of Exception + * @throws IOException if there is a problem reading the directory + * information. + */ + public DirectoryIterator( File rootDirectory, boolean changeInto ) + throws IOException + { + super(); + + enumStack = new Stack(); + + if( rootDirectory.isAbsolute() || changeInto ) + { + rootLength = rootDirectory.getPath().length() + 1; + } + else + { + rootLength = 0; + } + + Vector filesInRoot = getDirectoryEntries( rootDirectory ); + + currentEnum = filesInRoot.elements(); + } + + /** + * Template method to allow subclasses to supply elements for the iteration. + * The directory iterator maintains a stack of iterators covering each level + * in the directory hierarchy. The current iterator covers the current + * directory being scanned. If the next entry in that directory is a + * subdirectory, the current iterator is pushed onto the stack and a new + * iterator is created for the subdirectory. If the entry is a file, it is + * returned as the next element and the iterator remains valid. If there are + * no more entries in the current directory, the topmost iterator on the + * statck is popped off to become the current iterator. + * + * @return the next ClassFile in the iteration. + */ + public ClassFile getNextClassFile() + { + ClassFile nextElement = null; + + try + { + while( nextElement == null ) + { + if( currentEnum.hasMoreElements() ) + { + File element = ( File )currentEnum.nextElement(); + + if( element.isDirectory() ) + { + + // push the current iterator onto the stack and then + // iterate through this directory. + enumStack.push( currentEnum ); + + Vector files = getDirectoryEntries( element ); + + currentEnum = files.elements(); + } + else + { + + // we have a file. create a stream for it + FileInputStream inFileStream = new FileInputStream( element ); + + if( element.getName().endsWith( ".class" ) ) + { + + // create a data input stream from the jar input stream + ClassFile javaClass = new ClassFile(); + + javaClass.read( inFileStream ); + + nextElement = javaClass; + } + } + } + else + { + // this iterator is exhausted. Can we pop one off the stack + if( enumStack.empty() ) + { + break; + } + else + { + currentEnum = ( Enumeration )enumStack.pop(); + } + } + } + } + catch( IOException e ) + { + nextElement = null; + } + + return nextElement; + } + + /** + * Get a vector covering all the entries (files and subdirectories in a + * directory). + * + * @param directory the directory to be scanned. + * @return a vector containing File objects for each entry in the directory. + */ + private Vector getDirectoryEntries( File directory ) + { + Vector files = new Vector(); + + // File[] filesInDir = directory.listFiles(); + String[] filesInDir = directory.list(); + + if( filesInDir != null ) + { + int length = filesInDir.length; + + for( int i = 0; i < length; ++i ) + { + files.addElement( new File( directory, filesInDir[i] ) ); + } + } + + return files; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java new file mode 100644 index 000000000..1715255bf --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/JarFileIterator.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * A class file iterator which iterates through the contents of a Java jar file. + * + * @author Conor MacNeill + */ +public class JarFileIterator implements ClassFileIterator +{ + private ZipInputStream jarStream; + + public JarFileIterator( InputStream stream ) + throws IOException + { + super(); + + jarStream = new ZipInputStream( stream ); + } + + public ClassFile getNextClassFile() + { + ZipEntry jarEntry; + ClassFile nextElement = null; + + try + { + jarEntry = jarStream.getNextEntry(); + + while( nextElement == null && jarEntry != null ) + { + String entryName = jarEntry.getName(); + + if( !jarEntry.isDirectory() && entryName.endsWith( ".class" ) ) + { + + // create a data input stream from the jar input stream + ClassFile javaClass = new ClassFile(); + + javaClass.read( jarStream ); + + nextElement = javaClass; + } + else + { + + jarEntry = jarStream.getNextEntry(); + } + } + } + catch( IOException e ) + { + String message = e.getMessage(); + String text = e.getClass().getName(); + + if( message != null ) + { + text += ": " + message; + } + + throw new RuntimeException( "Problem reading JAR file: " + text ); + } + + return nextElement; + } + + private byte[] getEntryBytes( InputStream stream ) + throws IOException + { + byte[] buffer = new byte[8192]; + ByteArrayOutputStream baos = new ByteArrayOutputStream( 2048 ); + int n; + + while( ( n = stream.read( buffer, 0, buffer.length ) ) != -1 ) + { + baos.write( buffer, 0, n ); + } + + return baos.toByteArray(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java new file mode 100644 index 000000000..919486a22 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ClassCPInfo.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * The constant pool entry which stores class information. + * + * @author Conor MacNeill + */ +public class ClassCPInfo extends ConstantPoolEntry +{ + + /** + * The class' name. This will be only valid if the entry has been resolved + * against the constant pool. + */ + private String className; + + /** + * The index into the constant pool where this class' name is stored. If the + * class name is changed, this entry is invalid until this entry is + * connected to a constant pool. + */ + private int index; + + /** + * Constructor. Sets the tag value for this entry to type Class + */ + public ClassCPInfo() + { + super( CONSTANT_Class, 1 ); + } + + /** + * Get the class name of this entry. + * + * @return the class' name. + */ + public String getClassName() + { + return className; + } + + /** + * Read the entry from a stream. + * + * @param cpStream the stream containing the constant pool entry to be read. + * @exception IOException thrown if there is a problem reading the entry + * from the stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + index = cpStream.readUnsignedShort(); + className = "unresolved"; + } + + /** + * Resolve this class info against the given constant pool. + * + * @param constantPool the constant pool with which to resolve the class. + */ + public void resolve( ConstantPool constantPool ) + { + className = ( ( Utf8CPInfo )constantPool.getEntry( index ) ).getValue(); + + super.resolve( constantPool ); + } + + /** + * Generate a string readable version of this entry + * + * @return Description of the Returned Value + */ + public String toString() + { + return "Class Constant Pool Entry for " + className + "[" + index + "]"; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java new file mode 100644 index 000000000..f8ba55828 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantCPInfo.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; + + +/** + * A Constant Pool entry which represents a constant value. + * + * @author Conor MacNeill + */ +public abstract class ConstantCPInfo extends ConstantPoolEntry +{ + + /** + * The entry's untyped value. Each subclass interprets the constant value + * based on the subclass's type. The value here must be compatible. + */ + private Object value; + + /** + * Initialise the constant entry. + * + * @param tagValue the constant pool entry type to be used. + * @param entries the number of constant pool entry slots occupied by this + * entry. + */ + protected ConstantCPInfo( int tagValue, int entries ) + { + super( tagValue, entries ); + } + + /** + * Set the constant value. + * + * @param newValue the new untyped value of this constant. + */ + public void setValue( Object newValue ) + { + value = newValue; + } + + /** + * Get the value of the constant. + * + * @return the value of the constant (untyped). + */ + public Object getValue() + { + return value; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java new file mode 100644 index 000000000..ee3131ec3 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPool.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +/** + * The constant pool of a Java class. The constant pool is a collection of + * constants used in a Java class file. It stores strings, constant values, + * class names, method names, field names etc. + * + * @author Conor MacNeill + * @see The Java Virtual + * Machine Specification + */ +public class ConstantPool +{ + + /** + * The entries in the constant pool. + */ + private Vector entries; + + /** + * A Hashtable of UTF8 entries - used to get constant pool indexes of the + * UTF8 values quickly + */ + private Hashtable utf8Indexes; + + /** + * Initialise the constant pool. + */ + public ConstantPool() + { + entries = new Vector(); + + // The zero index is never present in the constant pool itself so + // we add a null entry for it + entries.addElement( null ); + + utf8Indexes = new Hashtable(); + } + + /** + * Get the index of a given CONSTANT_Class entry in the constant pool. + * + * @param className the name of the class for which the class entry index is + * required. + * @return the index at which the given class entry occurs in the constant + * pool or -1 if the value does not occur. + */ + public int getClassEntry( String className ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof ClassCPInfo ) + { + ClassCPInfo classinfo = ( ClassCPInfo )element; + + if( classinfo.getClassName().equals( className ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given constant value entry in the constant pool. + * + * @param constantValue the constant value for which the index is required. + * @return the index at which the given value entry occurs in the constant + * pool or -1 if the value does not occur. + */ + public int getConstantEntry( Object constantValue ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof ConstantCPInfo ) + { + ConstantCPInfo constantEntry = ( ConstantCPInfo )element; + + if( constantEntry.getValue().equals( constantValue ) ) + { + index = i; + } + } + } + + return index; + } + + + /** + * Get an constant pool entry at a particular index. + * + * @param index the index into the constant pool. + * @return the constant pool entry at that index. + */ + public ConstantPoolEntry getEntry( int index ) + { + return ( ConstantPoolEntry )entries.elementAt( index ); + } + + /** + * Get the index of a given CONSTANT_FieldRef entry in the constant pool. + * + * @param fieldClassName the name of the class which contains the field + * being referenced. + * @param fieldName the name of the field being referenced. + * @param fieldType the type descriptor of the field being referenced. + * @return the index at which the given field ref entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getFieldRefEntry( String fieldClassName, String fieldName, String fieldType ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof FieldRefCPInfo ) + { + FieldRefCPInfo fieldRefEntry = ( FieldRefCPInfo )element; + + if( fieldRefEntry.getFieldClassName().equals( fieldClassName ) && fieldRefEntry.getFieldName().equals( fieldName ) + && fieldRefEntry.getFieldType().equals( fieldType ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given CONSTANT_InterfaceMethodRef entry in the + * constant pool. + * + * @param interfaceMethodClassName the name of the interface which contains + * the method being referenced. + * @param interfaceMethodName the name of the method being referenced. + * @param interfaceMethodType the type descriptor of the metho dbeing + * referenced. + * @return the index at which the given method ref entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getInterfaceMethodRefEntry( String interfaceMethodClassName, String interfaceMethodName, String interfaceMethodType ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof InterfaceMethodRefCPInfo ) + { + InterfaceMethodRefCPInfo interfaceMethodRefEntry = ( InterfaceMethodRefCPInfo )element; + + if( interfaceMethodRefEntry.getInterfaceMethodClassName().equals( interfaceMethodClassName ) + && interfaceMethodRefEntry.getInterfaceMethodName().equals( interfaceMethodName ) + && interfaceMethodRefEntry.getInterfaceMethodType().equals( interfaceMethodType ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given CONSTANT_MethodRef entry in the constant pool. + * + * @param methodClassName the name of the class which contains the method + * being referenced. + * @param methodName the name of the method being referenced. + * @param methodType the type descriptor of the metho dbeing referenced. + * @return the index at which the given method ref entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getMethodRefEntry( String methodClassName, String methodName, String methodType ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof MethodRefCPInfo ) + { + MethodRefCPInfo methodRefEntry = ( MethodRefCPInfo )element; + + if( methodRefEntry.getMethodClassName().equals( methodClassName ) + && methodRefEntry.getMethodName().equals( methodName ) && methodRefEntry.getMethodType().equals( methodType ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given CONSTANT_NameAndType entry in the constant pool. + * + * @param name the name + * @param type the type + * @return the index at which the given NameAndType entry occurs in the + * constant pool or -1 if the value does not occur. + */ + public int getNameAndTypeEntry( String name, String type ) + { + int index = -1; + + for( int i = 0; i < entries.size() && index == -1; ++i ) + { + Object element = entries.elementAt( i ); + + if( element instanceof NameAndTypeCPInfo ) + { + NameAndTypeCPInfo nameAndTypeEntry = ( NameAndTypeCPInfo )element; + + if( nameAndTypeEntry.getName().equals( name ) && nameAndTypeEntry.getType().equals( type ) ) + { + index = i; + } + } + } + + return index; + } + + /** + * Get the index of a given UTF8 constant pool entry. + * + * @param value the string value of the UTF8 entry. + * @return the index at which the given string occurs in the constant pool + * or -1 if the value does not occur. + */ + public int getUTF8Entry( String value ) + { + int index = -1; + Integer indexInteger = ( Integer )utf8Indexes.get( value ); + + if( indexInteger != null ) + { + index = indexInteger.intValue(); + } + + return index; + } + + /** + * Add an entry to the constant pool. + * + * @param entry the new entry to be added to the constant pool. + * @return the index into the constant pool at which the entry is stored. + */ + public int addEntry( ConstantPoolEntry entry ) + { + int index = entries.size(); + + entries.addElement( entry ); + + int numSlots = entry.getNumEntries(); + + // add null entries for any additional slots required. + for( int j = 0; j < numSlots - 1; ++j ) + { + entries.addElement( null ); + } + + if( entry instanceof Utf8CPInfo ) + { + Utf8CPInfo utf8Info = ( Utf8CPInfo )entry; + + utf8Indexes.put( utf8Info.getValue(), new Integer( index ) ); + } + + return index; + } + + /** + * Read the constant pool from a class input stream. + * + * @param classStream the DataInputStream of a class file. + * @throws IOException if there is a problem reading the constant pool from + * the stream + */ + public void read( DataInputStream classStream ) + throws IOException + { + int numEntries = classStream.readUnsignedShort(); + + for( int i = 1; i < numEntries; ) + { + ConstantPoolEntry nextEntry = ConstantPoolEntry.readEntry( classStream ); + + i += nextEntry.getNumEntries(); + + addEntry( nextEntry ); + } + } + + /** + * Resolve the entries in the constant pool. Resolution of the constant pool + * involves transforming indexes to other constant pool entries into the + * actual data for that entry. + */ + public void resolve() + { + for( Enumeration i = entries.elements(); i.hasMoreElements(); ) + { + ConstantPoolEntry poolInfo = ( ConstantPoolEntry )i.nextElement(); + + if( poolInfo != null && !poolInfo.isResolved() ) + { + poolInfo.resolve( this ); + } + } + } + + /** + * Get the size of the constant pool. + * + * @return Description of the Returned Value + */ + public int size() + { + return entries.size(); + } + + /** + * Dump the constant pool to a string. + * + * @return the constant pool entries as strings + */ + public String toString() + { + StringBuffer sb = new StringBuffer( "\n" ); + int size = entries.size(); + + for( int i = 0; i < size; ++i ) + { + sb.append( "[" + i + "] = " + getEntry( i ) + "\n" ); + } + + return sb.toString(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java new file mode 100644 index 000000000..7e1a89c31 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/ConstantPoolEntry.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * An entry in the constant pool. This class contains a represenation of the + * constant pool entries. It is an abstract base class for all the different + * forms of constant pool entry. + * + * @author Conor MacNeill + * @see ConstantPool + */ +public abstract class ConstantPoolEntry +{ + + /** + * Tag value for UTF8 entries. + */ + public final static int CONSTANT_Utf8 = 1; + + /** + * Tag value for Integer entries. + */ + public final static int CONSTANT_Integer = 3; + + /** + * Tag value for Float entries. + */ + public final static int CONSTANT_Float = 4; + + /** + * Tag value for Long entries. + */ + public final static int CONSTANT_Long = 5; + + /** + * Tag value for Double entries. + */ + public final static int CONSTANT_Double = 6; + + /** + * Tag value for Class entries. + */ + public final static int CONSTANT_Class = 7; + + /** + * Tag value for String entries. + */ + public final static int CONSTANT_String = 8; + + /** + * Tag value for Field Reference entries. + */ + public final static int CONSTANT_FieldRef = 9; + + /** + * Tag value for Method Reference entries. + */ + public final static int CONSTANT_MethodRef = 10; + + /** + * Tag value for Interface Method Reference entries. + */ + public final static int CONSTANT_InterfaceMethodRef = 11; + + /** + * Tag value for Name and Type entries. + */ + public final static int CONSTANT_NameAndType = 12; + + /** + * The number of slots in the constant pool, occupied by this entry. + */ + private int numEntries; + + /** + * A flag which indiciates if this entry has been resolved or not. + */ + private boolean resolved; + + /** + * This entry's tag which identifies the type of this constant pool entry. + */ + private int tag; + + /** + * Initialse the constant pool entry. + * + * @param tagValue the tag value which identifies which type of constant + * pool entry this is. + * @param entries the number of constant pool entry slots this entry + * occupies. + */ + public ConstantPoolEntry( int tagValue, int entries ) + { + tag = tagValue; + numEntries = entries; + resolved = false; + } + + /** + * Read a constant pool entry from a stream. This is a factory method which + * reads a constant pool entry form a stream and returns the appropriate + * subclass for the entry. + * + * @param cpStream the stream from which the constant pool entry is to be + * read. + * @return Description of the Returned Value + * @exception IOException Description of Exception + * @returns the appropriate ConstantPoolEntry subclass representing the + * constant pool entry from the stream. + * @throws IOExcception if there is a problem reading the entry from the + * stream. + */ + public static ConstantPoolEntry readEntry( DataInputStream cpStream ) + throws IOException + { + ConstantPoolEntry cpInfo = null; + int cpTag = cpStream.readUnsignedByte(); + + switch ( cpTag ) + { + + case CONSTANT_Utf8: + cpInfo = new Utf8CPInfo(); + + break; + case CONSTANT_Integer: + cpInfo = new IntegerCPInfo(); + + break; + case CONSTANT_Float: + cpInfo = new FloatCPInfo(); + + break; + case CONSTANT_Long: + cpInfo = new LongCPInfo(); + + break; + case CONSTANT_Double: + cpInfo = new DoubleCPInfo(); + + break; + case CONSTANT_Class: + cpInfo = new ClassCPInfo(); + + break; + case CONSTANT_String: + cpInfo = new StringCPInfo(); + + break; + case CONSTANT_FieldRef: + cpInfo = new FieldRefCPInfo(); + + break; + case CONSTANT_MethodRef: + cpInfo = new MethodRefCPInfo(); + + break; + case CONSTANT_InterfaceMethodRef: + cpInfo = new InterfaceMethodRefCPInfo(); + + break; + case CONSTANT_NameAndType: + cpInfo = new NameAndTypeCPInfo(); + + break; + default: + throw new ClassFormatError( "Invalid Constant Pool entry Type " + cpTag ); + } + + cpInfo.read( cpStream ); + + return cpInfo; + } + + /** + * Get the number of Constant Pool Entry slots within the constant pool + * occupied by this entry. + * + * @return the number of slots used. + */ + public final int getNumEntries() + { + return numEntries; + } + + /** + * Get the Entry's type tag. + * + * @return The Tag value of this entry + */ + public int getTag() + { + return tag; + } + + /** + * Indicates whether this entry has been resolved. In general a constant + * pool entry can reference another constant pool entry by its index value. + * Resolution involves replacing this index value with the constant pool + * entry at that index. + * + * @return true if this entry has been resolved. + */ + public boolean isResolved() + { + return resolved; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public abstract void read( DataInputStream cpStream ) + throws IOException; + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + resolved = true; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java new file mode 100644 index 000000000..94122a060 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/DoubleCPInfo.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * The constant pool entry subclass used to represent double constant values. + * + * @author Conor MacNeill + */ +public class DoubleCPInfo extends ConstantCPInfo +{ + public DoubleCPInfo() + { + super( CONSTANT_Double, 2 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Double( cpStream.readDouble() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Double Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java new file mode 100644 index 000000000..bf56aee85 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FieldRefCPInfo.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A FieldRef CP Info + * + * @author Conor MacNeill + */ +public class FieldRefCPInfo extends ConstantPoolEntry +{ + private int classIndex; + private String fieldClassName; + private String fieldName; + private String fieldType; + private int nameAndTypeIndex; + + /** + * Constructor. + */ + public FieldRefCPInfo() + { + super( CONSTANT_FieldRef, 1 ); + } + + public String getFieldClassName() + { + return fieldClassName; + } + + public String getFieldName() + { + return fieldName; + } + + public String getFieldType() + { + return fieldType; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + classIndex = cpStream.readUnsignedShort(); + nameAndTypeIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + ClassCPInfo fieldClass = ( ClassCPInfo )constantPool.getEntry( classIndex ); + + fieldClass.resolve( constantPool ); + + fieldClassName = fieldClass.getClassName(); + + NameAndTypeCPInfo nt = ( NameAndTypeCPInfo )constantPool.getEntry( nameAndTypeIndex ); + + nt.resolve( constantPool ); + + fieldName = nt.getName(); + fieldType = nt.getType(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "Field : Class = " + fieldClassName + ", name = " + fieldName + ", type = " + fieldType; + } + else + { + value = "Field : Class index = " + classIndex + ", name and type index = " + nameAndTypeIndex; + } + + return value; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java new file mode 100644 index 000000000..affdeefc1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/FloatCPInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * A Float CP Info + * + * @author Conor MacNeill + */ +public class FloatCPInfo extends ConstantCPInfo +{ + + /** + * Constructor. + */ + public FloatCPInfo() + { + super( CONSTANT_Float, 1 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Float( cpStream.readFloat() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Float Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java new file mode 100644 index 000000000..ce7acbc52 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/IntegerCPInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * An Integer CP Info + * + * @author Conor MacNeill + */ +public class IntegerCPInfo extends ConstantCPInfo +{ + + /** + * Constructor. + */ + public IntegerCPInfo() + { + super( CONSTANT_Integer, 1 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Integer( cpStream.readInt() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Integer Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java new file mode 100644 index 000000000..bc2077fed --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/InterfaceMethodRefCPInfo.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A InterfaceMethodRef CP Info + * + * @author Conor MacNeill + */ +public class InterfaceMethodRefCPInfo extends ConstantPoolEntry +{ + private int classIndex; + private String interfaceMethodClassName; + private String interfaceMethodName; + private String interfaceMethodType; + private int nameAndTypeIndex; + + /** + * Constructor. + */ + public InterfaceMethodRefCPInfo() + { + super( CONSTANT_InterfaceMethodRef, 1 ); + } + + public String getInterfaceMethodClassName() + { + return interfaceMethodClassName; + } + + public String getInterfaceMethodName() + { + return interfaceMethodName; + } + + public String getInterfaceMethodType() + { + return interfaceMethodType; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + classIndex = cpStream.readUnsignedShort(); + nameAndTypeIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + ClassCPInfo interfaceMethodClass = ( ClassCPInfo )constantPool.getEntry( classIndex ); + + interfaceMethodClass.resolve( constantPool ); + + interfaceMethodClassName = interfaceMethodClass.getClassName(); + + NameAndTypeCPInfo nt = ( NameAndTypeCPInfo )constantPool.getEntry( nameAndTypeIndex ); + + nt.resolve( constantPool ); + + interfaceMethodName = nt.getName(); + interfaceMethodType = nt.getType(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "InterfaceMethod : Class = " + interfaceMethodClassName + ", name = " + interfaceMethodName + ", type = " + + interfaceMethodType; + } + else + { + value = "InterfaceMethod : Class index = " + classIndex + ", name and type index = " + nameAndTypeIndex; + } + + return value; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java new file mode 100644 index 000000000..12f981faf --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/LongCPInfo.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + +/** + * A Long CP Info + * + * @author Conor MacNeill + */ +public class LongCPInfo extends ConstantCPInfo +{ + + /** + * Constructor. + */ + public LongCPInfo() + { + super( CONSTANT_Long, 2 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + setValue( new Long( cpStream.readLong() ) ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "Long Constant Pool Entry: " + getValue(); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java new file mode 100644 index 000000000..a284adcf9 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/MethodRefCPInfo.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A MethodRef CP Info + * + * @author Conor MacNeill + */ +public class MethodRefCPInfo extends ConstantPoolEntry +{ + private int classIndex; + private String methodClassName; + private String methodName; + private String methodType; + private int nameAndTypeIndex; + + /** + * Constructor. + */ + public MethodRefCPInfo() + { + super( CONSTANT_MethodRef, 1 ); + } + + public String getMethodClassName() + { + return methodClassName; + } + + public String getMethodName() + { + return methodName; + } + + public String getMethodType() + { + return methodType; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + classIndex = cpStream.readUnsignedShort(); + nameAndTypeIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + ClassCPInfo methodClass = ( ClassCPInfo )constantPool.getEntry( classIndex ); + + methodClass.resolve( constantPool ); + + methodClassName = methodClass.getClassName(); + + NameAndTypeCPInfo nt = ( NameAndTypeCPInfo )constantPool.getEntry( nameAndTypeIndex ); + + nt.resolve( constantPool ); + + methodName = nt.getName(); + methodType = nt.getType(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "Method : Class = " + methodClassName + ", name = " + methodName + ", type = " + methodType; + } + else + { + value = "Method : Class index = " + classIndex + ", name and type index = " + nameAndTypeIndex; + } + + return value; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java new file mode 100644 index 000000000..8b769629b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/NameAndTypeCPInfo.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A NameAndType CP Info + * + * @author Conor MacNeill + */ +public class NameAndTypeCPInfo extends ConstantPoolEntry +{ + private int descriptorIndex; + + private String name; + private int nameIndex; + private String type; + + /** + * Constructor. + */ + public NameAndTypeCPInfo() + { + super( CONSTANT_NameAndType, 1 ); + } + + public String getName() + { + return name; + } + + public String getType() + { + return type; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + nameIndex = cpStream.readUnsignedShort(); + descriptorIndex = cpStream.readUnsignedShort(); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + name = ( ( Utf8CPInfo )constantPool.getEntry( nameIndex ) ).getValue(); + type = ( ( Utf8CPInfo )constantPool.getEntry( descriptorIndex ) ).getValue(); + + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + String value; + + if( isResolved() ) + { + value = "Name = " + name + ", type = " + type; + } + else + { + value = "Name index = " + nameIndex + ", descriptor index = " + descriptorIndex; + } + + return value; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java new file mode 100644 index 000000000..f65ad291b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/StringCPInfo.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A String Constant Pool Entry. The String info contains an index into the + * constant pool where a UTF8 string is stored. + * + * @author Conor MacNeill + */ +public class StringCPInfo extends ConstantCPInfo +{ + + private int index; + + /** + * Constructor. + */ + public StringCPInfo() + { + super( CONSTANT_String, 1 ); + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + index = cpStream.readUnsignedShort(); + + setValue( "unresolved" ); + } + + /** + * Resolve this constant pool entry with respect to its dependents in the + * constant pool. + * + * @param constantPool the constant pool of which this entry is a member and + * against which this entry is to be resolved. + */ + public void resolve( ConstantPool constantPool ) + { + setValue( ( ( Utf8CPInfo )constantPool.getEntry( index ) ).getValue() ); + super.resolve( constantPool ); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "String Constant Pool Entry for " + getValue() + "[" + index + "]"; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java new file mode 100644 index 000000000..dc42d1984 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/depend/constantpool/Utf8CPInfo.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.depend.constantpool; +import java.io.DataInputStream; +import java.io.IOException; + + +/** + * A UTF8 Constant Pool Entry. + * + * @author Conor MacNeill + */ +public class Utf8CPInfo extends ConstantPoolEntry +{ + private String value; + + /** + * Constructor. + */ + public Utf8CPInfo() + { + super( CONSTANT_Utf8, 1 ); + } + + public String getValue() + { + return value; + } + + /** + * read a constant pool entry from a class stream. + * + * @param cpStream the DataInputStream which contains the constant pool + * entry to be read. + * @throws IOException if there is a problem reading the entry from the + * stream. + */ + public void read( DataInputStream cpStream ) + throws IOException + { + value = cpStream.readUTF(); + } + + /** + * Print a readable version of the constant pool entry. + * + * @return the string representation of this constant pool entry. + */ + public String toString() + { + return "UTF8 Value = " + value; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java new file mode 100644 index 000000000..371264a4a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/CSharp.java @@ -0,0 +1,965 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.dotnet; +import java.io.File;// ==================================================================== +// imports +// ==================================================================== +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.types.Path; + + +// ==================================================================== +/** + * This task compiles CSharp source into executables or modules. The task will + * only work on win2K until other platforms support csc.exe or an equivalent. + * CSC.exe must be on the execute path too.

              + * + * All parameters are optional: <csc/> should suffice to produce a debug + * build of all *.cs files. References to external files do require explicit + * enumeration, so are one of the first attributes to consider adding.

              + * + * The task is a directory based task, so attributes like includes="*.cs" + * and excludes="broken.cs" can be used to control the files pulled in. + * By default, all *.cs files from the project folder down are included in the + * command. When this happens the output file -if not specified- is taken as the + * first file in the list, which may be somewhat hard to control. Specifying the + * output file with 'outfile' seems prudent.

              + * + *

              + * + * TODO + *

                + *
              1. is incremental build still broken in beta-1? + *
              2. is Win32Icon broken? + *
              3. all the missing options + *
              + *

              + * + * History + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
              + * 0.3 + * + * Beta 1 edition + * + * To avoid having to remember which assemblies to include, the task + * automatically refers to the main dotnet libraries in Beta1. + *
              + * 0.2 + * + * Slightly different + * + * Split command execution to a separate class; + *
              + * 0.1 + * + * "I can't believe it's so rudimentary" + * + * First pass; minimal builds only support; + *
              + * + * + * @author Steve Loughran steve_l@iseran.com + * @version 0.3 + */ + +public class CSharp + extends org.apache.tools.ant.taskdefs.MatchingTask +{ + + /** + * name of the executable. the .exe suffix is deliberately not included in + * anticipation of the unix version + */ + protected final static String csc_exe_name = "csc"; + + /** + * what is the file extension we search on? + */ + protected final static String csc_file_ext = "cs"; + + /** + * derive the search pattern from the extension + */ + protected final static String csc_file_pattern = "**/*." + csc_file_ext; + + /** + * Fix C# reference inclusion. C# is really dumb in how it handles + * inclusion. You have to list every 'assembly' -read DLL that is imported. + * So already you are making a platform assumption -shared libraries have a + * .dll;"+ extension and the poor developer has to know every library which + * is included why the compiler cant find classes on the path or in a + * directory, is a mystery. To reduce the need to be explicit, here is a + * long list of the core libraries used in Beta-1 of .NET ommitting the + * blatantly non portable (MS.win32.interop) and the .designer libraries. + * (ripping out Com was tempting) Casing is chosen to match that of the file + * system exactly so may work on a unix box too. + */ + + protected final static String DEFAULT_REFERENCE_LIST = + "Accessibility.dll;" + + "cscompmgd.dll;" + + "CustomMarshalers.dll;" + + "IEExecRemote.dll;" + + "IEHost.dll;" + + "IIEHost.dll;" + + "ISymWrapper.dll;" + + "Microsoft.JScript.dll;" + + "Microsoft.VisualBasic.dll;" + + "Microsoft.VisualC.dll;" + + "Microsoft.Vsa.dll;" + + "Mscorcfg.dll;" + + "RegCode.dll;" + + "System.Configuration.Install.dll;" + + "System.Data.dll;" + + "System.Design.dll;" + + "System.DirectoryServices.dll;" + + "System.EnterpriseServices.dll;" + + "System.dll;" + + "System.Drawing.Design.dll;" + + "System.Drawing.dll;" + + "System.Management.dll;" + + "System.Messaging.dll;" + + "System.Runtime.Remoting.dll;" + + "System.Runtime.Serialization.Formatters.Soap.dll;" + + "System.Security.dll;" + + "System.ServiceProcess.dll;" + + "System.Web.dll;" + + "System.Web.RegularExpressions.dll;" + + "System.Web.Services.dll;" + + "System.Windows.Forms.dll;" + + "System.XML.dll;"; + + /** + * utf out flag + */ + + protected boolean _utf8output = false; + + protected boolean _noconfig = false; + + // /fullpaths + protected boolean _fullpaths = false; + + /** + * debug flag. Controls generation of debug information. + */ + protected boolean _debug; + + /** + * output XML documentation flag + */ + protected File _docFile; + + /** + * any extra command options? + */ + protected String _extraOptions; + + /** + * flag to control action on execution trouble + */ + protected boolean _failOnError; + + /** + * flag to enable automatic reference inclusion + */ + protected boolean _includeDefaultReferences; + + /** + * incremental build flag + */ + protected boolean _incremental; + + /** + * main class (or null for automatic choice) + */ + protected String _mainClass; + + /** + * optimise flag + */ + protected boolean _optimize; + + /** + * output file. If not supplied this is derived from the source file + */ + protected File _outputFile; + + /** + * using the path approach didnt work as it could not handle the implicit + * execution path. Perhaps that could be extracted from the runtime and then + * the path approach would be viable + */ + protected Path _referenceFiles; + + /** + * list of reference classes. (pretty much a classpath equivalent) + */ + protected String _references; + + /** + * type of target. Should be one of exe|library|module|winexe|(null) default + * is exe; the actual value (if not null) is fed to the command line.
              + * See /target + */ + protected String _targetType; + + /** + * enable unsafe code flag. Clearly set to false by default + */ + protected boolean _unsafe; + + /** + * icon for incorporation into apps + */ + protected File _win32icon; + /** + * icon for incorporation into apps + */ + protected File _win32res; + + /** + * list of extra modules to refer to + */ + String _additionalModules; + + /** + * defines list something like 'RELEASE;WIN32;NO_SANITY_CHECKS;;SOMETHING_ELSE' + */ + String _definitions; + + /** + * destination directory (null means use the source directory) NB: this is + * currently not used + */ + private File _destDir; + + /** + * source directory upon which the search pattern is applied + */ + private File _srcDir; + + /** + * warning level: 0-4, with 4 being most verbose + */ + private int _warnLevel; + + /** + * constructor inits everything and set up the search pattern + */ + + public CSharp() + { + Clear(); + setIncludes( csc_file_pattern ); + } + + /** + * Set the definitions + * + * @param params The new AdditionalModules value + */ + public void setAdditionalModules( String params ) + { + _additionalModules = params; + } + + /** + * set the debug flag on or off + * + * @param f on/off flag + */ + public void setDebug( boolean f ) + { + _debug = f; + } + + /** + * Set the definitions + * + * @param params The new Definitions value + */ + public void setDefinitions( String params ) + { + _definitions = params; + } + + /** + * Set the destination dir to find the files to be compiled + * + * @param dirName The new DestDir value + */ + public void setDestDir( File dirName ) + { + _destDir = dirName; + } + + /** + * file for generated XML documentation + * + * @param f output file + */ + public void setDocFile( File f ) + { + _docFile = f; + } + + /** + * Sets the ExtraOptions attribute + * + * @param extraOptions The new ExtraOptions value + */ + public void setExtraOptions( String extraOptions ) + { + this._extraOptions = extraOptions; + } + + /** + * set fail on error flag + * + * @param b The new FailOnError value + */ + public void setFailOnError( boolean b ) + { + _failOnError = b; + } + + public void setFullPaths( boolean enabled ) + { + _fullpaths = enabled; + } + + /** + * set the automatic reference inclusion flag on or off this flag controls + * the string of references and the /nostdlib option in CSC + * + * @param f on/off flag + */ + public void setIncludeDefaultReferences( boolean f ) + { + _includeDefaultReferences = f; + } + + /** + * set the incremental compilation flag on or off + * + * @param f on/off flag + */ + public void setIncremental( boolean f ) + { + _incremental = f; + } + + /** + * Sets the MainClass attribute + * + * @param mainClass The new MainClass value + */ + public void setMainClass( String mainClass ) + { + this._mainClass = mainClass; + } + + /** + * set the optimise flag on or off + * + * @param f on/off flag + */ + public void setOptimize( boolean f ) + { + _optimize = f; + } + + /** + * Set the definitions + * + * @param params The new OutputFile value + */ + public void setOutputFile( File params ) + { + _outputFile = params; + } + + /** + * add another path to the reference file path list + * + * @param path another path to append + */ + public void setReferenceFiles( Path path ) + { + //demand create pathlist + if( _referenceFiles == null ) + _referenceFiles = new Path( this.project ); + _referenceFiles.append( path ); + } + + /** + * Set the reference list to be used for this compilation. + * + * @param s The new References value + */ + public void setReferences( String s ) + { + _references = s; + } + + /** + * Set the source dir to find the files to be compiled + * + * @param srcDirName The new SrcDir value + */ + public void setSrcDir( File srcDirName ) + { + _srcDir = srcDirName; + } + + /** + * define the target + * + * @param targetType The new TargetType value + * @exception BuildException if target is not one of + * exe|library|module|winexe + */ + public void setTargetType( String targetType ) + throws BuildException + { + targetType = targetType.toLowerCase(); + if( targetType.equals( "exe" ) || targetType.equals( "library" ) || + targetType.equals( "module" ) || targetType.equals( "winexe" ) ) + { + _targetType = targetType; + } + else + throw new BuildException( "targetType " + targetType + " is not a valid type" ); + } + + /** + * Sets the Unsafe attribute + * + * @param unsafe The new Unsafe value + */ + public void setUnsafe( boolean unsafe ) + { + this._unsafe = unsafe; + } + + /** + * enable generation of utf8 output from the compiler. + * + * @param enabled The new Utf8Output value + */ + public void setUtf8Output( boolean enabled ) + { + _utf8output = enabled; + } + + /** + * set warn level (no range checking) + * + * @param warnLevel warn level -see .net docs for valid range (probably 0-4) + */ + public void setWarnLevel( int warnLevel ) + { + this._warnLevel = warnLevel; + } + + /** + * Set the win32 icon + * + * @param fileName path to the file. Can be relative, absolute, whatever. + */ + public void setWin32Icon( File fileName ) + { + _win32icon = fileName; + } + + /** + * Set the win32 icon + * + * @param fileName path to the file. Can be relative, absolute, whatever. + */ + public void setWin32Res( File fileName ) + { + _win32res = fileName; + } + + /** + * query the debug flag + * + * @return true if debug is turned on + */ + public boolean getDebug() + { + return _debug; + } + + /** + * Gets the ExtraOptions attribute + * + * @return The ExtraOptions value + */ + public String getExtraOptions() + { + return this._extraOptions; + } + + /** + * query fail on error flag + * + * @return The FailFailOnError value + */ + public boolean getFailFailOnError() + { + return _failOnError; + } + + /** + * query the optimise flag + * + * @return true if optimise is turned on + */ + public boolean getIncludeDefaultReferences() + { + return _includeDefaultReferences; + } + + /** + * query the incrementalflag + * + * @return true iff incremental compilation is turned on + */ + public boolean getIncremental() + { + return _incremental; + } + + /** + * Gets the MainClass attribute + * + * @return The MainClass value + */ + public String getMainClass() + { + return this._mainClass; + } + + /** + * query the optimise flag + * + * @return true if optimise is turned on + */ + public boolean getOptimize() + { + return _optimize; + } + + /** + * Gets the TargetType attribute + * + * @return The TargetType value + */ + public String getTargetType() + { + return _targetType; + } + + /** + * query the Unsafe attribute + * + * @return The Unsafe value + */ + public boolean getUnsafe() + { + return this._unsafe; + } + + /** + * query warn level + * + * @return current value + */ + public int getWarnLevel() + { + return _warnLevel; + } + + /** + * reset all contents. + */ + public void Clear() + { + _targetType = null; + _win32icon = null; + _srcDir = null; + _destDir = null; + _mainClass = null; + _unsafe = false; + _warnLevel = 3; + _docFile = null; + _incremental = false; + _optimize = false; + _debug = true; + _references = null; + _failOnError = true; + _definitions = null; + _additionalModules = null; + _includeDefaultReferences = true; + _extraOptions = null; + _fullpaths = true; + } + + /** + * do the work by building the command line and then calling it + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( _srcDir == null ) + _srcDir = project.resolveFile( "." ); + + NetCommand command = new NetCommand( this, "CSC", csc_exe_name ); + command.setFailOnError( getFailFailOnError() ); + //DEBUG helper + command.setTraceCommandLine( true ); + //fill in args + command.addArgument( "/nologo" ); + command.addArgument( getAdditionalModulesParameter() ); + command.addArgument( getDefinitionsParameter() ); + command.addArgument( getDebugParameter() ); + command.addArgument( getDocFileParameter() ); + command.addArgument( getIncrementalParameter() ); + command.addArgument( getMainClassParameter() ); + command.addArgument( getOptimizeParameter() ); + command.addArgument( getReferencesParameter() ); + command.addArgument( getTargetTypeParameter() ); + command.addArgument( getUnsafeParameter() ); + command.addArgument( getWarnLevelParameter() ); + command.addArgument( getWin32IconParameter() ); + command.addArgument( getOutputFileParameter() ); + command.addArgument( getIncludeDefaultReferencesParameter() ); + command.addArgument( getDefaultReferenceParameter() ); + command.addArgument( getWin32ResParameter() ); + command.addArgument( getUtf8OutpuParameter() ); + command.addArgument( getNoConfigParameter() ); + command.addArgument( getFullPathsParameter() ); + command.addArgument( getExtraOptionsParameter() ); + + //get dependencies list. + DirectoryScanner scanner = super.getDirectoryScanner( _srcDir ); + String[] dependencies = scanner.getIncludedFiles(); + log( "compiling " + dependencies.length + " file" + ( ( dependencies.length == 1 ) ? "" : "s" ) ); + String baseDir = scanner.getBasedir().toString(); + //add to the command + for( int i = 0; i < dependencies.length; i++ ) + { + String targetFile = dependencies[i]; + targetFile = baseDir + File.separator + targetFile; + command.addArgument( targetFile ); + } + + //now run the command of exe + settings + files + command.runCommand(); + } + + protected void setNoConfig( boolean enabled ) + { + _noconfig = enabled; + } + + /** + * get the argument or null for no argument needed + * + * @return The AdditionalModules Parameter to CSC + */ + protected String getAdditionalModulesParameter() + { + if( notEmpty( _additionalModules ) ) + return "/addmodule:" + _additionalModules; + else + return null; + } + + /** + * get the debug switch argument + * + * @return The Debug Parameter to CSC + */ + protected String getDebugParameter() + { + return "/debug" + ( _debug ? "+" : "-" ); + } + + + /** + * get default reference list + * + * @return null or a string of references. + */ + protected String getDefaultReferenceParameter() + { + if( _includeDefaultReferences ) + { + StringBuffer s = new StringBuffer( "/reference:" ); + s.append( DEFAULT_REFERENCE_LIST ); + return new String( s ); + } + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The Definitions Parameter to CSC + */ + protected String getDefinitionsParameter() + { + if( notEmpty( _definitions ) ) + return "/define:" + _definitions; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The DocFile Parameter to CSC + */ + protected String getDocFileParameter() + { + if( _docFile != null ) + return "/doc:" + _docFile.toString(); + else + return null; + } + + /** + * get any extra options or null for no argument needed + * + * @return The ExtraOptions Parameter to CSC + */ + protected String getExtraOptionsParameter() + { + if( _extraOptions != null && _extraOptions.length() != 0 ) + return _extraOptions; + else + return null; + } + + protected String getFullPathsParameter() + { + return _fullpaths ? "/fullpaths" : null; + } + + /** + * get the include default references flag or null for no argument needed + * + * @return The Parameter to CSC + */ + protected String getIncludeDefaultReferencesParameter() + { + return "/nostdlib" + ( _includeDefaultReferences ? "-" : "+" ); + } + + /** + * get the incremental build argument + * + * @return The Incremental Parameter to CSC + */ + protected String getIncrementalParameter() + { + return "/incremental" + ( _incremental ? "+" : "-" ); + } + + /** + * get the /main argument or null for no argument needed + * + * @return The MainClass Parameter to CSC + */ + protected String getMainClassParameter() + { + if( _mainClass != null && _mainClass.length() != 0 ) + return "/main:" + _mainClass; + else + return null; + } + + protected String getNoConfigParameter() + { + return _noconfig ? "/noconfig" : null; + } + + /** + * get the optimise flag or null for no argument needed + * + * @return The Optimize Parameter to CSC + */ + protected String getOptimizeParameter() + { + return "/optimize" + ( _optimize ? "+" : "-" ); + } + + /** + * get the argument or null for no argument needed + * + * @return The OutputFile Parameter to CSC + */ + protected String getOutputFileParameter() + { + if( _outputFile != null ) + { + File f = _outputFile; + return "/out:" + f.toString(); + } + else + return null; + } + + /** + * turn the path list into a list of files and a /references argument + * + * @return null or a string of references. + */ + protected String getReferenceFilesParameter() + { + //bail on no references + if( _references == null ) + return null; + //iterate through the ref list & generate an entry for each + //or just rely on the fact that the toString operator does this, but + //noting that the separator is ';' on windows, ':' on unix + String refpath = _references.toString(); + + //bail on no references listed + if( refpath.length() == 0 ) + return null; + + StringBuffer s = new StringBuffer( "/reference:" ); + s.append( refpath ); + return new String( s ); + } + + /** + * get the reference string or null for no argument needed + * + * @return The References Parameter to CSC + */ + protected String getReferencesParameter() + { + //bail on no references + if( notEmpty( _references ) ) + return "/reference:" + _references; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The TargetType Parameter to CSC + */ + protected String getTargetTypeParameter() + { + if( notEmpty( _targetType ) ) + return "/target:" + _targetType; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The Unsafe Parameter to CSC + */ + protected String getUnsafeParameter() + { + return _unsafe ? "/unsafe" : null; + } + + protected String getUtf8OutpuParameter() + { + return _utf8output ? "/utf8output" : null; + } + + /** + * get the warn level switch + * + * @return The WarnLevel Parameter to CSC + */ + protected String getWarnLevelParameter() + { + return "/warn:" + _warnLevel; + } + + /** + * get the argument or null for no argument needed + * + * @return The Win32Icon Parameter to CSC + */ + protected String getWin32IconParameter() + { + if( _win32icon != null ) + return "/win32icon:" + _win32icon.toString(); + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The Win32Icon Parameter to CSC + */ + protected String getWin32ResParameter() + { + if( _win32res != null ) + return "/win32res:" + _win32res.toString(); + else + return null; + } + + /** + * test for a string containing something useful + * + * @param s string in + * @return true if the argument is not null or empty + */ + protected boolean notEmpty( String s ) + { + return s != null && s.length() != 0; + }// end execute + +}//end class diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java new file mode 100644 index 000000000..b1440d32f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/Ilasm.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.dotnet;// ==================================================================== +// imports +// ==================================================================== +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; + + +/** + * Task to assemble .net 'Intermediate Language' files. The task will only work + * on win2K until other platforms support csc.exe or an equivalent. ilasm.exe + * must be on the execute path too.

              + * + *

              + * + * All parameters are optional: <il/> should suffice to produce a debug + * build of all *.il files. The option set is roughly compatible with the CSharp + * class; even though the command line options are only vaguely equivalent. [The + * low level commands take things like /OUT=file, csc wants /out:file ... + * /verbose is used some places; /quiet here in ildasm... etc.] It would be nice + * if someone made all the command line tools consistent (and not as brittle as + * the java cmdline tools)

              + * + * The task is a directory based task, so attributes like includes="*.il" + * and excludes="broken.il" can be used to control the files pulled in. + * Each file is built on its own, producing an appropriately named output file + * unless manually specified with outfile + * + * @author Steve Loughran steve_l@iseran.com + * @version 0.2 + */ + +public class Ilasm + extends org.apache.tools.ant.taskdefs.MatchingTask +{ + + /** + * name of the executable. the .exe suffix is deliberately not included in + * anticipation of the unix version + */ + protected final static String exe_name = "ilasm"; + + /** + * what is the file extension we search on? + */ + protected final static String file_ext = "il"; + + /** + * and now derive the search pattern from the extension + */ + protected final static String file_pattern = "**/*." + file_ext; + + /** + * title of task for external presentation + */ + protected final static String exe_title = "ilasm"; + + /** + * debug flag. Controls generation of debug information. + */ + protected boolean _debug; + + /** + * any extra command options? + */ + protected String _extraOptions; + + /** + * flag to control action on execution trouble + */ + protected boolean _failOnError; + + /** + * listing flag + */ + + protected boolean _listing; + + /** + * output file. If not supplied this is derived from the source file + */ + protected File _outputFile; + + /** + * resource file (.res format) to include in the app. + */ + protected File _resourceFile; + + /** + * type of target. Should be one of exe|library|module|winexe|(null) default + * is exe; the actual value (if not null) is fed to the command line.
              + * See /target + */ + protected String _targetType; + + /** + * verbose flag + */ + protected boolean _verbose; + + /** + * file containing private key + */ + + private File _keyfile; + + /** + * source directory upon which the search pattern is applied + */ + private File _srcDir; + + /** + * constructor inits everything and set up the search pattern + */ + public Ilasm() + { + Clear(); + setIncludes( file_pattern ); + } + + /** + * set the debug flag on or off + * + * @param f on/off flag + */ + public void setDebug( boolean f ) + { + _debug = f; + } + + /** + * Sets the ExtraOptions attribute + * + * @param extraOptions The new ExtraOptions value + */ + public void setExtraOptions( String extraOptions ) + { + this._extraOptions = extraOptions; + } + + /** + * set fail on error flag + * + * @param b The new FailOnError value + */ + public void setFailOnError( boolean b ) + { + _failOnError = b; + } + + public void setKeyfile( File keyfile ) + { + this._keyfile = keyfile; + } + + /** + * enable/disable listing + * + * @param b flag set to true for listing on + */ + public void setListing( boolean b ) + { + _listing = b; + } + + /** + * Set the definitions + * + * @param params The new OutputFile value + */ + public void setOutputFile( File params ) + { + _outputFile = params; + } + + + /** + * Sets the Owner attribute + * + * @param s The new Owner value + */ + + public void setOwner( String s ) + { + log( "This option is not supported by ILASM as of Beta-2, and will be ignored", Project.MSG_WARN ); + } + + /** + * Set the resource file + * + * @param fileName path to the file. Can be relative, absolute, whatever. + */ + public void setResourceFile( File fileName ) + { + _resourceFile = fileName; + } + + /** + * Set the source dir to find the files to be compiled + * + * @param srcDirName The new SrcDir value + */ + public void setSrcDir( File srcDirName ) + { + _srcDir = srcDirName; + } + + /** + * define the target + * + * @param targetType one of exe|library| + * @exception BuildException if target is not one of + * exe|library|module|winexe + */ + + public void setTargetType( String targetType ) + throws BuildException + { + targetType = targetType.toLowerCase(); + if( targetType.equals( "exe" ) || targetType.equals( "library" ) ) + { + _targetType = targetType; + } + else + throw new BuildException( "targetType " + targetType + " is not a valid type" ); + } + + /** + * enable/disable verbose ILASM output + * + * @param b flag set to true for verbose on + */ + public void setVerbose( boolean b ) + { + _verbose = b; + } + + /** + * query the debug flag + * + * @return true if debug is turned on + */ + public boolean getDebug() + { + return _debug; + } + + /** + * Gets the ExtraOptions attribute + * + * @return The ExtraOptions value + */ + public String getExtraOptions() + { + return this._extraOptions; + } + + /** + * query fail on error flag + * + * @return The FailFailOnError value + */ + public boolean getFailFailOnError() + { + return _failOnError; + } + + /** + * accessor method for target type + * + * @return the current target option + */ + public String getTargetType() + { + return _targetType; + } + + /** + * reset all contents. + */ + public void Clear() + { + _targetType = null; + _srcDir = null; + _listing = false; + _verbose = false; + _debug = true; + _outputFile = null; + _failOnError = true; + _resourceFile = null; + _extraOptions = null; + } + + + /** + * This is the execution entry point. Build a list of files and call ilasm + * on each of them. + * + * @throws BuildException if the assembly failed and FailOnError is true + */ + public void execute() + throws BuildException + { + if( _srcDir == null ) + _srcDir = project.resolveFile( "." ); + + //get dependencies list. + DirectoryScanner scanner = super.getDirectoryScanner( _srcDir ); + String[] dependencies = scanner.getIncludedFiles(); + log( "assembling " + dependencies.length + " file" + ( ( dependencies.length == 1 ) ? "" : "s" ) ); + String baseDir = scanner.getBasedir().toString(); + //add to the command + for( int i = 0; i < dependencies.length; i++ ) + { + String targetFile = dependencies[i]; + targetFile = baseDir + File.separator + targetFile; + executeOneFile( targetFile ); + } + + }// end execute + + + /** + * do the work for one file by building the command line then calling it + * + * @param targetFile name of the the file to assemble + * @throws BuildException if the assembly failed and FailOnError is true + */ + public void executeOneFile( String targetFile ) + throws BuildException + { + NetCommand command = new NetCommand( this, exe_title, exe_name ); + command.setFailOnError( getFailFailOnError() ); + //DEBUG helper + command.setTraceCommandLine( true ); + //fill in args + command.addArgument( getDebugParameter() ); + command.addArgument( getTargetTypeParameter() ); + command.addArgument( getListingParameter() ); + command.addArgument( getOutputFileParameter() ); + command.addArgument( getResourceFileParameter() ); + command.addArgument( getVerboseParameter() ); + command.addArgument( getKeyfileParameter() ); + command.addArgument( getExtraOptionsParameter() ); + + /* + * space for more argumentativeness + * command.addArgument(); + * command.addArgument(); + */ + command.addArgument( targetFile ); + //now run the command of exe + settings + file + command.runCommand(); + } + + /** + * get the argument or null for no argument needed + * + * @return The DebugParameter value + */ + protected String getDebugParameter() + { + return _debug ? "/debug" : null; + } + + /** + * get any extra options or null for no argument needed + * + * @return The ExtraOptions Parameter to CSC + */ + protected String getExtraOptionsParameter() + { + if( _extraOptions != null && _extraOptions.length() != 0 ) + return _extraOptions; + else + return null; + } + + /** + * get the argument or null for no argument needed + * + * @return The KeyfileParameter value + */ + protected String getKeyfileParameter() + { + if( _keyfile != null ) + return "/keyfile:" + _keyfile.toString(); + else + return null; + } + + /** + * turn the listing flag into a parameter for ILASM + * + * @return the appropriate string from the state of the listing flag + */ + protected String getListingParameter() + { + return _listing ? "/listing" : "/nolisting"; + } + + /** + * get the output file + * + * @return the argument string or null for no argument + */ + protected String getOutputFileParameter() + { + if( _outputFile == null || _outputFile.length() == 0 ) + return null; + File f = _outputFile; + return "/output=" + f.toString(); + } + + protected String getResourceFileParameter() + { + if( _resourceFile != null ) + { + return "/resource=" + _resourceFile.toString(); + } + else + { + return null; + } + } + + /** + * g get the target type or null for no argument needed + * + * @return The TargetTypeParameter value + */ + + protected String getTargetTypeParameter() + { + if( !notEmpty( _targetType ) ) + return null; + if( _targetType.equals( "exe" ) ) + return "/exe"; + else + if( _targetType.equals( "library" ) ) + return "/dll"; + else + return null; + } + + /** + * turn the verbose flag into a parameter for ILASM + * + * @return null or the appropriate command line string + */ + protected String getVerboseParameter() + { + return _verbose ? null : "/quiet"; + } + + /** + * test for a string containing something useful + * + * @param s Description of Parameter + * @return Description of the Returned Value + * @returns true if the argument is not null or empty + */ + protected boolean notEmpty( String s ) + { + return s != null && s.length() != 0; + }// end executeOneFile +}//class diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java new file mode 100644 index 000000000..4f28dffe7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/dotnet/NetCommand.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.dotnet;// imports +import java.io.File; +import java.io.IOException; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; + + +/** + * This is a helper class to spawn net commands out. In its initial form it + * contains no .net specifics, just contains all the command line/exe + * construction stuff. However, it may be handy in future to have a means of + * setting the path to point to the dotnet bin directory; in which case the + * shared code should go in here. + * + * @author Steve Loughran steve_l@iseran.com + * @version 0.3 + * @created 2000-11-01 + */ + +public class NetCommand +{ + + /** + * trace flag + */ + protected boolean _traceCommandLine = false; + + /** + * what is the command line + */ + protected Commandline _commandLine; + + /** + * executabe + */ + protected Execute _exe; + + /** + * flag to control action on execution trouble + */ + protected boolean _failOnError; + + /** + * owner project + */ + protected Task _owner; + + /** + * actual program to invoke + */ + protected String _program; + + /** + * title of the command + */ + protected String _title; + + /** + * constructor + * + * @param title (for logging/errors) + * @param owner Description of Parameter + * @param program Description of Parameter + */ + + public NetCommand( Task owner, String title, String program ) + { + _owner = owner; + _title = title; + _program = program; + _commandLine = new Commandline(); + _commandLine.setExecutable( _program ); + prepareExecutor(); + } + + /** + * set fail on error flag + * + * @param b fail flag -set to true to cause an exception to be raised if the + * return value != 0 + */ + public void setFailOnError( boolean b ) + { + _failOnError = b; + } + + /** + * turn tracing on or off + * + * @param b trace flag + */ + public void setTraceCommandLine( boolean b ) + { + _traceCommandLine = b; + } + + /** + * query fail on error flag + * + * @return The FailFailOnError value + */ + public boolean getFailFailOnError() + { + return _failOnError; + } + + /** + * add an argument to a command line; do nothing if the arg is null or empty + * string + * + * @param argument The feature to be added to the Argument attribute + */ + public void addArgument( String argument ) + { + if( argument != null && argument.length() != 0 ) + { + _commandLine.createArgument().setValue( argument ); + } + } + + /** + * Run the command using the given Execute instance. + * + * @exception BuildException Description of Exception + * @throws an exception of something goes wrong and the failOnError flag is + * true + */ + public void runCommand() + throws BuildException + { + int err = -1;// assume the worst + try + { + if( _traceCommandLine ) + { + _owner.log( _commandLine.toString() ); + } + else + { + //in verbose mode we always log stuff + logVerbose( _commandLine.toString() ); + } + _exe.setCommandline( _commandLine.getCommandline() ); + err = _exe.execute(); + if( err != 0 ) + { + if( _failOnError ) + { + throw new BuildException( _title + " returned: " + err, _owner.getLocation() ); + } + else + { + _owner.log( _title + " Result: " + err, Project.MSG_ERR ); + } + } + } + catch( IOException e ) + { + throw new BuildException( _title + " failed: " + e, e, _owner.getLocation() ); + } + } + + + /** + * error text log + * + * @param msg message to display as an error + */ + protected void logError( String msg ) + { + _owner.getProject().log( msg, Project.MSG_ERR ); + } + + /** + * verbose text log + * + * @param msg string to add to log iff verbose is defined for the build + */ + protected void logVerbose( String msg ) + { + _owner.getProject().log( msg, Project.MSG_VERBOSE ); + } + + /** + * set up the command sequence.. + */ + protected void prepareExecutor() + { + // default directory to the project's base directory + File dir = _owner.getProject().getBaseDir(); + ExecuteStreamHandler handler = new LogStreamHandler( _owner, + Project.MSG_INFO, Project.MSG_WARN ); + _exe = new Execute( handler, null ); + _exe.setAntRun( _owner.getProject() ); + _exe.setWorkingDirectory( dir ); + } +}//class diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java new file mode 100644 index 000000000..ff7a72719 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandDeploymentTool.java @@ -0,0 +1,513 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + + +/** + * BorlandDeploymentTool is dedicated to the Borland Application Server 4.5 and + * 4.5.1 This task generates and compiles the stubs and skeletons for all ejb + * described into the Deployement Descriptor, builds the jar file including the + * support files and verify whether the produced jar is valid or not. The + * supported options are: + *

                + *
              • debug (boolean) : turn on the debug mode for generation of stubs and + * skeletons (default:false)
              • + *
              • verify (boolean) : turn on the verification at the end of the jar + * production (default:true)
              • + *
              • verifyargs (String) : add optional argument to verify command (see vbj + * com.inprise.ejb.util.Verify)
              • + *
              • basdtd (String) : location of the BAS DTD
              • + *
              • generateclient (boolean) : turn on the client jar file generation + *
              • + *
              + *
              + *
              + *      <ejbjar srcdir="${build.classes}"  basejarname="vsmp"  descriptordir="${rsc.dir}/hrmanager">
              + *        <borland destdir="tstlib">
              + *          <classpath refid="classpath" />
              + *        </borland>
              + *        <include name="**\ejb-jar.xml"/>
              + *        <support dir="${build.classes}">
              + *          <include name="demo\smp\*.class"/>
              + *          <include name="demo\helper\*.class"/>
              + *         </support>
              + *     </ejbjar>
              + *
              + * + * @author Benoit Moussaud + */ +public class BorlandDeploymentTool extends GenericDeploymentTool implements ExecuteStreamHandler +{ + public final static String PUBLICID_BORLAND_EJB + = "-//Inprise Corporation//DTD Enterprise JavaBeans 1.1//EN"; + + protected final static String DEFAULT_BAS45_EJB11_DTD_LOCATION + = "/com/inprise/j2ee/xml/dtds/ejb-jar.dtd"; + + protected final static String DEFAULT_BAS_DTD_LOCATION + = "/com/inprise/j2ee/xml/dtds/ejb-inprise.dtd"; + + protected final static String BAS_DD = "ejb-inprise.xml"; + + /** + * Java2iiop executable * + */ + protected final static String JAVA2IIOP = "java2iiop"; + + /** + * Verify class + */ + protected final static String VERIFY = "com.inprise.ejb.util.Verify"; + + /** + * Instance variable that stores the suffix for the borland jarfile. + */ + private String jarSuffix = "-ejb.jar"; + + /** + * Instance variable that determines whether the debug mode is on + */ + private boolean java2iiopdebug = false; + + /** + * Instance variable that determines whetger the client jar file is + * generated + */ + private boolean generateclient = false; + /** + * Instance variable that determines whether it is necessary to verify the + * produced jar + */ + private boolean verify = true; + private String verifyArgs = ""; + + private Hashtable _genfiles = new Hashtable(); + + /** + * Instance variable that stores the location of the borland DTD file. + */ + private String borlandDTD; + + /** + * Setter used to store the location of the borland DTD. This can be a file + * on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setBASdtd( String inString ) + { + this.borlandDTD = inString; + } + + /** + * set the debug mode for java2iiop (default false) + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.java2iiopdebug = debug; + } + + + /** + * setter used to store whether the task will include the generate client + * task. (see : BorlandGenerateClient task) + * + * @param b The new Generateclient value + */ + public void setGenerateclient( boolean b ) + { + this.generateclient = b; + } + + /** + * @param is The new ProcessErrorStream value + * @exception IOException Description of Exception + */ + public void setProcessErrorStream( InputStream is ) + throws IOException + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + String s = reader.readLine(); + if( s != null ) + { + log( "[java2iiop] " + s, Project.MSG_DEBUG ); + }// end of if () + } + + public void setProcessInputStream( OutputStream param1 ) + throws IOException { } + + /** + * @param is + * @exception IOException Description of Exception + */ + public void setProcessOutputStream( InputStream is ) + throws IOException + { + try + { + BufferedReader reader = new BufferedReader( new InputStreamReader( is ) ); + String javafile; + while( ( javafile = reader.readLine() ) != null ) + { + log( "buffer:" + javafile, Project.MSG_DEBUG ); + if( javafile.endsWith( ".java" ) ) + { + String classfile = toClassFile( javafile ); + String key = classfile.substring( getConfig().srcDir.getAbsolutePath().length() + 1 ); + log( " generated : " + classfile, Project.MSG_DEBUG ); + log( " key : " + key, Project.MSG_DEBUG ); + _genfiles.put( key, new File( classfile ) ); + }// end of if () + }// end of while () + reader.close(); + } + catch( Exception e ) + { + String msg = "Exception while parsing java2iiop output. Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } + + + /** + * Setter used to store the suffix for the generated borland jar file. + * + * @param inString the string to use as the suffix. + */ + public void setSuffix( String inString ) + { + this.jarSuffix = inString; + } + + /** + * set the verify mode for the produced jar (default true) + * + * @param verify The new Verify value + */ + public void setVerify( boolean verify ) + { + this.verify = verify; + } + + + /** + * sets some additional args to send to verify command + * + * @param args addtions command line parameters + */ + public void setVerifyArgs( String args ) + { + this.verifyArgs = args; + } + + // implementation of org.apache.tools.ant.taskdefs.ExecuteStreamHandler interface + + public void start() + throws IOException { } + + public void stop() { } + + + protected DescriptorHandler getBorlandDescriptorHandler( final File srcDir ) + { + DescriptorHandler handler = + new DescriptorHandler( getTask(), srcDir ) + { + protected void processElement() + { + if( currentElement.equals( "type-storage" ) ) + { + // Get the filename of vendor specific descriptor + String fileNameWithMETA = currentText; + //trim the META_INF\ off of the file name + String fileName = fileNameWithMETA.substring( META_DIR.length(), + fileNameWithMETA.length() ); + File descriptorFile = new File( srcDir, fileName ); + + ejbFiles.put( fileNameWithMETA, descriptorFile ); + } + } + }; + handler.registerDTD( PUBLICID_BORLAND_EJB, + borlandDTD == null ? DEFAULT_BAS_DTD_LOCATION : borlandDTD ); + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + } + return handler; + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + + File borlandDD = new File( getConfig().descriptorDir, ddPrefix + BAS_DD ); + if( borlandDD.exists() ) + { + log( "Borland specific file found " + borlandDD, Project.MSG_VERBOSE ); + ejbFiles.put( META_DIR + BAS_DD, borlandDD ); + } + else + { + log( "Unable to locate borland deployment descriptor. It was expected to be in " + + borlandDD.getPath(), Project.MSG_WARN ); + return; + } + } + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarFile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + protected void writeJar( String baseName, File jarFile, Hashtable files, String publicId ) + throws BuildException + { + //build the home classes list. + Vector homes = new Vector(); + Iterator it = files.keySet().iterator(); + while( it.hasNext() ) + { + String clazz = ( String )it.next(); + if( clazz.endsWith( "Home.class" ) ) + { + //remove .class extension + String home = toClass( clazz ); + homes.add( home ); + log( " Home " + home, Project.MSG_VERBOSE ); + }// end of if () + }// end of while () + + buildBorlandStubs( homes.iterator(), files ); + + //add the gen files to the collection + files.putAll( _genfiles ); + + super.writeJar( baseName, jarFile, files, publicId ); + + if( verify ) + { + verifyBorlandJar( jarFile ); + }// end of if () + + if( generateclient ) + { + generateClient( jarFile ); + }// end of if () + } + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( getDestDir(), baseName + jarSuffix ); + } + + /** + * Generate stubs & sketelton for each home found into the DD Add all the + * generate class file into the ejb files + * + * @param ithomes : iterator on home class + * @param files : file list , updated by the adding generated files + */ + private void buildBorlandStubs( Iterator ithomes, Hashtable files ) + { + Execute execTask = null; + + execTask = new Execute( this ); + Project project = getTask().getProject(); + execTask.setAntRun( project ); + execTask.setWorkingDirectory( project.getBaseDir() ); + + Commandline commandline = new Commandline(); + commandline.setExecutable( JAVA2IIOP ); + //debug ? + if( java2iiopdebug ) + { + commandline.createArgument().setValue( "-VBJdebug" ); + }// end of if () + //set the classpath + commandline.createArgument().setValue( "-VBJclasspath" ); + commandline.createArgument().setPath( getCombinedClasspath() ); + //list file + commandline.createArgument().setValue( "-list_files" ); + //no TIE classes + commandline.createArgument().setValue( "-no_tie" ); + //root dir + commandline.createArgument().setValue( "-root_dir" ); + commandline.createArgument().setValue( getConfig().srcDir.getAbsolutePath() ); + //compiling order + commandline.createArgument().setValue( "-compile" ); + //add the home class + while( ithomes.hasNext() ) + { + commandline.createArgument().setValue( ithomes.next().toString() ); + }// end of while () + + try + { + log( "Calling java2iiop", Project.MSG_VERBOSE ); + log( commandline.toString(), Project.MSG_DEBUG ); + execTask.setCommandline( commandline.getCommandline() ); + int result = execTask.execute(); + if( result != 0 ) + { + String msg = "Failed executing java2iiop (ret code is " + result + ")"; + throw new BuildException( msg, getTask().getLocation() ); + } + } + catch( java.io.IOException e ) + { + log( "java2iiop exception :" + e.getMessage(), Project.MSG_ERR ); + throw new BuildException( e ); + } + } + + /** + * Generate the client jar corresponding to the jar file passed as paremeter + * the method uses the BorlandGenerateClient task. + * + * @param sourceJar java.io.File representing the produced jar file + */ + private void generateClient( File sourceJar ) + { + getTask().getProject().addTaskDefinition( "internal_bas_generateclient", + org.apache.tools.ant.taskdefs.optional.ejb.BorlandGenerateClient.class ); + + org.apache.tools.ant.taskdefs.optional.ejb.BorlandGenerateClient gentask = null; + log( "generate client for " + sourceJar, Project.MSG_INFO ); + try + { + String args = verifyArgs; + args += " " + sourceJar.getPath(); + + gentask = ( BorlandGenerateClient )getTask().getProject().createTask( "internal_bas_generateclient" ); + gentask.setEjbjar( sourceJar ); + gentask.setDebug( java2iiopdebug ); + Path classpath = getCombinedClasspath(); + if( classpath != null ) + { + gentask.setClasspath( classpath ); + } + gentask.setTaskName( "generate client" ); + gentask.execute(); + } + catch( Exception e ) + { + //TO DO : delete the file if it is not a valid file. + String msg = "Exception while calling " + VERIFY + " Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } + + /** + * convert a class file name : A/B/C/toto.class into a class name: + * A.B.C.toto + * + * @param filename Description of Parameter + * @return Description of the Returned Value + */ + private String toClass( String filename ) + { + //remove the .class + String classname = filename.substring( 0, filename.lastIndexOf( ".class" ) ); + classname = classname.replace( '\\', '.' ); + return classname; + } + + /** + * convert a file name : A/B/C/toto.java into a class name: A/B/C/toto.class + * + * @param filename Description of Parameter + * @return Description of the Returned Value + */ + private String toClassFile( String filename ) + { + //remove the .class + String classfile = filename.substring( 0, filename.lastIndexOf( ".java" ) ); + classfile = classfile + ".class"; + return classfile; + } + + /** + * Verify the produced jar file by invoking the Borland verify tool + * + * @param sourceJar java.io.File representing the produced jar file + */ + private void verifyBorlandJar( File sourceJar ) + { + org.apache.tools.ant.taskdefs.Java javaTask = null; + log( "verify " + sourceJar, Project.MSG_INFO ); + try + { + + String args = verifyArgs; + args += " " + sourceJar.getPath(); + + javaTask = ( Java )getTask().getProject().createTask( "java" ); + javaTask.setTaskName( "verify" ); + javaTask.setClassname( VERIFY ); + Commandline.Argument arguments = javaTask.createArg(); + arguments.setLine( args ); + Path classpath = getCombinedClasspath(); + if( classpath != null ) + { + javaTask.setClasspath( classpath ); + javaTask.setFork( true ); + } + + log( "Calling " + VERIFY + " for " + sourceJar.toString(), Project.MSG_VERBOSE ); + javaTask.execute(); + } + catch( Exception e ) + { + //TO DO : delete the file if it is not a valid file. + String msg = "Exception while calling " + VERIFY + " Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java new file mode 100644 index 000000000..1dab7dd20 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/BorlandGenerateClient.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.ExecTask; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + + +/** + * BorlandGenerateClient is dedicated to the Borland Application Server 4.5 This + * task generates the client jar using as input the ejb jar file. Two mode are + * available: java mode (default) and fork mode. With the fork mode, it is + * impossible to add classpath to the commmand line. + * + * @author Benoit Moussaud + */ +public class BorlandGenerateClient extends Task +{ + final static String JAVA_MODE = "java"; + final static String FORK_MODE = "fork"; + + /** + * debug the generateclient task + */ + boolean debug = false; + + /** + * hold the ejbjar file name + */ + File ejbjarfile = null; + + /** + * hold the client jar file name + */ + File clientjarfile = null; + + /** + * hold the mode (java|fork) + */ + String mode = JAVA_MODE; + + /** + * hold the classpath + */ + Path classpath; + + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + public void setClientjar( File clientjar ) + { + clientjarfile = clientjar; + } + + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + public void setEjbjar( File ejbfile ) + { + ejbjarfile = ejbfile; + } + + public void setMode( String s ) + { + mode = s; + } + + public Path createClasspath() + { + if( this.classpath == null ) + { + this.classpath = new Path( project ); + } + return this.classpath.createPath(); + } + + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * a java task. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( ejbjarfile == null || + ejbjarfile.isDirectory() ) + { + throw new BuildException( "invalid ejb jar file." ); + }// end of if () + + if( clientjarfile == null || + clientjarfile.isDirectory() ) + { + log( "invalid or missing client jar file.", Project.MSG_VERBOSE ); + String ejbjarname = ejbjarfile.getAbsolutePath(); + //clientname = ejbjarfile+client.jar + String clientname = ejbjarname.substring( 0, ejbjarname.lastIndexOf( "." ) ); + clientname = clientname + "client.jar"; + clientjarfile = new File( clientname ); + + }// end of if () + + if( mode == null ) + { + log( "mode is null default mode is java" ); + setMode( JAVA_MODE ); + }// end of if () + + log( "client jar file is " + clientjarfile ); + + if( mode.equalsIgnoreCase( FORK_MODE ) ) + { + executeFork(); + }// end of if () + else + { + executeJava(); + }// end of else + } + + /** + * launch the generate client using system api + * + * @exception BuildException Description of Exception + */ + protected void executeFork() + throws BuildException + { + try + { + log( "mode : fork" ); + + org.apache.tools.ant.taskdefs.ExecTask execTask = null; + execTask = ( ExecTask )getProject().createTask( "exec" ); + + execTask.setDir( new File( "." ) ); + execTask.setExecutable( "iastool" ); + execTask.createArg().setValue( "generateclient" ); + if( debug ) + { + execTask.createArg().setValue( "-trace" ); + }// end of if () + + // + execTask.createArg().setValue( "-short" ); + execTask.createArg().setValue( "-jarfile" ); + // ejb jar file + execTask.createArg().setValue( ejbjarfile.getAbsolutePath() ); + //client jar file + execTask.createArg().setValue( "-single" ); + execTask.createArg().setValue( "-clientjarfile" ); + execTask.createArg().setValue( clientjarfile.getAbsolutePath() ); + + log( "Calling java2iiop", Project.MSG_VERBOSE ); + execTask.execute(); + } + catch( Exception e ) + { + // Have to catch this because of the semantics of calling main() + String msg = "Exception while calling generateclient Details: " + e.toString(); + throw new BuildException( msg, e ); + } + + } + + /** + * launch the generate client using java api + * + * @exception BuildException Description of Exception + */ + protected void executeJava() + throws BuildException + { + try + { + log( "mode : java" ); + + org.apache.tools.ant.taskdefs.Java execTask = null; + execTask = ( Java )getProject().createTask( "java" ); + + execTask.setDir( new File( "." ) ); + execTask.setClassname( "com.inprise.server.commandline.EJBUtilities" ); + //classpath + //add at the end of the classpath + //the system classpath in order to find the tools.jar file + execTask.setClasspath( classpath.concatSystemClasspath() ); + + execTask.setFork( true ); + execTask.createArg().setValue( "generateclient" ); + if( debug ) + { + execTask.createArg().setValue( "-trace" ); + }// end of if () + + // + execTask.createArg().setValue( "-short" ); + execTask.createArg().setValue( "-jarfile" ); + // ejb jar file + execTask.createArg().setValue( ejbjarfile.getAbsolutePath() ); + //client jar file + execTask.createArg().setValue( "-single" ); + execTask.createArg().setValue( "-clientjarfile" ); + execTask.createArg().setValue( clientjarfile.getAbsolutePath() ); + + log( "Calling EJBUtilities", Project.MSG_VERBOSE ); + execTask.execute(); + + } + catch( Exception e ) + { + // Have to catch this because of the semantics of calling main() + String msg = "Exception while calling generateclient Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java new file mode 100644 index 000000000..25218e180 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreator.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * Build a serialised deployment descriptor given a text file description of the + * descriptor in the format supported by WebLogic. This ant task is a front end + * for the weblogic DDCreator tool. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class DDCreator extends MatchingTask +{ + + /** + * The classpath to be used in the weblogic ejbc calls. It must contain the + * weblogic classes necessary fro DDCreator and the implementation + * classes of the home and remote interfaces. + */ + private String classpath; + /** + * The root directory of the tree containing the textual deployment + * desciptors. The actual deployment descriptor files are selected using + * include and exclude constructs on the EJBC task, as supported by the + * MatchingTask superclass. + */ + private File descriptorDirectory; + + /** + * The directory where generated serialised deployment descriptors are + * placed. + */ + private File generatedFilesDirectory; + + /** + * Set the classpath to be used for this compilation. + * + * @param s the classpath to use for the ddcreator tool. + */ + public void setClasspath( String s ) + { + this.classpath = project.translatePath( s ); + } + + /** + * Set the directory from where the text descriptions of the deployment + * descriptors are to be read. + * + * @param dirName the name of the directory containing the text deployment + * descriptor files. + */ + public void setDescriptors( String dirName ) + { + descriptorDirectory = new File( dirName ); + } + + /** + * Set the directory into which the serialised deployment descriptors are to + * be written. + * + * @param dirName the name of the directory into which the serialised + * deployment descriptors are written. + */ + public void setDest( String dirName ) + { + generatedFilesDirectory = new File( dirName ); + } + + /** + * Do the work. The work is actually done by creating a helper task. This + * approach allows the classpath of the helper task to be set. Since the + * weblogic tools require the class files of the project's home and remote + * interfaces to be available in the classpath, this also avoids having to + * start ant with the class path of the project it is building. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( descriptorDirectory == null || + !descriptorDirectory.isDirectory() ) + { + throw new BuildException( "descriptors directory " + descriptorDirectory.getPath() + + " is not valid" ); + } + if( generatedFilesDirectory == null || + !generatedFilesDirectory.isDirectory() ) + { + throw new BuildException( "dest directory " + generatedFilesDirectory.getPath() + + " is not valid" ); + } + + String args = descriptorDirectory + " " + generatedFilesDirectory; + + // get all the files in the descriptor directory + DirectoryScanner ds = super.getDirectoryScanner( descriptorDirectory ); + + String[] files = ds.getIncludedFiles(); + + for( int i = 0; i < files.length; ++i ) + { + args += " " + files[i]; + } + + String systemClassPath = System.getProperty( "java.class.path" ); + String execClassPath = project.translatePath( systemClassPath + ":" + classpath ); + Java ddCreatorTask = ( Java )project.createTask( "java" ); + ddCreatorTask.setTaskName( getTaskName() ); + ddCreatorTask.setFork( true ); + ddCreatorTask.setClassname( "org.apache.tools.ant.taskdefs.optional.ejb.DDCreatorHelper" ); + Commandline.Argument arguments = ddCreatorTask.createArg(); + arguments.setLine( args ); + ddCreatorTask.setClasspath( new Path( project, execClassPath ) ); + if( ddCreatorTask.executeJava() != 0 ) + { + throw new BuildException( "Execution of ddcreator helper failed" ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java new file mode 100644 index 000000000..f59a3288c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DDCreatorHelper.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import javax.ejb.deployment.DeploymentDescriptor; + +/** + * A helper class which performs the actual work of the ddcreator task. This + * class is run with a classpath which includes the weblogic tools and the home + * and remote interface class files referenced in the deployment descriptors + * being built. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class DDCreatorHelper +{ + + /** + * The descriptor text files for which a serialised descriptor is to be + * created. + */ + String[] descriptors; + /** + * The root directory of the tree containing the textual deployment + * desciptors. + */ + private File descriptorDirectory; + + /** + * The directory where generated serialised desployment descriptors are + * written. + */ + private File generatedFilesDirectory; + + /** + * Initialise the helper with the command arguments. + * + * @param args Description of Parameter + */ + private DDCreatorHelper( String[] args ) + { + int index = 0; + descriptorDirectory = new File( args[index++] ); + generatedFilesDirectory = new File( args[index++] ); + + descriptors = new String[args.length - index]; + for( int i = 0; index < args.length; ++i ) + { + descriptors[i] = args[index++]; + } + } + + /** + * The main method. The main method creates an instance of the + * DDCreatorHelper, passing it the args which it then processes. + * + * @param args The command line arguments + * @exception Exception Description of Exception + */ + public static void main( String[] args ) + throws Exception + { + DDCreatorHelper helper = new DDCreatorHelper( args ); + helper.process(); + } + + /** + * Do the actual work. The work proceeds by examining each descriptor given. + * If the serialised file does not exist or is older than the text + * description, the weblogic DDCreator tool is invoked directly to build the + * serialised descriptor. + * + * @exception Exception Description of Exception + */ + private void process() + throws Exception + { + for( int i = 0; i < descriptors.length; ++i ) + { + String descriptorName = descriptors[i]; + File descriptorFile = new File( descriptorDirectory, descriptorName ); + + int extIndex = descriptorName.lastIndexOf( "." ); + String serName = null; + if( extIndex != -1 ) + { + serName = descriptorName.substring( 0, extIndex ) + ".ser"; + } + else + { + serName = descriptorName + ".ser"; + } + File serFile = new File( generatedFilesDirectory, serName ); + + // do we need to regenerate the file + if( !serFile.exists() || serFile.lastModified() < descriptorFile.lastModified() + || regenerateSerializedFile( serFile ) ) + { + + String[] args = {"-noexit", + "-d", serFile.getParent(), + "-outputfile", serFile.getName(), + descriptorFile.getPath()}; + try + { + weblogic.ejb.utils.DDCreator.main( args ); + } + catch( Exception e ) + { + // there was an exception - run with no exit to get proper error + String[] newArgs = {"-d", generatedFilesDirectory.getPath(), + "-outputfile", serFile.getName(), + descriptorFile.getPath()}; + weblogic.ejb.utils.DDCreator.main( newArgs ); + } + } + } + } + + /** + * EJBC will fail if the serialized descriptor file does not match the bean + * classes. You can test for this by trying to load the deployment + * descriptor. If it fails, the serialized file needs to be regenerated + * because the associated class files don't match. + * + * @param serFile Description of Parameter + * @return Description of the Returned Value + */ + private boolean regenerateSerializedFile( File serFile ) + { + try + { + + FileInputStream fis = new FileInputStream( serFile ); + ObjectInputStream ois = new ObjectInputStream( fis ); + DeploymentDescriptor dd = ( DeploymentDescriptor )ois.readObject(); + fis.close(); + + // Since the descriptor read properly, everything should be o.k. + return false; + } + catch( Exception e ) + { + + // Weblogic will throw an error if the deployment descriptor does + // not match the class files. + return true; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java new file mode 100644 index 000000000..4f0a226cc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/DescriptorHandler.java @@ -0,0 +1,403 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Hashtable; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.xml.sax.AttributeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Inner class used by EjbJar to facilitate the parsing of deployment + * descriptors and the capture of appropriate information. Extends HandlerBase + * so it only implements the methods needed. During parsing creates a hashtable + * consisting of entries mapping the name it should be inserted into an EJB jar + * as to a File representing the file on disk. This list can then be accessed + * through the getFiles() method. + * + * @author RT + */ +public class DescriptorHandler extends org.xml.sax.HandlerBase +{ + private final static int STATE_LOOKING_EJBJAR = 1; + private final static int STATE_IN_EJBJAR = 2; + private final static int STATE_IN_BEANS = 3; + private final static int STATE_IN_SESSION = 4; + private final static int STATE_IN_ENTITY = 5; + private final static int STATE_IN_MESSAGE = 6; + + /** + * Bunch of constants used for storing entries in a hashtable, and for + * constructing the filenames of various parts of the ejb jar. + */ + private final static String EJB_REF = "ejb-ref"; + private final static String HOME_INTERFACE = "home"; + private final static String REMOTE_INTERFACE = "remote"; + private final static String LOCAL_HOME_INTERFACE = "local-home"; + private final static String LOCAL_INTERFACE = "local"; + private final static String BEAN_CLASS = "ejb-class"; + private final static String PK_CLASS = "prim-key-class"; + private final static String EJB_NAME = "ejb-name"; + private final static String EJB_JAR = "ejb-jar"; + private final static String ENTERPRISE_BEANS = "enterprise-beans"; + private final static String ENTITY_BEAN = "entity"; + private final static String SESSION_BEAN = "session"; + private final static String MESSAGE_BEAN = "message-driven"; + + private String publicId = null; + + /** + * The state of the parsing + */ + private int parseState = STATE_LOOKING_EJBJAR; + + /** + * Instance variable used to store the name of the current element being + * processed by the SAX parser. Accessed by the SAX parser call-back methods + * startElement() and endElement(). + */ + protected String currentElement = null; + + /** + * The text of the current element + */ + protected String currentText = null; + + /** + * Instance variable that stores the names of the files as they will be put + * into the jar file, mapped to File objects Accessed by the SAX parser + * call-back method characters(). + */ + protected Hashtable ejbFiles = null; + + /** + * Instance variable that stores the value found in the <ejb-name> + * element + */ + protected String ejbName = null; + + private Hashtable fileDTDs = new Hashtable(); + + private Hashtable resourceDTDs = new Hashtable(); + + private boolean inEJBRef = false; + + private Hashtable urlDTDs = new Hashtable(); + + private Task owningTask; + + /** + * The directory containing the bean classes and interfaces. This is used + * for performing dependency file lookups. + */ + private File srcDir; + + public DescriptorHandler( Task task, File srcDir ) + { + this.owningTask = task; + this.srcDir = srcDir; + } + + /** + * Getter method that returns the value of the <ejb-name> element. + * + * @return The EjbName value + */ + public String getEjbName() + { + return ejbName; + } + + /** + * Getter method that returns the set of files to include in the EJB jar. + * + * @return The Files value + */ + public Hashtable getFiles() + { + return ( ejbFiles == null ) ? new Hashtable() : ejbFiles; + } + + /** + * Get the publicId of the DTD + * + * @return The PublicId value + */ + public String getPublicId() + { + return publicId; + } + + /** + * SAX parser call-back method invoked whenever characters are located + * within an element. currentAttribute (modified by startElement and + * endElement) tells us whether we are in an interesting element (one of the + * up to four classes of an EJB). If so then converts the classname from the + * format org.apache.tools.ant.Parser to the convention for storing such a + * class, org/apache/tools/ant/Parser.class. This is then resolved into a + * file object under the srcdir which is stored in a Hashtable. + * + * @param ch A character array containing all the characters in the element, + * and maybe others that should be ignored. + * @param start An integer marking the position in the char array to start + * reading from. + * @param length An integer representing an offset into the char array where + * the current data terminates. + * @exception SAXException Description of Exception + */ + public void characters( char[] ch, int start, int length ) + throws SAXException + { + + currentText += new String( ch, start, length ); + } + + + /** + * SAX parser call-back method that is invoked when an element is exited. + * Used to blank out (set to the empty string, not nullify) the name of the + * currentAttribute. A better method would be to use a stack as an instance + * variable, however since we are only interested in leaf-node data this is + * a simpler and workable solution. + * + * @param name The name of the attribute being exited. Ignored in this + * implementation. + * @exception SAXException Description of Exception + */ + public void endElement( String name ) + throws SAXException + { + processElement(); + currentText = ""; + this.currentElement = ""; + if( name.equals( EJB_REF ) ) + { + inEJBRef = false; + } + else if( parseState == STATE_IN_ENTITY && name.equals( ENTITY_BEAN ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_SESSION && name.equals( SESSION_BEAN ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_MESSAGE && name.equals( MESSAGE_BEAN ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_BEANS && name.equals( ENTERPRISE_BEANS ) ) + { + parseState = STATE_IN_EJBJAR; + } + else if( parseState == STATE_IN_EJBJAR && name.equals( EJB_JAR ) ) + { + parseState = STATE_LOOKING_EJBJAR; + } + } + + public void registerDTD( String publicId, String location ) + { + if( location == null ) + { + return; + } + + File fileDTD = new File( location ); + if( fileDTD.exists() ) + { + if( publicId != null ) + { + fileDTDs.put( publicId, fileDTD ); + owningTask.log( "Mapped publicId " + publicId + " to file " + fileDTD, Project.MSG_VERBOSE ); + } + return; + } + + if( getClass().getResource( location ) != null ) + { + if( publicId != null ) + { + resourceDTDs.put( publicId, location ); + owningTask.log( "Mapped publicId " + publicId + " to resource " + location, Project.MSG_VERBOSE ); + } + } + + try + { + if( publicId != null ) + { + URL urldtd = new URL( location ); + urlDTDs.put( publicId, urldtd ); + } + } + catch( java.net.MalformedURLException e ) + { + //ignored + } + + } + + public InputSource resolveEntity( String publicId, String systemId ) + throws SAXException + { + this.publicId = publicId; + + File dtdFile = ( File )fileDTDs.get( publicId ); + if( dtdFile != null ) + { + try + { + owningTask.log( "Resolved " + publicId + " to local file " + dtdFile, Project.MSG_VERBOSE ); + return new InputSource( new FileInputStream( dtdFile ) ); + } + catch( FileNotFoundException ex ) + { + // ignore + } + } + + String dtdResourceName = ( String )resourceDTDs.get( publicId ); + if( dtdResourceName != null ) + { + InputStream is = this.getClass().getResourceAsStream( dtdResourceName ); + if( is != null ) + { + owningTask.log( "Resolved " + publicId + " to local resource " + dtdResourceName, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + } + + URL dtdUrl = ( URL )urlDTDs.get( publicId ); + if( dtdUrl != null ) + { + try + { + InputStream is = dtdUrl.openStream(); + owningTask.log( "Resolved " + publicId + " to url " + dtdUrl, Project.MSG_VERBOSE ); + return new InputSource( is ); + } + catch( IOException ioe ) + { + //ignore + } + } + + owningTask.log( "Could not resolve ( publicId: " + publicId + ", systemId: " + systemId + ") to a local entity", + Project.MSG_INFO ); + + return null; + } + + /** + * SAX parser call-back method that is used to initialize the values of some + * instance variables to ensure safe operation. + * + * @exception SAXException Description of Exception + */ + public void startDocument() + throws SAXException + { + this.ejbFiles = new Hashtable( 10, 1 ); + this.currentElement = null; + inEJBRef = false; + } + + + /** + * SAX parser call-back method that is invoked when a new element is entered + * into. Used to store the context (attribute name) in the currentAttribute + * instance variable. + * + * @param name The name of the element being entered. + * @param attrs Attributes associated to the element. + * @exception SAXException Description of Exception + */ + public void startElement( String name, AttributeList attrs ) + throws SAXException + { + this.currentElement = name; + currentText = ""; + if( name.equals( EJB_REF ) ) + { + inEJBRef = true; + } + else if( parseState == STATE_LOOKING_EJBJAR && name.equals( EJB_JAR ) ) + { + parseState = STATE_IN_EJBJAR; + } + else if( parseState == STATE_IN_EJBJAR && name.equals( ENTERPRISE_BEANS ) ) + { + parseState = STATE_IN_BEANS; + } + else if( parseState == STATE_IN_BEANS && name.equals( SESSION_BEAN ) ) + { + parseState = STATE_IN_SESSION; + } + else if( parseState == STATE_IN_BEANS && name.equals( ENTITY_BEAN ) ) + { + parseState = STATE_IN_ENTITY; + } + else if( parseState == STATE_IN_BEANS && name.equals( MESSAGE_BEAN ) ) + { + parseState = STATE_IN_MESSAGE; + } + } + + + protected void processElement() + { + if( inEJBRef || + ( parseState != STATE_IN_ENTITY && parseState != STATE_IN_SESSION && parseState != STATE_IN_MESSAGE ) ) + { + return; + } + + if( currentElement.equals( HOME_INTERFACE ) || + currentElement.equals( REMOTE_INTERFACE ) || + currentElement.equals( LOCAL_INTERFACE ) || + currentElement.equals( LOCAL_HOME_INTERFACE ) || + currentElement.equals( BEAN_CLASS ) || + currentElement.equals( PK_CLASS ) ) + { + + // Get the filename into a String object + File classFile = null; + String className = currentText.trim(); + + // If it's a primitive wrapper then we shouldn't try and put + // it into the jar, so ignore it. + if( !className.startsWith( "java." ) && + !className.startsWith( "javax." ) ) + { + // Translate periods into path separators, add .class to the + // name, create the File object and add it to the Hashtable. + className = className.replace( '.', File.separatorChar ); + className += ".class"; + classFile = new File( srcDir, className ); + ejbFiles.put( className, classFile ); + } + } + + // Get the value of the tag. Only the first occurence. + if( currentElement.equals( EJB_NAME ) ) + { + if( ejbName == null ) + { + ejbName = currentText.trim(); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java new file mode 100644 index 000000000..147faed55 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EJBDeploymentTool.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import javax.xml.parsers.SAXParser; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +public interface EJBDeploymentTool +{ + /** + * Process a deployment descriptor, generating the necessary vendor specific + * deployment files. + * + * @param descriptorFilename the name of the deployment descriptor + * @param saxParser a SAX parser which can be used to parse the deployment + * descriptor. + * @exception BuildException Description of Exception + */ + void processDescriptor( String descriptorFilename, SAXParser saxParser ) + throws BuildException; + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + void validateConfigured() + throws BuildException; + + /** + * Set the task which owns this tool + * + * @param task The new Task value + */ + void setTask( Task task ); + + /** + * Configure this tool for use in the ejbjar task. + * + * @param config Description of Parameter + */ + void configure( EjbJar.Config config ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java new file mode 100644 index 000000000..df7aa90bb --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbJar.java @@ -0,0 +1,567 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb;// Standard java imports +import java.io.File; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import javax.xml.parsers.ParserConfigurationException;// XML imports +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory;// Apache/Ant imports +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.xml.sax.SAXException; + +/** + *

              + * + * Provides automated ejb jar file creation for ant. Extends the MatchingTask + * class provided in the default ant distribution to provide a directory + * scanning EJB jarfile generator.

              + * + * The task works by taking the deployment descriptors one at a time and parsing + * them to locate the names of the classes which should be placed in the jar. + * The classnames are translated to java.io.Files by replacing periods with + * File.separatorChar and resolving the generated filename as a relative path + * under the srcDir attribute. All necessary files are then assembled into a + * jarfile. One jarfile is constructed for each deployment descriptor found. + *

              + * + * Functionality is currently provided for standard EJB1.1 jars and Weblogic 5.1 + * jars. The weblogic deployment descriptors, used in constructing the Weblogic + * jar, are located based on a simple naming convention. The name of the + * standard deployment descriptor is taken upto the first instance of a String, + * specified by the attribute baseNameTerminator, and then the regular Weblogic + * descriptor name is appended. For example if baseNameTerminator is set to '-', + * its default value, and a standard descriptor is called Foo-ejb-jar.xml then + * the files Foo-weblogic-ejb-jar.xml and Foo-weblogic-cmp-rdbms-jar.xml will be + * looked for, and if found, included in the jarfile.

              + * + * Attributes and setter methods are provided to support optional generation of + * Weblogic5.1 jars, optional deletion of generic jar files, setting alternate + * values for baseNameTerminator, and setting the strings to append to the names + * of the generated jarfiles.

              + * + * @author Tim Fennell + */ +public class EjbJar extends MatchingTask +{ + + private Config config = new Config(); + + /** + * Instance variable that stores the suffix for the generated jarfile. + */ + private String genericJarSuffix = "-generic.jar"; + + /** + * The list of deployment tools we are going to run. + */ + private ArrayList deploymentTools = new ArrayList(); + + /** + * Stores a handle to the directory to put the Jar files in. This is only + * used by the generic deployment descriptor tool which is created if no + * other deployment descriptor tools are provided. Normally each deployment + * tool will specify the desitination dir itself. + */ + private File destDir; + + /** + * Set the base name of the EJB jar that is to be created if it is not to be + * determined from the name of the deployment descriptor files. + * + * @param inValue the basename that will be used when writing the jar file + * containing the EJB + */ + public void setBasejarname( String inValue ) + { + config.baseJarName = inValue; + if( config.namingScheme == null ) + { + config.namingScheme = new NamingScheme(); + config.namingScheme.setValue( NamingScheme.BASEJARNAME ); + } + else if( !config.namingScheme.getValue().equals( NamingScheme.BASEJARNAME ) ) + { + throw new BuildException( "The basejarname attribute is not compatible with the " + + config.namingScheme.getValue() + " naming scheme" ); + } + } + + /** + * Set the baseNameTerminator. The basename terminator is the string which + * terminates the bean name. The convention used by this task is that bean + * descriptors are named as the BeanName with some suffix. The + * baseNameTerminator string separates the bean name and the suffix and is + * used to determine the bean name. + * + * @param inValue a string which marks the end of the basename. + */ + public void setBasenameterminator( String inValue ) + { + config.baseNameTerminator = inValue; + } + + /** + * Set the classpath to use when resolving classes for inclusion in the jar. + * + * @param classpath the classpath to use. + */ + public void setClasspath( Path classpath ) + { + config.classpath = classpath; + } + + /** + * Set the descriptor directory. The descriptor directory contains the EJB + * deployment descriptors. These are XML files that declare the properties + * of a bean in a particular deployment scenario. Such properties include, + * for example, the transactional nature of the bean and the security access + * control to the bean's methods. + * + * @param inDir the directory containing the deployment descriptors. + */ + public void setDescriptordir( File inDir ) + { + config.descriptorDir = inDir; + } + + + /** + * Set the destination directory. The EJB jar files will be written into + * this directory. The jar files that exist in this directory are also used + * when determining if the contents of the jar file have changed. Note that + * this parameter is only used if no deployment tools are specified. + * Typically each deployment tool will specify its own destination + * directory. + * + * @param inDir The new Destdir value + */ + public void setDestdir( File inDir ) + { + this.destDir = inDir; + } + + /** + * Set the flat dest dir flag. This flag controls whether the destination + * jars are written out in the destination directory with the same + * hierarchal structure from which the deployment descriptors have been + * read. If this is set to true the generated EJB jars are written into the + * root of the destination directory, otherwise they are written out in the + * same relative position as the deployment descriptors in the descriptor + * directory. + * + * @param inValue the new value of the flatdestdir flag. + */ + public void setFlatdestdir( boolean inValue ) + { + config.flatDestDir = inValue; + } + + /** + * Set the suffix for the generated jar file. When generic jars are + * generated, they have a suffix which is appended to the the bean name to + * create the name of the jar file. Note that this suffix includes the + * extension fo te jar file and should therefore end with an appropriate + * extension such as .jar or .ear + * + * @param inString the string to use as the suffix. + */ + public void setGenericjarsuffix( String inString ) + { + this.genericJarSuffix = inString; + } + + + /** + * Set the Manifest file to use when jarring. As of EJB 1.1, manifest files + * are no longer used to configure the EJB. However, they still have a vital + * importance if the EJB is intended to be packaged in an EAR file. By + * adding "Class-Path" settings to a Manifest file, the EJB can look for + * classes inside the EAR file itself, allowing for easier deployment. This + * is outlined in the J2EE specification, and all J2EE components are meant + * to support it. + * + * @param manifest The new Manifest value + */ + public void setManifest( File manifest ) + { + config.manifest = manifest; + } + + /** + * Set the naming scheme used to determine the name of the generated jars + * from the deployment descriptor + * + * @param namingScheme The new Naming value + */ + public void setNaming( NamingScheme namingScheme ) + { + config.namingScheme = namingScheme; + if( !config.namingScheme.getValue().equals( NamingScheme.BASEJARNAME ) && + config.baseJarName != null ) + { + throw new BuildException( "The basejarname attribute is not compatible with the " + + config.namingScheme.getValue() + " naming scheme" ); + } + } + + /** + * Set the srcdir attribute. The source directory is the directory that + * contains the classes that will be added to the EJB jar. Typically this + * will include the home and remote interfaces and the bean class. + * + * @param inDir the source directory. + */ + public void setSrcdir( File inDir ) + { + config.srcDir = inDir; + } + + /** + * Create a Borland nested element used to configure a deployment tool for + * Borland server. + * + * @return the deployment tool instance to be configured. + */ + public BorlandDeploymentTool createBorland() + { + log( "Borland deployment tools", Project.MSG_VERBOSE ); + + BorlandDeploymentTool tool = new BorlandDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * creates a nested classpath element. This classpath is used to locate the + * super classes and interfaces of the classes that will make up the EJB + * jar. + * + * @return the path to be configured. + */ + public Path createClasspath() + { + if( config.classpath == null ) + { + config.classpath = new Path( project ); + } + return config.classpath.createPath(); + } + + /** + * Create a DTD location record. This stores the location of a DTD. The DTD + * is identified by its public Id. The location may either be a file + * location or a resource location. + * + * @return Description of the Returned Value + */ + public DTDLocation createDTD() + { + DTDLocation dtdLocation = new DTDLocation(); + config.dtdLocations.add( dtdLocation ); + + return dtdLocation; + } + + /** + * Create a nested element used to configure a deployment tool for iPlanet + * Application Server. + * + * @return the deployment tool instance to be configured. + */ + public IPlanetDeploymentTool createIplanet() + { + log( "iPlanet Application Server deployment tools", Project.MSG_VERBOSE ); + + IPlanetDeploymentTool tool = new IPlanetDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a jboss nested element used to configure a deployment tool for + * Jboss server. + * + * @return the deployment tool instance to be configured. + */ + public JbossDeploymentTool createJboss() + { + JbossDeploymentTool tool = new JbossDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a file set for support elements + * + * @return a fileset which can be populated with support files. + */ + public FileSet createSupport() + { + FileSet supportFileSet = new FileSet(); + config.supportFileSets.add( supportFileSet ); + return supportFileSet; + } + + /** + * Create a weblogic nested element used to configure a deployment tool for + * Weblogic server. + * + * @return the deployment tool instance to be configured. + */ + public WeblogicDeploymentTool createWeblogic() + { + WeblogicDeploymentTool tool = new WeblogicDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a nested element for weblogic when using the Toplink Object- + * Relational mapping. + * + * @return the deployment tool instance to be configured. + */ + public WeblogicTOPLinkDeploymentTool createWeblogictoplink() + { + WeblogicTOPLinkDeploymentTool tool = new WeblogicTOPLinkDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Create a websphere nested element used to configure a deployment tool for + * Websphere 4.0 server. + * + * @return the deployment tool instance to be configured. + */ + public WebsphereDeploymentTool createWebsphere() + { + WebsphereDeploymentTool tool = new WebsphereDeploymentTool(); + tool.setTask( this ); + deploymentTools.add( tool ); + return tool; + } + + /** + * Invoked by Ant after the task is prepared, when it is ready to execute + * this task. This will configure all of the nested deployment tools to + * allow them to process the jar. If no deployment tools have been + * configured a generic tool is created to handle the jar. A parser is + * configured and then each descriptor found is passed to all the deployment + * tool elements for processing. + * + * @exception BuildException thrown whenever a problem is encountered that + * cannot be recovered from, to signal to ant that a major problem + * occurred within this task. + */ + public void execute() + throws BuildException + { + validateConfig(); + + if( deploymentTools.size() == 0 ) + { + GenericDeploymentTool genericTool = new GenericDeploymentTool(); + genericTool.setTask( this ); + genericTool.setDestdir( destDir ); + genericTool.setGenericJarSuffix( genericJarSuffix ); + deploymentTools.add( genericTool ); + } + + for( Iterator i = deploymentTools.iterator(); i.hasNext(); ) + { + EJBDeploymentTool tool = ( EJBDeploymentTool )i.next(); + tool.configure( config ); + tool.validateConfigured(); + } + + try + { + // Create the parser using whatever parser the system dictates + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setValidating( true ); + SAXParser saxParser = saxParserFactory.newSAXParser(); + + DirectoryScanner ds = getDirectoryScanner( config.descriptorDir ); + ds.scan(); + String[] files = ds.getIncludedFiles(); + + log( files.length + " deployment descriptors located.", + Project.MSG_VERBOSE ); + + // Loop through the files. Each file represents one deployment + // descriptor, and hence one bean in our model. + for( int index = 0; index < files.length; ++index ) + { + // process the deployment descriptor in each tool + for( Iterator i = deploymentTools.iterator(); i.hasNext(); ) + { + EJBDeploymentTool tool = ( EJBDeploymentTool )i.next(); + tool.processDescriptor( files[index], saxParser ); + } + } + } + catch( SAXException se ) + { + String msg = "SAXException while creating parser." + + " Details: " + + se.getMessage(); + throw new BuildException( msg, se ); + } + catch( ParserConfigurationException pce ) + { + String msg = "ParserConfigurationException while creating parser. " + + "Details: " + pce.getMessage(); + throw new BuildException( msg, pce ); + } + } + + private void validateConfig() + { + if( config.srcDir == null ) + { + throw new BuildException( "The srcDir attribute must be specified" ); + } + + if( config.descriptorDir == null ) + { + config.descriptorDir = config.srcDir; + } + + if( config.namingScheme == null ) + { + config.namingScheme = new NamingScheme(); + config.namingScheme.setValue( NamingScheme.DESCRIPTOR ); + } + else if( config.namingScheme.getValue().equals( NamingScheme.BASEJARNAME ) && + config.baseJarName == null ) + { + throw new BuildException( "The basejarname attribute must be specified " + + "with the basejarname naming scheme" ); + } + } + + public static class DTDLocation + { + private String publicId = null; + private String location = null; + + public void setLocation( String location ) + { + this.location = location; + } + + public void setPublicId( String publicId ) + { + this.publicId = publicId; + } + + public String getLocation() + { + return location; + } + + public String getPublicId() + { + return publicId; + } + } + + public static class NamingScheme extends EnumeratedAttribute + { + public final static String EJB_NAME = "ejb-name"; + public final static String DIRECTORY = "directory"; + public final static String DESCRIPTOR = "descriptor"; + public final static String BASEJARNAME = "basejarname"; + + public String[] getValues() + { + return new String[]{EJB_NAME, DIRECTORY, DESCRIPTOR, BASEJARNAME}; + } + } + + /** + * A class which contains the configuration state of the ejbjar task. This + * state is passed to the deployment tools for configuration + * + * @author RT + */ + static class Config + { + + /** + * Instance variable that marks the end of the 'basename' + */ + public String baseNameTerminator = "-"; + + /** + * Instance variable that determines whether to use a package structure + * of a flat directory as the destination for the jar files. + */ + public boolean flatDestDir = false; + + /** + * A Fileset of support classes + */ + public List supportFileSets = new ArrayList(); + + /** + * The list of configured DTD locations + */ + public ArrayList dtdLocations = new ArrayList(); + + /** + * Stores a handle to the destination EJB Jar file + */ + public String baseJarName; + + /** + * The classpath to use when loading classes + */ + public Path classpath; + + /** + * Stores a handle to the directory under which to search for deployment + * descriptors + */ + public File descriptorDir; + + /** + * The Manifest file + */ + public File manifest; + + /** + * The naming scheme used to determine the generated jar name from the + * descriptor information + */ + public NamingScheme namingScheme; + /** + * Stores a handle to the directory under which to search for class + * files + */ + public File srcDir; + }// end of execute() +} + + + + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java new file mode 100644 index 000000000..0041c3756 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/Ejbc.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.Path; + +/** + * Build EJB support classes using Weblogic's ejbc tool from a directory + * containing a set of deployment descriptors. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class Ejbc extends MatchingTask +{ + + public boolean keepgenerated; + + /** + * The classpath to be used in the weblogic ejbc calls. It must contain the + * weblogic classes and the implementation classes of the home and + * remote interfaces. + */ + private String classpath; + /** + * The root directory of the tree containing the serialised deployment + * desciptors. The actual deployment descriptor files are selected using + * include and exclude constructs on the ejbc task provided by the + * MatchingTask superclass. + */ + private File descriptorDirectory; + + /** + * The directory where generated files are placed. + */ + private File generatedFilesDirectory; + + /** + * The name of the manifest file generated for the EJB jar. + */ + private File generatedManifestFile; + + /** + * The source directory for the home and remote interfaces. This is used to + * determine if the generated deployment classes are out of date. + */ + private File sourceDirectory; + + /** + * Set the classpath to be used for this compilation. + * + * @param s The new Classpath value + */ + public void setClasspath( String s ) + { + this.classpath = project.translatePath( s ); + } + + /** + * Set the directory from where the serialised deployment descriptors are to + * be read. + * + * @param dirName the name of the directory containing the serialised + * deployment descriptors. + */ + public void setDescriptors( String dirName ) + { + descriptorDirectory = new File( dirName ); + } + + /** + * Set the directory into which the support classes, RMI stubs, etc are to + * be written + * + * @param dirName the name of the directory into which code is generated + */ + public void setDest( String dirName ) + { + generatedFilesDirectory = new File( dirName ); + } + + public void setKeepgenerated( String newKeepgenerated ) + { + keepgenerated = Boolean.valueOf( newKeepgenerated.trim() ).booleanValue(); + + } + + /** + * Set the generated manifest file. For each EJB that is processed an entry + * is created in this file. This can then be used to create a jar file for + * dploying the beans. + * + * @param manifestFilename The new Manifest value + */ + public void setManifest( String manifestFilename ) + { + generatedManifestFile = new File( manifestFilename ); + } + + /** + * Set the directory containing the source code for the home interface, + * remote interface and public key class definitions. + * + * @param dirName the directory containg the source tree for the EJB's + * interface classes. + */ + public void setSrc( String dirName ) + { + sourceDirectory = new File( dirName ); + } + + public boolean getKeepgenerated() + { + return keepgenerated; + } + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * a helper task. This approach allows the classpath of the helper task to + * be set. Since the weblogic tools require the class files of the project's + * home and remote interfaces to be available in the classpath, this also + * avoids having to start ant with the class path of the project it is + * building. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( descriptorDirectory == null || + !descriptorDirectory.isDirectory() ) + { + throw new BuildException( "descriptors directory " + descriptorDirectory.getPath() + + " is not valid" ); + } + if( generatedFilesDirectory == null || + !generatedFilesDirectory.isDirectory() ) + { + throw new BuildException( "dest directory " + generatedFilesDirectory.getPath() + + " is not valid" ); + } + + if( sourceDirectory == null || + !sourceDirectory.isDirectory() ) + { + throw new BuildException( "src directory " + sourceDirectory.getPath() + + " is not valid" ); + } + + String systemClassPath = System.getProperty( "java.class.path" ); + String execClassPath = project.translatePath( systemClassPath + ":" + classpath + + ":" + generatedFilesDirectory ); + // get all the files in the descriptor directory + DirectoryScanner ds = super.getDirectoryScanner( descriptorDirectory ); + + String[] files = ds.getIncludedFiles(); + + Java helperTask = ( Java )project.createTask( "java" ); + helperTask.setTaskName( getTaskName() ); + helperTask.setFork( true ); + helperTask.setClassname( "org.apache.tools.ant.taskdefs.optional.ejb.EjbcHelper" ); + String args = ""; + args += " " + descriptorDirectory; + args += " " + generatedFilesDirectory; + args += " " + sourceDirectory; + args += " " + generatedManifestFile; + args += " " + keepgenerated; + + for( int i = 0; i < files.length; ++i ) + { + args += " " + files[i]; + } + + Commandline.Argument arguments = helperTask.createArg(); + arguments.setLine( args ); + helperTask.setClasspath( new Path( project, execClassPath ) ); + if( helperTask.executeJava() != 0 ) + { + throw new BuildException( "Execution of ejbc helper failed" ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java new file mode 100644 index 000000000..73b2ed414 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/EjbcHelper.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.PrintWriter; +import java.util.Vector; +import javax.ejb.deployment.DeploymentDescriptor; +import javax.ejb.deployment.EntityDescriptor; + + +/** + * A helper class which performs the actual work of the ejbc task. This class is + * run with a classpath which includes the weblogic tools and the home and + * remote interface class files referenced in the deployment descriptors being + * processed. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class EjbcHelper +{ + + /** + * The names of the serialised deployment descriptors + */ + String[] descriptors; + + /** + * The classpath to be used in the weblogic ejbc calls. It must contain the + * weblogic classes and the implementation classes of the home and + * remote interfaces. + */ + private String classpath; + /** + * The root directory of the tree containing the serialised deployment + * desciptors. + */ + private File descriptorDirectory; + + /** + * The directory where generated files are placed. + */ + private File generatedFilesDirectory; + + private boolean keepGenerated; + + /** + * The name of the manifest file generated for the EJB jar. + */ + private File manifestFile; + + /** + * The source directory for the home and remote interfaces. This is used to + * determine if the generated deployment classes are out of date. + */ + private File sourceDirectory; + + /** + * Initialise the EjbcHelper by reading the command arguments. + * + * @param args Description of Parameter + */ + private EjbcHelper( String[] args ) + { + int index = 0; + descriptorDirectory = new File( args[index++] ); + generatedFilesDirectory = new File( args[index++] ); + sourceDirectory = new File( args[index++] ); + manifestFile = new File( args[index++] ); + keepGenerated = Boolean.valueOf( args[index++] ).booleanValue(); + + descriptors = new String[args.length - index]; + for( int i = 0; index < args.length; ++i ) + { + descriptors[i] = args[index++]; + } + } + + /** + * Command line interface for the ejbc helper task. + * + * @param args The command line arguments + * @exception Exception Description of Exception + */ + public static void main( String[] args ) + throws Exception + { + EjbcHelper helper = new EjbcHelper( args ); + helper.process(); + } + + private String[] getCommandLine( boolean debug, File descriptorFile ) + { + Vector v = new Vector(); + if( !debug ) + { + v.addElement( "-noexit" ); + } + if( keepGenerated ) + { + v.addElement( "-keepgenerated" ); + } + v.addElement( "-d" ); + v.addElement( generatedFilesDirectory.getPath() ); + v.addElement( descriptorFile.getPath() ); + + String[] args = new String[v.size()]; + v.copyInto( args ); + return args; + } + + /** + * Determine if the weblogic EJB support classes need to be regenerated for + * a given deployment descriptor. This process attempts to determine if the + * support classes need to be rebuilt. It does this by examining only some + * of the support classes which are typically generated. If the ejbc task is + * interrupted generating the support classes for a bean, all of the support + * classes should be removed to force regeneration of the support classes. + * + * @param descriptorFile the serialised deployment descriptor + * @return true if the support classes need to be regenerated. + * @throws IOException if the descriptor file cannot be closed. + */ + private boolean isRegenRequired( File descriptorFile ) + throws IOException + { + // read in the descriptor. Under weblogic, the descriptor is a weblogic + // specific subclass which has references to the implementation classes. + // These classes must, therefore, be in the classpath when the deployment + // descriptor is loaded from the .ser file + FileInputStream fis = null; + try + { + fis = new FileInputStream( descriptorFile ); + ObjectInputStream ois = new ObjectInputStream( fis ); + DeploymentDescriptor dd = ( DeploymentDescriptor )ois.readObject(); + fis.close(); + + String homeInterfacePath = dd.getHomeInterfaceClassName().replace( '.', '/' ) + ".java"; + String remoteInterfacePath = dd.getRemoteInterfaceClassName().replace( '.', '/' ) + ".java"; + String primaryKeyClassPath = null; + if( dd instanceof EntityDescriptor ) + { + primaryKeyClassPath = ( ( EntityDescriptor )dd ).getPrimaryKeyClassName().replace( '.', '/' ) + ".java"; + ; + } + + File homeInterfaceSource = new File( sourceDirectory, homeInterfacePath ); + File remoteInterfaceSource = new File( sourceDirectory, remoteInterfacePath ); + File primaryKeyClassSource = null; + if( primaryKeyClassPath != null ) + { + primaryKeyClassSource = new File( sourceDirectory, remoteInterfacePath ); + } + + // are any of the above out of date. + // we find the implementation classes and see if they are older than any + // of the above or the .ser file itself. + String beanClassBase = dd.getEnterpriseBeanClassName().replace( '.', '/' ); + File ejbImplentationClass + = new File( generatedFilesDirectory, beanClassBase + "EOImpl.class" ); + File homeImplementationClass + = new File( generatedFilesDirectory, beanClassBase + "HomeImpl.class" ); + File beanStubClass + = new File( generatedFilesDirectory, beanClassBase + "EOImpl_WLStub.class" ); + + // if the implementation classes don;t exist regenerate + if( !ejbImplentationClass.exists() || !homeImplementationClass.exists() || + !beanStubClass.exists() ) + { + return true; + } + + // Is the ser file or any of the source files newer then the class files. + // firstly find the oldest of the two class files. + long classModificationTime = ejbImplentationClass.lastModified(); + if( homeImplementationClass.lastModified() < classModificationTime ) + { + classModificationTime = homeImplementationClass.lastModified(); + } + if( beanStubClass.lastModified() < classModificationTime ) + { + classModificationTime = beanStubClass.lastModified(); + } + + if( descriptorFile.lastModified() > classModificationTime || + homeInterfaceSource.lastModified() > classModificationTime || + remoteInterfaceSource.lastModified() > classModificationTime ) + { + return true; + } + + if( primaryKeyClassSource != null && + primaryKeyClassSource.lastModified() > classModificationTime ) + { + return true; + } + } + catch( Throwable descriptorLoadException ) + { + System.out.println( "Exception occurred reading " + descriptorFile.getName() + " - continuing" ); + // any problems - just regenerate + return true; + } + finally + { + if( fis != null ) + { + fis.close(); + } + } + + return false; + } + + /** + * Process the descriptors in turn generating support classes for each and a + * manifest file for all of the beans. + * + * @exception Exception Description of Exception + */ + private void process() + throws Exception + { + String manifest = "Manifest-Version: 1.0\n\n"; + for( int i = 0; i < descriptors.length; ++i ) + { + String descriptorName = descriptors[i]; + File descriptorFile = new File( descriptorDirectory, descriptorName ); + + if( isRegenRequired( descriptorFile ) ) + { + System.out.println( "Running ejbc for " + descriptorFile.getName() ); + regenerateSupportClasses( descriptorFile ); + } + else + { + System.out.println( descriptorFile.getName() + " is up to date" ); + } + manifest += "Name: " + descriptorName.replace( '\\', '/' ) + "\nEnterprise-Bean: True\n\n"; + } + + FileWriter fw = new FileWriter( manifestFile ); + PrintWriter pw = new PrintWriter( fw ); + pw.print( manifest ); + fw.flush(); + fw.close(); + } + + /** + * Perform the weblogic.ejbc call to regenerate the support classes. Note + * that this method relies on an undocumented -noexit option to the ejbc + * tool to stop the ejbc tool exiting the VM altogether. + * + * @param descriptorFile Description of Parameter + * @exception Exception Description of Exception + */ + private void regenerateSupportClasses( File descriptorFile ) + throws Exception + { + // create a Java task to do the rebuild + + + String[] args = getCommandLine( false, descriptorFile ); + + try + { + weblogic.ejbc.main( args ); + } + catch( Exception e ) + { + // run with no exit for better reporting + String[] newArgs = getCommandLine( true, descriptorFile ); + weblogic.ejbc.main( newArgs ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java new file mode 100644 index 000000000..29a49f9aa --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/GenericDeploymentTool.java @@ -0,0 +1,970 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import javax.xml.parsers.SAXParser; +import org.apache.bcel.*; +import org.apache.bcel.classfile.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Location; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.*; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.util.depend.Dependencies; +import org.apache.tools.ant.util.depend.Filter; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + + +/** + * A deployment tool which creates generic EJB jars. Generic jars contains only + * those classes and META-INF entries specified in the EJB 1.1 standard This + * class is also used as a framework for the creation of vendor specific + * deployment tools. A number of template methods are provided through which the + * vendor specific tool can hook into the EJB creation process. + * + * @author RT + */ +public class GenericDeploymentTool implements EJBDeploymentTool +{ + /** + * Private constants that are used when constructing the standard jarfile + */ + protected final static String META_DIR = "META-INF/"; + protected final static String EJB_DD = "ejb-jar.xml"; + + /** + * Instance variable that stores the suffix for the generated jarfile. + */ + private String genericJarSuffix = "-generic.jar"; + + /** + * The classloader generated from the given classpath to load the super + * classes and super interfaces. + */ + private ClassLoader classpathLoader = null; + + /** + * List of files have been loaded into the EJB jar + */ + private List addedfiles; + + /** + * The classpath to use with this deployment tool. This is appended to any + * paths from the ejbjar task itself. + */ + private Path classpath; + + /** + * The configuration from the containing task. This config combined with the + * settings of the individual attributes here constitues the complete config + * for this deployment tool. + */ + private EjbJar.Config config; + + /** + * Stores a handle to the directory to put the Jar files in + */ + private File destDir; + + /** + * Handler used to parse the EJB XML descriptor + */ + private DescriptorHandler handler; + + /** + * The task to which this tool belongs. This is used to access services + * provided by the ant core, such as logging. + */ + private Task task; + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + this.classpath = classpath; + } + + /** + * Setter used to store the value of destination directory prior to + * execute() being called. + * + * @param inDir the destination directory. + */ + public void setDestdir( File inDir ) + { + this.destDir = inDir; + } + + /** + * Setter used to store the suffix for the generated jar file. + * + * @param inString the string to use as the suffix. + */ + public void setGenericJarSuffix( String inString ) + { + this.genericJarSuffix = inString; + } + + + /** + * Set the task which owns this tool + * + * @param task The new Task value + */ + public void setTask( Task task ) + { + this.task = task; + } + + /** + * Get the prefix for vendor deployment descriptors. This will contain the + * path and the start of the descriptor name, depending on the naming scheme + * + * @param baseName Description of Parameter + * @param descriptorFileName Description of Parameter + * @return The VendorDDPrefix value + */ + public String getVendorDDPrefix( String baseName, String descriptorFileName ) + { + String ddPrefix = null; + + if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.DESCRIPTOR ) ) + { + ddPrefix = baseName + config.baseNameTerminator; + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.BASEJARNAME ) || + config.namingScheme.getValue().equals( EjbJar.NamingScheme.EJB_NAME ) || + config.namingScheme.getValue().equals( EjbJar.NamingScheme.DIRECTORY ) ) + { + String canonicalDescriptor = descriptorFileName.replace( '\\', '/' ); + int index = canonicalDescriptor.lastIndexOf( '/' ); + if( index == -1 ) + { + ddPrefix = ""; + } + else + { + ddPrefix = descriptorFileName.substring( 0, index + 1 ); + } + } + return ddPrefix; + } + + + /** + * Configure this tool for use in the ejbjar task. + * + * @param config Description of Parameter + */ + public void configure( EjbJar.Config config ) + { + this.config = config; + + classpathLoader = null; + } + + /** + * Add the classpath for the user classes + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( task.getProject() ); + } + return classpath.createPath(); + } + + public void processDescriptor( String descriptorFileName, SAXParser saxParser ) + { + + checkConfiguration( descriptorFileName, saxParser ); + + try + { + handler = getDescriptorHandler( config.srcDir ); + + // Retrive the files to be added to JAR from EJB descriptor + Hashtable ejbFiles = parseEjbFiles( descriptorFileName, saxParser ); + + // Add any support classes specified in the build file + addSupportClasses( ejbFiles ); + + // Determine the JAR filename (without filename extension) + String baseName = getJarBaseName( descriptorFileName ); + + String ddPrefix = getVendorDDPrefix( baseName, descriptorFileName ); + + // First the regular deployment descriptor + ejbFiles.put( META_DIR + EJB_DD, + new File( config.descriptorDir, descriptorFileName ) ); + + // now the vendor specific files, if any + addVendorFiles( ejbFiles, ddPrefix ); + + // add any dependent files + checkAndAddDependants( ejbFiles ); + + // Lastly create File object for the Jar files. If we are using + // a flat destination dir, then we need to redefine baseName! + if( config.flatDestDir && baseName.length() != 0 ) + { + int startName = baseName.lastIndexOf( File.separator ); + if( startName == -1 ) + { + startName = 0; + } + + int endName = baseName.length(); + baseName = baseName.substring( startName, endName ); + } + + File jarFile = getVendorOutputJarFile( baseName ); + + // Check to see if we need a build and start doing the work! + if( needToRebuild( ejbFiles, jarFile ) ) + { + // Log that we are going to build... + log( "building " + + jarFile.getName() + + " with " + + String.valueOf( ejbFiles.size() ) + + " files", + Project.MSG_INFO ); + + // Use helper method to write the jarfile + String publicId = getPublicId(); + writeJar( baseName, jarFile, ejbFiles, publicId ); + + } + else + { + // Log that the file is up to date... + log( jarFile.toString() + " is up to date.", + Project.MSG_VERBOSE ); + } + + } + catch( SAXException se ) + { + String msg = "SAXException while parsing '" + + descriptorFileName.toString() + + "'. This probably indicates badly-formed XML." + + " Details: " + + se.getMessage(); + throw new BuildException( msg, se ); + } + catch( IOException ioe ) + { + String msg = "IOException while parsing'" + + descriptorFileName.toString() + + "'. This probably indicates that the descriptor" + + " doesn't exist. Details: " + + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + } + + /** + * Called to validate that the tool parameters have been configured. + * + * @throws BuildException If the Deployment Tool's configuration isn't valid + */ + public void validateConfigured() + throws BuildException + { + if( ( destDir == null ) || ( !destDir.isDirectory() ) ) + { + String msg = "A valid destination directory must be specified " + + "using the \"destdir\" attribute."; + throw new BuildException( msg, getLocation() ); + } + } + + + /** + * Returns a Classloader object which parses the passed in generic EjbJar + * classpath. The loader is used to dynamically load classes from + * javax.ejb.* and the classes being added to the jar. + * + * @return The ClassLoaderForBuild value + */ + protected ClassLoader getClassLoaderForBuild() + { + if( classpathLoader != null ) + { + return classpathLoader; + } + + Path combinedClasspath = getCombinedClasspath(); + + // only generate a new ClassLoader if we have a classpath + if( combinedClasspath == null ) + { + classpathLoader = getClass().getClassLoader(); + } + else + { + classpathLoader = new AntClassLoader( getTask().getProject(), combinedClasspath ); + } + + return classpathLoader; + } + + /** + * Get the classpath by combining the one from the surrounding task, if any + * and the one from this tool. + * + * @return The CombinedClasspath value + */ + protected Path getCombinedClasspath() + { + Path combinedPath = classpath; + if( config.classpath != null ) + { + if( combinedPath == null ) + { + combinedPath = config.classpath; + } + else + { + combinedPath.append( config.classpath ); + } + } + + return combinedPath; + } + + /** + * Get the basename terminator. + * + * @return The Config value + */ + protected EjbJar.Config getConfig() + { + return config; + } + + protected DescriptorHandler getDescriptorHandler( File srcDir ) + { + DescriptorHandler handler = new DescriptorHandler( getTask(), srcDir ); + + registerKnownDTDs( handler ); + + // register any DTDs supplied by the user + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + } + return handler; + } + + /** + * Get the desitination directory. + * + * @return The DestDir value + */ + protected File getDestDir() + { + return destDir; + } + + + /** + * Using the EJB descriptor file name passed from the ejbjar + * task, this method returns the "basename" which will be used to name the + * completed JAR file. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @return The "basename" which will be used to name the completed JAR file + */ + protected String getJarBaseName( String descriptorFileName ) + { + + String baseName = ""; + + // Work out what the base name is + if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.BASEJARNAME ) ) + { + String canonicalDescriptor = descriptorFileName.replace( '\\', '/' ); + int index = canonicalDescriptor.lastIndexOf( '/' ); + if( index != -1 ) + { + baseName = descriptorFileName.substring( 0, index + 1 ); + } + baseName += config.baseJarName; + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.DESCRIPTOR ) ) + { + int lastSeparatorIndex = descriptorFileName.lastIndexOf( File.separator ); + int endBaseName = -1; + if( lastSeparatorIndex != -1 ) + { + endBaseName = descriptorFileName.indexOf( config.baseNameTerminator, + lastSeparatorIndex ); + } + else + { + endBaseName = descriptorFileName.indexOf( config.baseNameTerminator ); + } + + if( endBaseName != -1 ) + { + baseName = descriptorFileName.substring( 0, endBaseName ); + } + baseName = descriptorFileName.substring( 0, endBaseName ); + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.DIRECTORY ) ) + { + int lastSeparatorIndex = descriptorFileName.lastIndexOf( File.separator ); + String dirName = descriptorFileName.substring( 0, lastSeparatorIndex ); + int dirSeparatorIndex = dirName.lastIndexOf( File.separator ); + if( dirSeparatorIndex != -1 ) + { + dirName = dirName.substring( dirSeparatorIndex + 1 ); + } + + baseName = dirName; + } + else if( config.namingScheme.getValue().equals( EjbJar.NamingScheme.EJB_NAME ) ) + { + baseName = handler.getEjbName(); + } + return baseName; + } + + protected Location getLocation() + { + return getTask().getLocation(); + } + + /** + * Returns the Public ID of the DTD specified in the EJB descriptor. Not + * every vendor-specific DeploymentTool will need to reference + * this value or may want to determine this value in a vendor-specific way. + * + * @return Public ID of the DTD specified in the EJB descriptor. + */ + protected String getPublicId() + { + return handler.getPublicId(); + } + + /** + * Get the task for this tool. + * + * @return The Task value + */ + protected Task getTask() + { + return task; + } + + /** + * Utility method that encapsulates the logic of adding a file entry to a + * .jar file. Used by execute() to add entries to the jar file as it is + * constructed. + * + * @param jStream A JarOutputStream into which to write the jar entry. + * @param inputFile A File from which to read the contents the file being + * added. + * @param logicalFilename A String representing the name, including all + * relevant path information, that should be stored for the entry being + * added. + * @exception BuildException Description of Exception + */ + protected void addFileToJar( JarOutputStream jStream, + File inputFile, + String logicalFilename ) + throws BuildException + { + FileInputStream iStream = null; + try + { + if( !addedfiles.contains( logicalFilename ) ) + { + iStream = new FileInputStream( inputFile ); + // Create the zip entry and add it to the jar file + ZipEntry zipEntry = new ZipEntry( logicalFilename.replace( '\\', '/' ) ); + jStream.putNextEntry( zipEntry ); + + // Create the file input stream, and buffer everything over + // to the jar output stream + byte[] byteBuffer = new byte[2 * 1024]; + int count = 0; + do + { + jStream.write( byteBuffer, 0, count ); + count = iStream.read( byteBuffer, 0, byteBuffer.length ); + }while ( count != -1 ); + + //add it to list of files in jar + addedfiles.add( logicalFilename ); + } + } + catch( IOException ioe ) + { + log( "WARNING: IOException while adding entry " + + logicalFilename + " to jarfile from " + inputFile.getPath() + " " + + ioe.getClass().getName() + "-" + ioe.getMessage(), Project.MSG_WARN ); + } + finally + { + // Close up the file input stream for the class file + if( iStream != null ) + { + try + { + iStream.close(); + } + catch( IOException closeException ) + {} + } + } + } + + /** + * Adds any classes the user specifies using support nested elements + * to the ejbFiles Hashtable. + * + * @param ejbFiles Hashtable of EJB classes (and other) files that will be + * added to the completed JAR file + */ + protected void addSupportClasses( Hashtable ejbFiles ) + { + // add in support classes if any + Project project = task.getProject(); + for( Iterator i = config.supportFileSets.iterator(); i.hasNext(); ) + { + FileSet supportFileSet = ( FileSet )i.next(); + File supportBaseDir = supportFileSet.getDir( project ); + DirectoryScanner supportScanner = supportFileSet.getDirectoryScanner( project ); + supportScanner.scan(); + String[] supportFiles = supportScanner.getIncludedFiles(); + for( int j = 0; j < supportFiles.length; ++j ) + { + ejbFiles.put( supportFiles[j], new File( supportBaseDir, supportFiles[j] ) ); + } + } + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + // nothing to add for generic tool. + }// end of writeJar + + + /** + * Add all available classes, that depend on Remote, Home, Bean, PK + * + * @param checkEntries files, that are extracted from the deployment + * descriptor + * @exception BuildException Description of Exception + */ + protected void checkAndAddDependants( Hashtable checkEntries ) + throws BuildException + { + Dependencies visitor = new Dependencies(); + Set set = new TreeSet(); + Set newSet = new HashSet(); + final String base = config.srcDir.getAbsolutePath() + File.separator; + + Iterator i = checkEntries.keySet().iterator(); + while( i.hasNext() ) + { + String entryName = ( String )i.next(); + if( entryName.endsWith( ".class" ) ) + newSet.add( entryName.substring( 0, entryName.length() - ".class".length() ).replace( File.separatorChar, '/' ) ); + } + set.addAll( newSet ); + + do + { + i = newSet.iterator(); + while( i.hasNext() ) + { + String fileName = base + ( ( String )i.next() ).replace( '/', File.separatorChar ) + ".class"; + + try + { + JavaClass javaClass = new ClassParser( fileName ).parse(); + javaClass.accept( visitor ); + } + catch( IOException e ) + { + log( "exception: " + e.getMessage(), Project.MSG_INFO ); + } + } + newSet.clear(); + newSet.addAll( visitor.getDependencies() ); + visitor.clearDependencies(); + + Dependencies.applyFilter( newSet, + new Filter() + { + public boolean accept( Object object ) + { + String fileName = base + ( ( String )object ).replace( '/', File.separatorChar ) + ".class"; + return new File( fileName ).exists(); + } + } ); + newSet.removeAll( set ); + set.addAll( newSet ); + }while ( newSet.size() > 0 ); + + i = set.iterator(); + while( i.hasNext() ) + { + String next = ( ( String )i.next() ).replace( '/', File.separatorChar ); + checkEntries.put( next + ".class", new File( base + next + ".class" ) ); + log( "dependent class: " + next + ".class" + " - " + base + next + ".class", Project.MSG_VERBOSE ); + } + } + + /** + * This method is called as the first step in the processDescriptor method + * to allow vendor-specific subclasses to validate the task configuration + * prior to processing the descriptor. If the configuration is invalid, a + * BuildException should be thrown. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @exception BuildException Description of Exception + * @thows BuildException Thrown if the configuration is invalid + */ + protected void checkConfiguration( String descriptorFileName, + SAXParser saxParser ) + throws BuildException + { + + /* + * For the GenericDeploymentTool, do nothing. Vendor specific + * subclasses should throw a BuildException if the configuration is + * invalid for their server. + */ + } + + protected void log( String message, int level ) + { + getTask().log( message, level ); + } + + /** + * This method checks the timestamp on each file listed in the + * ejbFiles and compares them to the timestamp on the jarFile + * . If the jarFile's timestamp is more recent than each + * EJB file, true is returned. Otherwise, false + * is returned. TODO: find a way to check the manifest-file, that is + * found by naming convention + * + * @param ejbFiles Hashtable of EJB classes (and other) files that will be + * added to the completed JAR file + * @param jarFile JAR file which will contain all of the EJB classes (and + * other) files + * @return boolean indicating whether or not the jarFile is up + * to date + */ + protected boolean needToRebuild( Hashtable ejbFiles, File jarFile ) + { + if( jarFile.exists() ) + { + long lastBuild = jarFile.lastModified(); + + if( config.manifest != null && config.manifest.exists() && + config.manifest.lastModified() > lastBuild ) + { + log( "Build needed because manifest " + config.manifest + " is out of date", + Project.MSG_VERBOSE ); + return true; + } + + Iterator fileIter = ejbFiles.values().iterator(); + + // Loop through the files seeing if any has been touched + // more recently than the destination jar. + while( fileIter.hasNext() ) + { + File currentFile = ( File )fileIter.next(); + if( lastBuild < currentFile.lastModified() ) + { + log( "Build needed because " + currentFile.getPath() + " is out of date", + Project.MSG_VERBOSE ); + return true; + } + } + return false; + } + + return true; + } + + /** + * This method returns a list of EJB files found when the specified EJB + * descriptor is parsed and processed. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @return Hashtable of EJB class (and other) files to be added to the + * completed JAR file + * @throws SAXException Any SAX exception, possibly wrapping another + * exception + * @throws IOException An IOException from the parser, possibly from a the + * byte stream or character stream + */ + protected Hashtable parseEjbFiles( String descriptorFileName, SAXParser saxParser ) + throws IOException, SAXException + { + FileInputStream descriptorStream = null; + Hashtable ejbFiles = null; + + try + { + + /* + * Parse the ejb deployment descriptor. While it may not + * look like much, we use a SAXParser and an inner class to + * get hold of all the classfile names for the descriptor. + */ + descriptorStream = new FileInputStream( new File( config.descriptorDir, descriptorFileName ) ); + saxParser.parse( new InputSource( descriptorStream ), handler ); + + ejbFiles = handler.getFiles(); + + } + finally + { + if( descriptorStream != null ) + { + try + { + descriptorStream.close(); + } + catch( IOException closeException ) + {} + } + } + + return ejbFiles; + } + + /** + * Register the locations of all known DTDs. vendor-specific subclasses + * should override this method to define the vendor-specific locations of + * the EJB DTDs + * + * @param handler Description of Parameter + */ + protected void registerKnownDTDs( DescriptorHandler handler ) + { + // none to register for generic + } + + /** + * Returns true, if the meta-inf dir is being explicitly set, false + * otherwise. + * + * @return Description of the Returned Value + */ + protected boolean usingBaseJarName() + { + return config.baseJarName != null; + } + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarfile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + protected void writeJar( String baseName, File jarfile, Hashtable files, + String publicId ) + throws BuildException + { + + JarOutputStream jarStream = null; + try + { + // clean the addedfiles Vector + addedfiles = new ArrayList(); + + /* + * If the jarfile already exists then whack it and recreate it. + * Should probably think of a more elegant way to handle this + * so that in case of errors we don't leave people worse off + * than when we started =) + */ + if( jarfile.exists() ) + { + jarfile.delete(); + } + jarfile.getParentFile().mkdirs(); + jarfile.createNewFile(); + + InputStream in = null; + Manifest manifest = null; + try + { + File manifestFile = new File( getConfig().descriptorDir, baseName + "-manifest.mf" ); + if( manifestFile.exists() ) + { + in = new FileInputStream( manifestFile ); + } + else if( config.manifest != null ) + { + in = new FileInputStream( config.manifest ); + if( in == null ) + { + throw new BuildException( "Could not find manifest file: " + config.manifest, + getLocation() ); + } + } + else + { + String defaultManifest = "/org/apache/tools/ant/defaultManifest.mf"; + in = this.getClass().getResourceAsStream( defaultManifest ); + if( in == null ) + { + throw new BuildException( "Could not find default manifest: " + defaultManifest, + getLocation() ); + } + } + + manifest = new Manifest( in ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to read manifest", e, getLocation() ); + } + finally + { + if( in != null ) + { + in.close(); + } + } + + // Create the streams necessary to write the jarfile + + jarStream = new JarOutputStream( new FileOutputStream( jarfile ), manifest ); + jarStream.setMethod( JarOutputStream.DEFLATED ); + + // Loop through all the class files found and add them to the jar + for( Iterator entryIterator = files.keySet().iterator(); entryIterator.hasNext(); ) + { + String entryName = ( String )entryIterator.next(); + File entryFile = ( File )files.get( entryName ); + + log( "adding file '" + entryName + "'", + Project.MSG_VERBOSE ); + + addFileToJar( jarStream, entryFile, entryName ); + + // See if there are any inner classes for this class and add them in if there are + InnerClassFilenameFilter flt = new InnerClassFilenameFilter( entryFile.getName() ); + File entryDir = entryFile.getParentFile(); + String[] innerfiles = entryDir.list( flt ); + for( int i = 0, n = innerfiles.length; i < n; i++ ) + { + + //get and clean up innerclass name + int entryIndex = entryName.lastIndexOf( entryFile.getName() ) - 1; + if( entryIndex < 0 ) + { + entryName = innerfiles[i]; + } + else + { + entryName = entryName.substring( 0, entryIndex ) + File.separatorChar + innerfiles[i]; + } + // link the file + entryFile = new File( config.srcDir, entryName ); + + log( "adding innerclass file '" + entryName + "'", + Project.MSG_VERBOSE ); + + addFileToJar( jarStream, entryFile, entryName ); + + } + } + } + catch( IOException ioe ) + { + String msg = "IOException while processing ejb-jar file '" + + jarfile.toString() + + "'. Details: " + + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + finally + { + if( jarStream != null ) + { + try + { + jarStream.close(); + } + catch( IOException closeException ) + {} + } + } + } + + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( destDir, baseName + genericJarSuffix ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java new file mode 100644 index 000000000..0dfdd0041 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetDeploymentTool.java @@ -0,0 +1,408 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.IOException; +import java.util.Hashtable; +import javax.xml.parsers.SAXParser; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.xml.sax.SAXException; + +/** + * This class is used to generate iPlanet Application Server (iAS) 6.0 stubs and + * skeletons and build an EJB Jar file. It is designed to be used with the Ant + * ejbjar task. If only stubs and skeletons need to be generated + * (in other words, if no JAR file needs to be created), refer to the iplanet-ejbc + * task and the IPlanetEjbcTask class.

              + * + * The following attributes may be specified by the user: + *

                + *
              • destdir -- The base directory into which the generated JAR + * files will be written. Each JAR file is written in directories which + * correspond to their location within the "descriptordir" namespace. This is + * a required attribute. + *
              • classpath -- The classpath used when generating EJB stubs and + * skeletons. This is an optional attribute (if omitted, the classpath + * specified in the "ejbjar" parent task will be used). If specified, the + * classpath elements will be prepended to the classpath specified in the + * parent "ejbjar" task. Note that nested "classpath" elements may also be + * used. + *
              • keepgenerated -- Indicates whether or not the Java source files + * which are generated by ejbc will be saved or automatically deleted. If + * "yes", the source files will be retained. This is an optional attribute (if + * omitted, it defaults to "no"). + *
              • debug -- Indicates whether or not the ejbc utility should log + * additional debugging statements to the standard output. If "yes", the + * additional debugging statements will be generated (if omitted, it defaults + * to "no"). + *
              • iashome -- May be used to specify the "home" directory for this + * iPlanet Application server installation. This is used to find the ejbc + * utility if it isn't included in the user's system path. This is an optional + * attribute (if specified, it should refer to the [install-location]/iplanet/ias6/ias + * directory). If omitted, the ejbc utility + * must be on the user's system path. + *
              • suffix -- String value appended to the JAR filename when + * creating each JAR. This attribute is not required (if omitted, it defaults + * to ".jar"). + *
              + *

              + * + * For each EJB descriptor found in the "ejbjar" parent task, this deployment + * tool will locate the three classes that comprise the EJB. If these class + * files cannot be located in the specified srcdir directory, the + * task will fail. The task will also attempt to locate the EJB stubs and + * skeletons in this directory. If found, the timestamps on the stubs and + * skeletons will be checked to ensure they are up to date. Only if these files + * cannot be found or if they are out of date will ejbc be called. + * + * @author Greg Nelson greg@netscape.com + * @see IPlanetEjbc + */ +public class IPlanetDeploymentTool extends GenericDeploymentTool +{ + + /* + * Regardless of the name of the iAS-specific EJB descriptor file, it will + * written in the completed JAR file as "ias-ejb-jar.xml". This is the + * naming convention implemented by iAS. + */ + private final static String IAS_DD = "ias-ejb-jar.xml"; + private String jarSuffix = ".jar"; + private boolean keepgenerated = false; + private boolean debug = false; + + /* + * Filenames of the standard EJB descriptor (which is passed to this class + * from the parent "ejbjar" task) and the iAS-specific EJB descriptor + * (whose name is determined by this class). Both filenames are relative + * to the directory specified by the "srcdir" attribute in the ejbjar task. + */ + private String descriptorName; + + /* + * The displayName variable stores the value of the "display-name" element + * from the standard EJB descriptor. As a future enhancement to this task, + * we may determine the name of the EJB JAR file using this display-name, + * but this has not be implemented yet. + */ + private String displayName; + private String iasDescriptorName; + + /* + * Attributes set by the Ant build file + */ + private File iashome; + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debug A boolean indicating if debugging output should be generated + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Since iAS doesn't generate a "generic" JAR as part of its processing, + * this attribute is ignored and a warning message is displayed to the user. + * + * @param inString the string to use as the suffix. This parameter is + * ignored. + */ + public void setGenericJarSuffix( String inString ) + { + log( "Since a generic JAR file is not created during processing, the " + + "iPlanet Deployment Tool does not support the " + + "\"genericjarsuffix\" attribute. It will be ignored.", + Project.MSG_WARN ); + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be [install-location]/iplanet/ias6/ias + * . + * + * @param iashome The home directory for the user's iAS installation. + */ + public void setIashome( File iashome ) + { + this.iashome = iashome; + } + + /** + * Setter method used to specify whether the Java source files generated by + * the ejbc utility should be saved or automatically deleted. + * + * @param keepgenerated boolean which, if true, indicates that + * Java source files generated by ejbc for the stubs and skeletons + * should be kept. + */ + public void setKeepgenerated( boolean keepgenerated ) + { + this.keepgenerated = keepgenerated; + } + + /** + * Setter method used to specify the filename suffix (for example, ".jar") + * for the JAR files to be created. + * + * @param jarSuffix The string to use as the JAR filename suffix. + */ + public void setSuffix( String jarSuffix ) + { + this.jarSuffix = jarSuffix; + } + + public void processDescriptor( String descriptorName, SAXParser saxParser ) + { + this.descriptorName = descriptorName; + + log( "iPlanet Deployment Tool processing: " + descriptorName + " (and " + + getIasDescriptorName() + ")", Project.MSG_VERBOSE ); + + super.processDescriptor( descriptorName, saxParser ); + } + + /** + * The iAS ejbc utility doesn't require the Public ID of the descriptor's + * DTD for it to process correctly--this method always returns null + * . + * + * @return null. + */ + protected String getPublicId() + { + return null; + } + + /** + * Add the iAS-specific EJB descriptor to the list of files which will be + * written to the JAR file. + * + * @param ejbFiles Hashtable of EJB class (and other) files to be added to + * the completed JAR file. + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + ejbFiles.put( META_DIR + IAS_DD, new File( getConfig().descriptorDir, + getIasDescriptorName() ) ); + } + + /** + * Verifies that the user selections are valid. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @throws BuildException If the user selections are invalid. + */ + protected void checkConfiguration( String descriptorFileName, + SAXParser saxParser ) + throws BuildException + { + + int startOfName = descriptorFileName.lastIndexOf( File.separatorChar ) + 1; + String stdXml = descriptorFileName.substring( startOfName ); + if( stdXml.equals( EJB_DD ) && ( getConfig().baseJarName == null ) ) + { + String msg = "No name specified for the completed JAR file. The EJB" + + " descriptor should be prepended with the JAR " + + "name or it should be specified using the " + + "attribute \"basejarname\" in the \"ejbjar\" task."; + throw new BuildException( msg, getLocation() ); + } + + File iasDescriptor = new File( getConfig().descriptorDir, + getIasDescriptorName() ); + if( ( !iasDescriptor.exists() ) || ( !iasDescriptor.isFile() ) ) + { + String msg = "The iAS-specific EJB descriptor (" + + iasDescriptor + ") was not found."; + throw new BuildException( msg, getLocation() ); + } + + if( ( iashome != null ) && ( !iashome.isDirectory() ) ) + { + String msg = "If \"iashome\" is specified, it must be a valid " + + "directory (it was set to " + iashome + ")."; + throw new BuildException( msg, getLocation() ); + } + } + + /** + * This method returns a list of EJB files found when the specified EJB + * descriptor is parsed and processed. + * + * @param descriptorFileName String representing the file name of an EJB + * descriptor to be processed + * @param saxParser SAXParser which may be used to parse the XML descriptor + * @return Hashtable of EJB class (and other) files to be added to the + * completed JAR file + * @throws IOException An IOException from the parser, possibly from the + * byte stream or character stream + * @throws SAXException Any SAX exception, possibly wrapping another + * exception + */ + protected Hashtable parseEjbFiles( String descriptorFileName, + SAXParser saxParser ) + throws IOException, SAXException + { + + Hashtable files; + + /* + * Build and populate an instance of the ejbc utility + */ + IPlanetEjbc ejbc = new IPlanetEjbc( + new File( getConfig().descriptorDir, + descriptorFileName ), + new File( getConfig().descriptorDir, + getIasDescriptorName() ), + getConfig().srcDir, + getCombinedClasspath().toString(), + saxParser ); + ejbc.setRetainSource( keepgenerated ); + ejbc.setDebugOutput( debug ); + if( iashome != null ) + { + ejbc.setIasHomeDir( iashome ); + } + + /* + * Execute the ejbc utility -- stubs/skeletons are rebuilt, if needed + */ + try + { + ejbc.execute(); + } + catch( IPlanetEjbc.EjbcException e ) + { + throw new BuildException( "An error has occurred while trying to " + + "execute the iAS ejbc utility", e, getLocation() ); + } + + displayName = ejbc.getDisplayName(); + files = ejbc.getEjbFiles(); + + /* + * Add CMP descriptors to the list of EJB files + */ + String[] cmpDescriptors = ejbc.getCmpDescriptors(); + if( cmpDescriptors.length > 0 ) + { + File baseDir = getConfig().descriptorDir; + + int endOfPath = descriptorFileName.lastIndexOf( File.separator ); + String relativePath = descriptorFileName.substring( 0, endOfPath + 1 ); + + for( int i = 0; i < cmpDescriptors.length; i++ ) + { + int endOfCmp = cmpDescriptors[i].lastIndexOf( '/' ); + String cmpDescriptor = cmpDescriptors[i].substring( endOfCmp + 1 ); + + File cmpFile = new File( baseDir, relativePath + cmpDescriptor ); + if( !cmpFile.exists() ) + { + throw new BuildException( "The CMP descriptor file (" + + cmpFile + ") could not be found.", getLocation() ); + } + files.put( cmpDescriptors[i], cmpFile ); + } + } + + return files; + } + + /** + * Get the name of the Jar that will be written. The modification date of + * this jar will be checked against the dependent bean classes. + * + * @param baseName String name of the EJB JAR file to be written (without a + * filename extension). + * @return File representing the JAR file which will be written. + */ + File getVendorOutputJarFile( String baseName ) + { + File jarFile = new File( getDestDir(), baseName + jarSuffix ); + log( "JAR file name: " + jarFile.toString(), Project.MSG_VERBOSE ); + return jarFile; + } + + /** + * Determines the name of the iAS-specific EJB descriptor using the + * specified standard EJB descriptor name. In general, the standard + * descriptor will be named "[basename]-ejb-jar.xml", and this method will + * return "[basename]-ias-ejb-jar.xml". + * + * @return The name of the iAS-specific EJB descriptor file. + */ + private String getIasDescriptorName() + { + + /* + * Only calculate the descriptor name once + */ + if( iasDescriptorName != null ) + { + return iasDescriptorName; + } + + String path = "";// Directory path of the EJB descriptor + String basename;// Filename appearing before name terminator + String remainder;// Filename appearing after the name terminator + + /* + * Find the end of the standard descriptor's relative path + */ + int startOfFileName = descriptorName.lastIndexOf( File.separatorChar ); + if( startOfFileName != -1 ) + { + path = descriptorName.substring( 0, startOfFileName + 1 ); + } + + /* + * Check to see if the standard name is used (there's no basename) + */ + if( descriptorName.substring( startOfFileName + 1 ).equals( EJB_DD ) ) + { + basename = ""; + remainder = EJB_DD; + + } + else + { + int endOfBaseName = descriptorName.indexOf( + getConfig().baseNameTerminator, + startOfFileName ); + /* + * Check for the odd case where the terminator and/or filename + * extension aren't found. These will ensure "ias-" appears at the + * end of the name and before the '.' (if present). + */ + if( endOfBaseName < 0 ) + { + endOfBaseName = descriptorName.lastIndexOf( '.' ) - 1; + if( endOfBaseName < 0 ) + { + endOfBaseName = descriptorName.length() - 1; + } + } + + basename = descriptorName.substring( startOfFileName + 1, + endOfBaseName + 1 ); + remainder = descriptorName.substring( endOfBaseName + 1 ); + } + + iasDescriptorName = path + basename + "ias-" + remainder; + return iasDescriptorName; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java new file mode 100644 index 000000000..8c3b18d60 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbc.java @@ -0,0 +1,1690 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.StringTokenizer; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.AttributeList; +import org.xml.sax.HandlerBase; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Utility class to compile EJB stubs and skeletons for the iPlanet Application + * Server (iAS). The class will read a standard EJB descriptor (as well as an + * EJB descriptor specific to iPlanet Application Server) to identify one or + * more EJBs to process. It will search for EJB "source" classes (the remote + * interface, home interface, and EJB implementation class) and the EJB stubs + * and skeletons in the specified destination directory. Only if the stubs and + * skeletons cannot be found or if they're out of date will the iPlanet + * Application Server ejbc utility be run.

              + * + * Because this class (and it's assorted inner classes) may be bundled into the + * iPlanet Application Server distribution at some point (and removed from the + * Ant distribution), the class has been written to be independent of all + * Ant-specific classes. It is also for this reason (and to avoid cluttering the + * Apache Ant source files) that this utility has been packaged into a single + * source file.

              + * + * For more information on Ant Tasks for iPlanet Application Server, see the + * IPlanetDeploymentTool and IPlanetEjbcTask classes. + * + * @author Greg Nelson greg@netscape.com + * @see IPlanetDeploymentTool + * @see IPlanetEjbcTask + */ +public class IPlanetEjbc +{ + + /* + * Constants used for the "beantype" attribute + */ + private final static String ENTITY_BEAN = "entity"; + private final static String STATELESS_SESSION = "stateless"; + private final static String STATEFUL_SESSION = "stateful"; + + /* + * Options passed to the iAS ejbc + */ + private boolean retainSource = false; + private boolean debugOutput = false; + private EjbcHandler handler = new EjbcHandler(); + + /* + * This Hashtable maintains a list of EJB class files processed by the ejbc + * utility (both "source" class files as well as stubs and skeletons). The + * key for the Hashtable is a String representing the path to the class file + * (relative to the destination directory). The value for the Hashtable is + * a File object which reference the actual class file. + */ + private Hashtable ejbFiles = new Hashtable(); + + /* + * Classpath used when the iAS ejbc is called + */ + private String classpath; + private String[] classpathElements; + + /* + * Directory where "source" EJB files are stored and where stubs and + * skeletons will also be written. + */ + private File destDirectory; + + /* + * Value of the display-name element read from the standard EJB descriptor + */ + private String displayName; + private File iasDescriptor; + + /* + * iAS installation directory (used if ejbc isn't on user's PATH) + */ + private File iasHomeDir; + + /* + * Parser and handler used to process both EJB descriptor files + */ + private SAXParser parser; + + /* + * Filenames of the standard EJB descriptor and the iAS-specific descriptor + */ + private File stdDescriptor; + + /** + * Constructs an instance which may be used to process EJB descriptors and + * generate EJB stubs and skeletons, if needed. + * + * @param stdDescriptor File referencing a standard EJB descriptor. + * @param iasDescriptor File referencing an iAS-specific EJB descriptor. + * @param destDirectory File referencing the base directory where both EJB + * "source" files are found and where stubs and skeletons will be + * written. + * @param classpath String representation of the classpath to be used by the + * iAS ejbc utility. + * @param parser SAXParser to be used to process both of the EJB + * descriptors. + */ + public IPlanetEjbc( File stdDescriptor, + File iasDescriptor, + File destDirectory, + String classpath, + SAXParser parser ) + { + this.stdDescriptor = stdDescriptor; + this.iasDescriptor = iasDescriptor; + this.destDirectory = destDirectory; + this.classpath = classpath; + this.parser = parser; + + /* + * Parse the classpath into it's individual elements and store the + * results in the "classpathElements" instance variable. + */ + List elements = new ArrayList(); + if( classpath != null ) + { + StringTokenizer st = new StringTokenizer( classpath, + File.pathSeparator ); + while( st.hasMoreTokens() ) + { + elements.add( st.nextToken() ); + } + classpathElements + = ( String[] )elements.toArray( new String[elements.size()] ); + } + } + + /** + * Main application method for the iPlanet Application Server ejbc utility. + * If the application is run with no commandline arguments, a usage + * statement is printed for the user. + * + * @param args The commandline arguments passed to the application. + */ + public static void main( String[] args ) + { + File stdDescriptor; + File iasDescriptor; + File destDirectory = null; + String classpath = null; + SAXParser parser = null; + boolean debug = false; + boolean retainSource = false; + IPlanetEjbc ejbc; + + if( ( args.length < 2 ) || ( args.length > 8 ) ) + { + usage(); + return; + } + + stdDescriptor = new File( args[args.length - 2] ); + iasDescriptor = new File( args[args.length - 1] ); + + for( int i = 0; i < args.length - 2; i++ ) + { + if( args[i].equals( "-classpath" ) ) + { + classpath = args[++i]; + } + else if( args[i].equals( "-d" ) ) + { + destDirectory = new File( args[++i] ); + } + else if( args[i].equals( "-debug" ) ) + { + debug = true; + } + else if( args[i].equals( "-keepsource" ) ) + { + retainSource = true; + } + else + { + usage(); + return; + } + } + + /* + * If the -classpath flag isn't specified, use the system classpath + */ + if( classpath == null ) + { + Properties props = System.getProperties(); + classpath = props.getProperty( "java.class.path" ); + } + + /* + * If the -d flag isn't specified, use the working directory as the + * destination directory + */ + if( destDirectory == null ) + { + Properties props = System.getProperties(); + destDirectory = new File( props.getProperty( "user.dir" ) ); + } + + /* + * Construct a SAXParser used to process the descriptors + */ + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + parserFactory.setValidating( true ); + try + { + parser = parserFactory.newSAXParser(); + } + catch( Exception e ) + { + // SAXException or ParserConfigurationException may be thrown + System.out.println( "An exception was generated while trying to " ); + System.out.println( "create a new SAXParser." ); + e.printStackTrace(); + return; + } + + /* + * Build and populate an instance of the ejbc utility + */ + ejbc = new IPlanetEjbc( stdDescriptor, iasDescriptor, destDirectory, + classpath, parser ); + ejbc.setDebugOutput( debug ); + ejbc.setRetainSource( retainSource ); + + /* + * Execute the ejbc utility -- stubs/skeletons are rebuilt, if needed + */ + try + { + ejbc.execute(); + } + catch( IOException e ) + { + System.out.println( "An IOException has occurred while reading the " + + "XML descriptors (" + e.getMessage() + ")." ); + return; + } + catch( SAXException e ) + { + System.out.println( "A SAXException has occurred while reading the " + + "XML descriptors (" + e.getMessage() + ")." ); + return; + } + catch( IPlanetEjbc.EjbcException e ) + { + System.out.println( "An error has occurred while executing the ejbc " + + "utility (" + e.getMessage() + ")." ); + return; + } + } + + /** + * Print a usage statement. + */ + private static void usage() + { + System.out.println( "java org.apache.tools.ant.taskdefs.optional.ejb.IPlanetEjbc \\" ); + System.out.println( " [OPTIONS] [EJB 1.1 descriptor] [iAS EJB descriptor]" ); + System.out.println( "" ); + System.out.println( "Where OPTIONS are:" ); + System.out.println( " -debug -- for additional debugging output" ); + System.out.println( " -keepsource -- to retain Java source files generated" ); + System.out.println( " -classpath [classpath] -- classpath used for compilation" ); + System.out.println( " -d [destination directory] -- directory for compiled classes" ); + System.out.println( "" ); + System.out.println( "If a classpath is not specified, the system classpath" ); + System.out.println( "will be used. If a destination directory is not specified," ); + System.out.println( "the current working directory will be used (classes will" ); + System.out.println( "still be placed in subfolders which correspond to their" ); + System.out.println( "package name)." ); + System.out.println( "" ); + System.out.println( "The EJB home interface, remote interface, and implementation" ); + System.out.println( "class must be found in the destination directory. In" ); + System.out.println( "addition, the destination will look for the stubs and skeletons" ); + System.out.println( "in the destination directory to ensure they are up to date." ); + } + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debugOutput A boolean indicating if debugging output should be + * generated + */ + public void setDebugOutput( boolean debugOutput ) + { + this.debugOutput = debugOutput; + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be [install-location]/iplanet/ias6/ias + * . + * + * @param iasHomeDir The new IasHomeDir value + */ + public void setIasHomeDir( File iasHomeDir ) + { + this.iasHomeDir = iasHomeDir; + } + + /** + * Sets whether or not the Java source files which are generated by the ejbc + * process should be retained or automatically deleted. + * + * @param retainSource The new RetainSource value + */ + public void setRetainSource( boolean retainSource ) + { + this.retainSource = retainSource; + } + + /** + * Returns the list of CMP descriptors referenced in the EJB descriptors. + * + * @return An array of CMP descriptors. + */ + public String[] getCmpDescriptors() + { + List returnList = new ArrayList(); + + EjbInfo[] ejbs = handler.getEjbs(); + + for( int i = 0; i < ejbs.length; i++ ) + { + List descriptors = ( List )ejbs[i].getCmpDescriptors(); + returnList.addAll( descriptors ); + } + + return ( String[] )returnList.toArray( new String[returnList.size()] ); + } + + /** + * Returns the display-name element read from the standard EJB descriptor. + * + * @return The EJB-JAR display name. + */ + public String getDisplayName() + { + return displayName; + } + + /** + * Returns a Hashtable which contains a list of EJB class files processed by + * the ejbc utility (both "source" class files as well as stubs and + * skeletons). The key for the Hashtable is a String representing the path + * to the class file (relative to the destination directory). The value for + * the Hashtable is a File object which reference the actual class file. + * + * @return The list of EJB files processed by the ejbc utility. + */ + public Hashtable getEjbFiles() + { + return ejbFiles; + } + + /** + * Compiles the stub and skeletons for the specified EJBs, if they need to + * be updated. + * + * @throws EjbcException If the ejbc utility cannot be correctly configured + * or if one or more of the EJB "source" classes cannot be found in the + * destination directory + * @throws IOException If the parser encounters a problem reading the XML + * file + * @throws SAXException If the parser encounters a problem processing the + * XML descriptor (it may wrap another exception) + */ + public void execute() + throws EjbcException, IOException, SAXException + { + + checkConfiguration();// Throws EjbcException if unsuccessful + + EjbInfo[] ejbs = getEjbs();// Returns list of EJBs for processing + + for( int i = 0; i < ejbs.length; i++ ) + { + log( "EJBInfo..." ); + log( ejbs[i].toString() ); + } + + for( int i = 0; i < ejbs.length; i++ ) + { + EjbInfo ejb = ejbs[i]; + + ejb.checkConfiguration( destDirectory );// Throws EjbcException + + if( ejb.mustBeRecompiled( destDirectory ) ) + { + log( ejb.getName() + " must be recompiled using ejbc." ); + + String[] arguments = buildArgumentList( ejb ); + callEjbc( arguments ); + + } + else + { + log( ejb.getName() + " is up to date." ); + } + } + } + + /** + * Registers the location of a local DTD file or resource. By registering a + * local DTD, EJB descriptors can be parsed even when the remote servers + * which contain the "public" DTDs cannot be accessed. + * + * @param publicID The public DTD identifier found in an XML document. + * @param location The file or resource name for the appropriate DTD stored + * on the local machine. + */ + public void registerDTD( String publicID, String location ) + { + handler.registerDTD( publicID, location ); + } + + /** + * Verifies that the user selections are valid. + * + * @throws EjbcException If the user selections are invalid. + */ + protected void checkConfiguration() + throws EjbcException + { + + String msg = ""; + + if( stdDescriptor == null ) + { + msg += "A standard XML descriptor file must be specified. "; + } + if( iasDescriptor == null ) + { + msg += "An iAS-specific XML descriptor file must be specified. "; + } + if( classpath == null ) + { + msg += "A classpath must be specified. "; + } + if( parser == null ) + { + msg += "An XML parser must be specified. "; + } + + if( destDirectory == null ) + { + msg += "A destination directory must be specified. "; + } + else if( !destDirectory.exists() ) + { + msg += "The destination directory specified does not exist. "; + } + else if( !destDirectory.isDirectory() ) + { + msg += "The destination specified is not a directory. "; + } + + if( msg.length() > 0 ) + { + throw new EjbcException( msg ); + } + } + + /** + * Parses the EJB descriptors and returns a list of EJBs which may need to + * be compiled. + * + * @return An array of objects which describe the EJBs to be processed. + * @throws IOException If the parser encounters a problem reading the XML + * files + * @throws SAXException If the parser encounters a problem processing the + * XML descriptor (it may wrap another exception) + */ + private EjbInfo[] getEjbs() + throws IOException, SAXException + { + EjbInfo[] ejbs = null; + + /* + * The EJB information is gathered from the standard XML EJB descriptor + * and the iAS-specific XML EJB descriptor using a SAX parser. + */ + parser.parse( stdDescriptor, handler ); + parser.parse( iasDescriptor, handler ); + ejbs = handler.getEjbs(); + + return ejbs; + } + + /** + * Based on this object's instance variables as well as the EJB to be + * processed, the correct flags and parameters are set for the ejbc + * command-line utility. + * + * @param ejb The EJB for which stubs and skeletons will be compiled. + * @return An array of Strings which are the command-line parameters for for + * the ejbc utility. + */ + private String[] buildArgumentList( EjbInfo ejb ) + { + + List arguments = new ArrayList(); + + /* + * OPTIONAL COMMAND LINE PARAMETERS + */ + if( debugOutput ) + { + arguments.add( "-debug" ); + } + + /* + * No beantype flag is needed for an entity bean + */ + if( ejb.getBeantype().equals( STATELESS_SESSION ) ) + { + arguments.add( "-sl" ); + } + else if( ejb.getBeantype().equals( STATEFUL_SESSION ) ) + { + arguments.add( "-sf" ); + } + + if( ejb.getIiop() ) + { + arguments.add( "-iiop" ); + } + + if( ejb.getCmp() ) + { + arguments.add( "-cmp" ); + } + + if( retainSource ) + { + arguments.add( "-gs" ); + } + + if( ejb.getHasession() ) + { + arguments.add( "-fo" ); + } + + /* + * REQUIRED COMMAND LINE PARAMETERS + */ + arguments.add( "-classpath" ); + arguments.add( classpath ); + + arguments.add( "-d" ); + arguments.add( destDirectory.toString() ); + + arguments.add( ejb.getHome().getQualifiedClassName() ); + arguments.add( ejb.getRemote().getQualifiedClassName() ); + arguments.add( ejb.getImplementation().getQualifiedClassName() ); + + /* + * Convert the List into an Array and return it + */ + return ( String[] )arguments.toArray( new String[arguments.size()] ); + } + + /** + * Executes the iPlanet Application Server ejbc command-line utility. + * + * @param arguments Command line arguments to be passed to the ejbc utility. + */ + private void callEjbc( String[] arguments ) + { + + /* + * Concatenate all of the command line arguments into a single String + */ + StringBuffer args = new StringBuffer(); + for( int i = 0; i < arguments.length; i++ ) + { + args.append( arguments[i] ).append( " " ); + } + + /* + * If an iAS home directory is specified, prepend it to the commmand + */ + String command; + if( iasHomeDir == null ) + { + command = ""; + } + else + { + command = iasHomeDir.toString() + File.separator + "bin" + + File.separator; + } + command += "ejbc "; + + log( command + args ); + + /* + * Use the Runtime object to execute an external command. Use the + * RedirectOutput inner class to direct the standard and error output + * from the command to the JRE's standard output + */ + try + { + Process p = Runtime.getRuntime().exec( command + args ); + RedirectOutput output = new RedirectOutput( p.getInputStream() ); + RedirectOutput error = new RedirectOutput( p.getErrorStream() ); + output.start(); + error.start(); + p.waitFor(); + p.destroy(); + } + catch( IOException e ) + { + log( "An IOException has occurred while trying to execute ejbc." ); + e.printStackTrace(); + } + catch( InterruptedException e ) + { + // Do nothing + } + } + + /** + * Convenience method used to print messages to the user if debugging + * messages are enabled. + * + * @param msg The String to print to standard output. + */ + private void log( String msg ) + { + if( debugOutput ) + { + System.out.println( msg ); + } + } + + + /* + * Inner classes follow + */ + + /** + * This inner class is used to signal any problems during the execution of + * the ejbc compiler. + * + * @author Greg Nelson greg@netscape.com + * + */ + public class EjbcException extends Exception + { + + /** + * Constructs an exception with the given descriptive message. + * + * @param msg Description of the exception which has occurred. + */ + public EjbcException( String msg ) + { + super( msg ); + } + }// End of EjbInfo inner class + + /** + * Convenience class used to represent the fully qualified name of a Java + * class. It provides an easy way to retrieve components of the class name + * in a format that is convenient for building iAS stubs and skeletons. + * + * @author Greg Nelson greg@netscape.com + * + */ + private class Classname + {// Name of the package for this class + private String className;// Fully qualified name of the Java class + private String packageName; + private String qualifiedName;// Name of the class without the package + + /** + * This constructor builds an object which represents the name of a Java + * class. + * + * @param qualifiedName String representing the fully qualified class + * name of the Java class. + */ + public Classname( String qualifiedName ) + { + if( qualifiedName == null ) + { + return; + } + + this.qualifiedName = qualifiedName; + + int index = qualifiedName.lastIndexOf( '.' ); + if( index == -1 ) + { + className = qualifiedName; + packageName = ""; + } + else + { + packageName = qualifiedName.substring( 0, index ); + className = qualifiedName.substring( index + 1 ); + } + } + + /** + * Returns a File which references the class relative to the specified + * directory. Note that the class file may or may not exist. + * + * @param directory A File referencing the base directory containing + * class files. + * @return File referencing this class. + */ + public File getClassFile( File directory ) + { + String pathToFile = qualifiedName.replace( '.', File.separatorChar ) + + ".class"; + return new File( directory, pathToFile ); + } + + /** + * Gets the Java class name without the package structure. + * + * @return String representing the name for the class. + */ + public String getClassName() + { + return className; + } + + /** + * Gets the package name for the Java class. + * + * @return String representing the package name for the class. + */ + public String getPackageName() + { + return packageName; + } + + /** + * Gets the fully qualified name of the Java class. + * + * @return String representing the fully qualified class name. + */ + public String getQualifiedClassName() + { + return qualifiedName; + } + + /** + * Gets the fully qualified name of the Java class with underscores + * separating the components of the class name rather than periods. This + * format is used in naming some of the stub and skeleton classes for + * the iPlanet Application Server. + * + * @return String representing the fully qualified class name using + * underscores instead of periods. + */ + public String getQualifiedWithUnderscores() + { + return qualifiedName.replace( '.', '_' ); + } + + /** + * String representation of this class name. It returns the fully + * qualified class name. + * + * @return String representing the fully qualified class name. + */ + public String toString() + { + return getQualifiedClassName(); + } + }// End of EjbcHandler inner class + + + /** + * This inner class represents an EJB that will be compiled using ejbc. + * + * @author Greg Nelson greg@netscape.com + * + */ + private class EjbInfo + {// EJB's implementation class + private String beantype = "entity";// or "stateful" or "stateless" + private boolean cmp = false;// Does this EJB support CMP? + private boolean iiop = false;// Does this EJB support IIOP? + private boolean hasession = false;// Does this EJB require failover? + private List cmpDescriptors = new ArrayList();// EJB's display name + private Classname home;// EJB's remote interface name + private Classname implementation; + private String name;// EJB's home interface name + private Classname remote;// CMP descriptor list + + /** + * Construct a new EJBInfo object with the given name. + * + * @param name The display name for the EJB. + */ + public EjbInfo( String name ) + { + this.name = name; + } + + public void setBeantype( String beantype ) + { + this.beantype = beantype.toLowerCase(); + } + + public void setCmp( boolean cmp ) + { + this.cmp = cmp; + } + + public void setCmp( String cmp ) + { + setCmp( cmp.equals( "Container" ) ); + } + + public void setHasession( boolean hasession ) + { + this.hasession = hasession; + } + + public void setHasession( String hasession ) + { + setHasession( hasession.equals( "true" ) ); + } + + /* + * Below are getter's and setter's for each of the instance variables. + * Note that (in addition to supporting setters with the same type as + * the instance variable) a setter is provided with takes a String + * argument -- this are provided so the XML document handler can set + * the EJB values using the Strings it parses. + */ + public void setHome( String home ) + { + setHome( new Classname( home ) ); + } + + public void setHome( Classname home ) + { + this.home = home; + } + + public void setIiop( boolean iiop ) + { + this.iiop = iiop; + } + + public void setIiop( String iiop ) + { + setIiop( iiop.equals( "true" ) ); + } + + public void setImplementation( String implementation ) + { + setImplementation( new Classname( implementation ) ); + } + + public void setImplementation( Classname implementation ) + { + this.implementation = implementation; + } + + public void setRemote( String remote ) + { + setRemote( new Classname( remote ) ); + } + + public void setRemote( Classname remote ) + { + this.remote = remote; + } + + public String getBeantype() + { + return beantype; + } + + public boolean getCmp() + { + return cmp; + } + + public List getCmpDescriptors() + { + return cmpDescriptors; + } + + public boolean getHasession() + { + return hasession; + } + + public Classname getHome() + { + return home; + } + + public boolean getIiop() + { + return iiop; + } + + public Classname getImplementation() + { + return implementation; + } + + /** + * Returns the display name of the EJB. If a display name has not been + * set, it returns the EJB implementation classname (if the + * implementation class is not set, it returns "[unnamed]"). + * + * @return The display name for the EJB. + */ + public String getName() + { + if( name == null ) + { + if( implementation == null ) + { + return "[unnamed]"; + } + else + { + return implementation.getClassName(); + } + } + return name; + } + + public Classname getRemote() + { + return remote; + } + + public void addCmpDescriptor( String descriptor ) + { + cmpDescriptors.add( descriptor ); + } + + /** + * Determines if the ejbc utility needs to be run or not. If the stubs + * and skeletons can all be found in the destination directory AND all + * of their timestamps are more recent than the EJB source classes + * (home, remote, and implementation classes), the method returns false + * . Otherwise, the method returns true. + * + * @param destDir The directory where the EJB source classes, stubs and + * skeletons are located. + * @return A boolean indicating whether or not the ejbc utility needs to + * be run to bring the stubs and skeletons up to date. + */ + public boolean mustBeRecompiled( File destDir ) + { + + long sourceModified = sourceClassesModified( destDir ); + + long destModified = destClassesModified( destDir ); + + return ( destModified < sourceModified ); + } + + /** + * Convenience method which creates a String representation of all the + * instance variables of an EjbInfo object. + * + * @return A String representing the EjbInfo instance. + */ + public String toString() + { + String s = "EJB name: " + name + + "\n\r home: " + home + + "\n\r remote: " + remote + + "\n\r impl: " + implementation + + "\n\r beantype: " + beantype + + "\n\r cmp: " + cmp + + "\n\r iiop: " + iiop + + "\n\r hasession: " + hasession; + + Iterator i = cmpDescriptors.iterator(); + while( i.hasNext() ) + { + s += "\n\r CMP Descriptor: " + i.next(); + } + + return s; + } + + /** + * Verifies that the EJB is valid--if it is invalid, an exception is + * thrown + * + * @param buildDir The directory where the EJB remote interface, home + * interface, and implementation class must be found. + * @throws EjbcException If the EJB is invalid. + */ + private void checkConfiguration( File buildDir ) + throws EjbcException + { + + /* + * Check that the specified instance variables are valid + */ + if( home == null ) + { + throw new EjbcException( "A home interface was not found " + + "for the " + name + " EJB." ); + } + if( remote == null ) + { + throw new EjbcException( "A remote interface was not found " + + "for the " + name + " EJB." ); + } + if( implementation == null ) + { + throw new EjbcException( "An EJB implementation class was not " + + "found for the " + name + " EJB." ); + } + + if( ( !beantype.equals( ENTITY_BEAN ) ) + && ( !beantype.equals( STATELESS_SESSION ) ) + && ( !beantype.equals( STATEFUL_SESSION ) ) ) + { + throw new EjbcException( "The beantype found (" + beantype + ") " + + "isn't valid in the " + name + " EJB." ); + } + + if( cmp && ( !beantype.equals( ENTITY_BEAN ) ) ) + { + System.out.println( "CMP stubs and skeletons may not be generated" + + " for a Session Bean -- the \"cmp\" attribute will be" + + " ignoredfor the " + name + " EJB." ); + } + + if( hasession && ( !beantype.equals( STATEFUL_SESSION ) ) ) + { + System.out.println( "Highly available stubs and skeletons may " + + "only be generated for a Stateful Session Bean -- the " + + "\"hasession\" attribute will be ignored for the " + + name + " EJB." ); + } + + /* + * Check that the EJB "source" classes all exist + */ + if( !remote.getClassFile( buildDir ).exists() ) + { + throw new EjbcException( "The remote interface " + + remote.getQualifiedClassName() + " could not be " + + "found." ); + } + if( !home.getClassFile( buildDir ).exists() ) + { + throw new EjbcException( "The home interface " + + home.getQualifiedClassName() + " could not be " + + "found." ); + } + if( !implementation.getClassFile( buildDir ).exists() ) + { + throw new EjbcException( "The EJB implementation class " + + implementation.getQualifiedClassName() + " could " + + "not be found." ); + } + } + + /** + * Builds an array of class names which represent the stubs and + * skeletons which need to be generated for a given EJB. The class names + * are fully qualified. Nine classes are generated for all EJBs while an + * additional six classes are generated for beans requiring RMI/IIOP + * access. + * + * @return An array of Strings representing the fully-qualified class + * names for the stubs and skeletons to be generated. + */ + private String[] classesToGenerate() + { + String[] classnames = ( iiop ) ? new String[15] : new String[9]; + + final String remotePkg = remote.getPackageName() + "."; + final String remoteClass = remote.getClassName(); + final String homePkg = home.getPackageName() + "."; + final String homeClass = home.getClassName(); + final String implPkg = implementation.getPackageName() + "."; + final String implFullClass = implementation.getQualifiedWithUnderscores(); + int index = 0; + + String fullPath; + + classnames[index++] = implPkg + "ejb_fac_" + implFullClass; + classnames[index++] = implPkg + "ejb_home_" + implFullClass; + classnames[index++] = implPkg + "ejb_skel_" + implFullClass; + classnames[index++] = remotePkg + "ejb_kcp_skel_" + remoteClass; + classnames[index++] = homePkg + "ejb_kcp_skel_" + homeClass; + classnames[index++] = remotePkg + "ejb_kcp_stub_" + remoteClass; + classnames[index++] = homePkg + "ejb_kcp_stub_" + homeClass; + classnames[index++] = remotePkg + "ejb_stub_" + remoteClass; + classnames[index++] = homePkg + "ejb_stub_" + homeClass; + + if( !iiop ) + { + return classnames; + } + + classnames[index++] = remotePkg + "_" + remoteClass + "_Stub"; + classnames[index++] = homePkg + "_" + homeClass + "_Stub"; + classnames[index++] = remotePkg + "_ejb_RmiCorbaBridge_" + + remoteClass + "_Tie"; + classnames[index++] = homePkg + "_ejb_RmiCorbaBridge_" + homeClass + + "_Tie"; + classnames[index++] = remotePkg + "ejb_RmiCorbaBridge_" + + remoteClass; + classnames[index++] = homePkg + "ejb_RmiCorbaBridge_" + homeClass; + + return classnames; + } + + /** + * Examines each of the EJB stubs and skeletons in the destination + * directory and returns the modification timestamp for the "oldest" + * class. If one of the stubs or skeletons cannot be found, -1 + * is returned. + * + * @param destDir Description of Parameter + * @return The modification timestamp for the "oldest" EJB stub or + * skeleton. If one of the classes cannot be found, -1 + * is returned. + * @throws BuildException If the canonical path of the destination + * directory cannot be found. + */ + private long destClassesModified( File destDir ) + { + String[] classnames = classesToGenerate();// List of all stubs & skels + long destClassesModified = new Date().getTime();// Earliest mod time + boolean allClassesFound = true;// Has each been found? + + /* + * Loop through each stub/skeleton class that must be generated, and + * determine (if all exist) which file has the most recent timestamp + */ + for( int i = 0; i < classnames.length; i++ ) + { + + String pathToClass = + classnames[i].replace( '.', File.separatorChar ) + ".class"; + File classFile = new File( destDir, pathToClass ); + + /* + * Add each stub/skeleton class to the list of EJB files. Note + * that each class is added even if it doesn't exist now. + */ + ejbFiles.put( pathToClass, classFile ); + + allClassesFound = allClassesFound && classFile.exists(); + + if( allClassesFound ) + { + long fileMod = classFile.lastModified(); + + /* + * Keep track of the oldest modification timestamp + */ + destClassesModified = Math.min( destClassesModified, fileMod ); + } + } + + return ( allClassesFound ) ? destClassesModified : -1; + } + + /** + * Examines each of the EJB source classes (home, remote, and + * implementation) and returns the modification timestamp for the + * "oldest" class. + * + * @param buildDir Description of Parameter + * @return The modification timestamp for the "oldest" EJB source class. + * @throws BuildException If one of the EJB source classes cannot be + * found on the classpath. + */ + private long sourceClassesModified( File buildDir ) + { + long latestModified;// The timestamp of the "newest" class + long modified;// Timestamp for a given class + File remoteFile;// File for the remote interface class + File homeFile;// File for the home interface class + File implFile;// File for the EJB implementation class + + /* + * Check the timestamp on the remote interface + */ + remoteFile = remote.getClassFile( buildDir ); + modified = remoteFile.lastModified(); + if( modified == -1 ) + { + System.out.println( "The class " + + remote.getQualifiedClassName() + " couldn't " + + "be found on the classpath" ); + return -1; + } + latestModified = modified; + + /* + * Check the timestamp on the home interface + */ + homeFile = home.getClassFile( buildDir ); + modified = homeFile.lastModified(); + if( modified == -1 ) + { + System.out.println( "The class " + + home.getQualifiedClassName() + " couldn't be " + + "found on the classpath" ); + return -1; + } + latestModified = Math.max( latestModified, modified ); + + /* + * Check the timestamp on the EJB implementation class. + * + * Note that if ONLY the implementation class has changed, it's not + * necessary to rebuild the EJB stubs and skeletons. For this + * reason, we ensure the file exists (using lastModified above), but + * we DON'T compare it's timestamp with the timestamps of the home + * and remote interfaces (because it's irrelevant in determining if + * ejbc must be run) + */ + implFile = implementation.getClassFile( buildDir ); + modified = implFile.lastModified(); + if( modified == -1 ) + { + System.out.println( "The class " + + implementation.getQualifiedClassName() + + " couldn't be found on the classpath" ); + return -1; + } + + String pathToFile = remote.getQualifiedClassName(); + pathToFile = pathToFile.replace( '.', File.separatorChar ) + ".class"; + ejbFiles.put( pathToFile, remoteFile ); + + pathToFile = home.getQualifiedClassName(); + pathToFile = pathToFile.replace( '.', File.separatorChar ) + ".class"; + ejbFiles.put( pathToFile, homeFile ); + + pathToFile = implementation.getQualifiedClassName(); + pathToFile = pathToFile.replace( '.', File.separatorChar ) + ".class"; + ejbFiles.put( pathToFile, implFile ); + + return latestModified; + } + + }// End of EjbcException inner class + + + /** + * This inner class is an XML document handler that can be used to parse EJB + * descriptors (both the standard EJB descriptor as well as the iAS-specific + * descriptor that stores additional values for iAS). Once the descriptors + * have been processed, the list of EJBs found can be obtained by calling + * the getEjbs() method. + * + * @author Greg Nelson greg@netscape.com + * + * @see EjbInfo + */ + private class EjbcHandler extends HandlerBase + { + + /* + * Two Maps are used to track local DTDs that will be used in case the + * remote copies of these DTDs cannot be accessed. The key for the Map + * is the DTDs public ID and the value is the local location for the DTD + */ + private Map resourceDtds = new HashMap(); + private Map fileDtds = new HashMap(); + + private Map ejbs = new HashMap();// One item within the Map + private boolean iasDescriptor = false;// Is doc iAS or EJB descriptor + + private String currentLoc = "";// List of EJBs found in XML + private EjbInfo currentEjb;// Tracks current element + private String currentText;// Tracks current text data + private String ejbType;// "session" or "entity" + + /** + * Constructs a new instance of the handler and registers local copies + * of the standard EJB 1.1 descriptor DTD as well as iAS's EJB + * descriptor DTD. + */ + public EjbcHandler() + { + final String PUBLICID_EJB11 = + "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"; + final String PUBLICID_IPLANET_EJB_60 = + "-//Sun Microsystems, Inc.//DTD iAS Enterprise JavaBeans 1.0//EN"; + + final String DEFAULT_IAS60_EJB11_DTD_LOCATION = + "ejb-jar_1_1.dtd"; + final String DEFAULT_IAS60_DTD_LOCATION = + "IASEjb_jar_1_0.dtd"; + + registerDTD( PUBLICID_EJB11, DEFAULT_IAS60_EJB11_DTD_LOCATION ); + registerDTD( PUBLICID_IPLANET_EJB_60, DEFAULT_IAS60_DTD_LOCATION ); + } + + /** + * Returns the value of the display-name element found in the standard + * EJB 1.1 descriptor. + * + * @return String display-name value. + */ + public String getDisplayName() + { + return displayName; + } + + /** + * Returns the list of EJB objects found during the processing of the + * standard EJB 1.1 descriptor and iAS-specific EJB descriptor. + * + * @return An array of EJBs which were found during the descriptor + * parsing. + */ + public EjbInfo[] getEjbs() + { + return ( EjbInfo[] )ejbs.values().toArray( new EjbInfo[ejbs.size()] ); + } + + /** + * Receive notification that character data has been found in the XML + * document + * + * @param ch Array of characters which have been found in the document. + * @param start Starting index of the data found in the document. + * @param len The number of characters found in the document. + * @throws SAXException If the parser cannot process the document. + */ + public void characters( char[] ch, int start, int len ) + throws SAXException + { + + currentText += new String( ch ).substring( start, start + len ); + } + + /** + * Receive notification that the end of an XML element has been found. + * + * @param name String name of the element. + * @throws SAXException If the parser cannot process the document. + */ + public void endElement( String name ) + throws SAXException + { + + /* + * If this is a standard EJB 1.1 descriptor, we are looking for one + * set of data, while if this is an iAS-specific descriptor, we're + * looking for different set of data. Hand the processing off to + * the appropriate method. + */ + if( iasDescriptor ) + { + iasCharacters( currentText ); + } + else + { + stdCharacters( currentText ); + } + + /* + * I need to "pop" the element off the String (currentLoc) which + * always represents my current location in the XML document. + */ + int nameLength = name.length() + 1;// Add one for the "\" + int locLength = currentLoc.length(); + + currentLoc = currentLoc.substring( 0, locLength - nameLength ); + } + + /** + * Registers a local DTD that will be used when parsing an EJB + * descriptor. When the DTD's public identifier is found in an XML + * document, the parser will reference the local DTD rather than the + * remote DTD. This enables XML documents to be processed even when the + * public DTD isn't available. + * + * @param publicID The DTD's public identifier. + * @param location The location of the local DTD copy -- the location + * may either be a resource found on the classpath or a local file. + */ + public void registerDTD( String publicID, String location ) + { + log( "Registering: " + location ); + if( ( publicID == null ) || ( location == null ) ) + { + return; + } + + if( ClassLoader.getSystemResource( location ) != null ) + { + log( "Found resource: " + location ); + resourceDtds.put( publicID, location ); + } + else + { + File dtdFile = new File( location ); + if( dtdFile.exists() && dtdFile.isFile() ) + { + log( "Found file: " + location ); + fileDtds.put( publicID, location ); + } + } + } + + /** + * Resolves an external entity found during XML processing. If a public + * ID is found that has been registered with the handler, an + * InputSource will be returned which refers to the local copy. + * If the public ID hasn't been registered or if an error occurs, the + * superclass implementation is used. + * + * @param publicId The DTD's public identifier. + * @param systemId The location of the DTD, as found in the XML + * document. + * @return Description of the Returned Value + * @exception SAXException Description of Exception + */ + public InputSource resolveEntity( String publicId, String systemId ) + throws SAXException + { + InputStream inputStream = null; + + try + { + + /* + * Search the resource Map and (if not found) file Map + */ + String location = ( String )resourceDtds.get( publicId ); + if( location != null ) + { + inputStream + = ClassLoader.getSystemResource( location ).openStream(); + } + else + { + location = ( String )fileDtds.get( publicId ); + if( location != null ) + { + inputStream = new FileInputStream( location ); + } + } + } + catch( IOException e ) + { + return super.resolveEntity( publicId, systemId ); + } + + if( inputStream == null ) + { + return super.resolveEntity( publicId, systemId ); + } + else + { + return new InputSource( inputStream ); + } + } + + /** + * Receive notification that the start of an XML element has been found. + * + * @param name String name of the element found. + * @param atts AttributeList of the attributes included with the element + * (if any). + * @throws SAXException If the parser cannot process the document. + */ + public void startElement( String name, AttributeList atts ) + throws SAXException + { + + /* + * I need to "push" the element onto the String (currentLoc) which + * always represents the current location in the XML document. + */ + currentLoc += "\\" + name; + + /* + * A new element has started, so reset the text being captured + */ + currentText = ""; + + if( currentLoc.equals( "\\ejb-jar" ) ) + { + iasDescriptor = false; + } + else if( currentLoc.equals( "\\ias-ejb-jar" ) ) + { + iasDescriptor = true; + } + + if( ( name.equals( "session" ) ) || ( name.equals( "entity" ) ) ) + { + ejbType = name; + } + } + + /** + * Receive notification that character data has been found in an + * iAS-specific descriptor. We're interested in retrieving data + * indicating whether the bean must support RMI/IIOP access, whether the + * bean must provide highly available stubs and skeletons (in the case + * of stateful session beans), and if this bean uses additional CMP XML + * descriptors (in the case of entity beans with CMP). + * + * @param value String data found in the XML document. + */ + private void iasCharacters( String value ) + { + String base = "\\ias-ejb-jar\\enterprise-beans\\" + ejbType; + + if( currentLoc.equals( base + "\\ejb-name" ) ) + { + currentEjb = ( EjbInfo )ejbs.get( value ); + if( currentEjb == null ) + { + currentEjb = new EjbInfo( value ); + ejbs.put( value, currentEjb ); + } + } + else if( currentLoc.equals( base + "\\iiop" ) ) + { + currentEjb.setIiop( value ); + } + else if( currentLoc.equals( base + "\\failover-required" ) ) + { + currentEjb.setHasession( value ); + } + else if( currentLoc.equals( base + "\\persistence-manager" + + "\\properties-file-location" ) ) + { + currentEjb.addCmpDescriptor( value ); + } + } + + /** + * Receive notification that character data has been found in a standard + * EJB 1.1 descriptor. We're interested in retrieving the home + * interface, remote interface, implementation class, the type of bean, + * and if the bean uses CMP. + * + * @param value String data found in the XML document. + */ + private void stdCharacters( String value ) + { + + if( currentLoc.equals( "\\ejb-jar\\display-name" ) ) + { + displayName = value; + return; + } + + String base = "\\ejb-jar\\enterprise-beans\\" + ejbType; + + if( currentLoc.equals( base + "\\ejb-name" ) ) + { + currentEjb = ( EjbInfo )ejbs.get( value ); + if( currentEjb == null ) + { + currentEjb = new EjbInfo( value ); + ejbs.put( value, currentEjb ); + } + } + else if( currentLoc.equals( base + "\\home" ) ) + { + currentEjb.setHome( value ); + } + else if( currentLoc.equals( base + "\\remote" ) ) + { + currentEjb.setRemote( value ); + } + else if( currentLoc.equals( base + "\\ejb-class" ) ) + { + currentEjb.setImplementation( value ); + } + else if( currentLoc.equals( base + "\\session-type" ) ) + { + currentEjb.setBeantype( value ); + } + else if( currentLoc.equals( base + "\\persistence-type" ) ) + { + currentEjb.setCmp( value ); + } + } + }// End of Classname inner class + + + /** + * Thread class used to redirect output from an InputStream to + * the JRE standard output. This class may be used to redirect output from + * an external process to the standard output. + * + * @author Greg Nelson greg@netscape.com + * + */ + private class RedirectOutput extends Thread + { + InputStream stream;// Stream to read and redirect to standard output + + /** + * Constructs a new instance that will redirect output from the + * specified stream to the standard output. + * + * @param stream InputStream which will be read and redirected to the + * standard output. + */ + public RedirectOutput( InputStream stream ) + { + this.stream = stream; + } + + /** + * Reads text from the input stream and redirects it to standard output + * using a separate thread. + */ + public void run() + { + BufferedReader reader = new BufferedReader( + new InputStreamReader( stream ) ); + String text; + try + { + while( ( text = reader.readLine() ) != null ) + { + System.out.println( text ); + } + } + catch( IOException e ) + { + e.printStackTrace(); + } + finally + { + try + { + reader.close(); + } + catch( IOException e ) + { + // Do nothing + } + } + } + }// End of RedirectOutput inner class + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java new file mode 100644 index 000000000..ce5f22396 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/IPlanetEjbcTask.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.IOException; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.Path; +import org.xml.sax.SAXException; + +/** + * Task to compile EJB stubs and skeletons for the iPlanet Application Server. + * The EJBs to be processed are specified by the EJB 1.1 standard XML + * descriptor, and additional attributes are obtained from the iPlanet + * Application Server-specific XML descriptor. Since the XML descriptors can + * include multiple EJBs, this is a convenient way of specifying many EJBs in a + * single Ant task. The following attributes are allowed: + *

                + *
              • ejbdescriptor -- Standard EJB 1.1 XML descriptor (typically + * titled "ejb-jar.xml"). This attribute is required. + *
              • iasdescriptor -- EJB XML descriptor for iPlanet Application + * Server (typically titled "ias-ejb-jar.xml). This attribute is required. + * + *
              • dest -- The is the base directory where the RMI stubs and + * skeletons are written. In addition, the class files for each bean (home + * interface, remote interface, and EJB implementation) must be found in this + * directory. This attribute is required. + *
              • classpath -- The classpath used when generating EJB stubs and + * skeletons. This is an optional attribute (if omitted, the classpath + * specified when Ant was started will be used). Nested "classpath" elements + * may also be used. + *
              • keepgenerated -- Indicates whether or not the Java source files + * which are generated by ejbc will be saved or automatically deleted. If + * "yes", the source files will be retained. This is an optional attribute (if + * omitted, it defaults to "no"). + *
              • debug -- Indicates whether or not the ejbc utility should log + * additional debugging statements to the standard output. If "yes", the + * additional debugging statements will be generated (if omitted, it defaults + * to "no"). + *
              • iashome -- May be used to specify the "home" directory for this + * iPlanet Application Server installation. This is used to find the ejbc + * utility if it isn't included in the user's system path. This is an optional + * attribute (if specified, it should refer to the [install-location]/iplanet/ias6/ias + * directory). If omitted, the ejbc utility + * must be on the user's system path. + *
              + *

              + * + * For each EJB specified, this task will locate the three classes that comprise + * the EJB. If these class files cannot be located in the dest + * directory, the task will fail. The task will also attempt to locate the EJB + * stubs and skeletons in this directory. If found, the timestamps on the stubs + * and skeletons will be checked to ensure they are up to date. Only if these + * files cannot be found or if they are out of date will ejbc be called to + * generate new stubs and skeletons. + * + * @author Greg Nelson greg@netscape.com + * @see IPlanetEjbc + */ +public class IPlanetEjbcTask extends Task +{ + private boolean keepgenerated = false; + private boolean debug = false; + private Path classpath; + private File dest; + + /* + * Attributes set by the Ant build file + */ + private File ejbdescriptor; + private File iasdescriptor; + private File iashome; + + /** + * Sets the classpath to be used when compiling the EJB stubs and skeletons. + * + * @param classpath The classpath to be used. + */ + public void setClasspath( Path classpath ) + { + if( this.classpath == null ) + { + this.classpath = classpath; + } + else + { + this.classpath.append( classpath ); + } + } + + /** + * Sets whether or not debugging output will be generated when ejbc is + * executed. + * + * @param debug A boolean indicating if debugging output should be generated + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Sets the destination directory where the EJB "source" classes must exist + * and where the stubs and skeletons will be written. The destination + * directory must exist before this task is executed. + * + * @param dest The directory where the compiled classes will be written. + */ + public void setDest( File dest ) + { + this.dest = dest; + } + + /** + * Sets the location of the standard XML EJB descriptor. Typically, this + * file is named "ejb-jar.xml". + * + * @param ejbdescriptor The name and location of the EJB descriptor. + */ + public void setEjbdescriptor( File ejbdescriptor ) + { + this.ejbdescriptor = ejbdescriptor; + } + + /** + * Sets the location of the iAS-specific XML EJB descriptor. Typically, this + * file is named "ias-ejb-jar.xml". + * + * @param iasdescriptor The name and location of the iAS-specific EJB + * descriptor. + */ + public void setIasdescriptor( File iasdescriptor ) + { + this.iasdescriptor = iasdescriptor; + } + + /** + * Setter method used to store the "home" directory of the user's iAS + * installation. The directory specified should typically be [install-location]/iplanet/ias6/ias + * . + * + * @param iashome The home directory for the user's iAS installation. + */ + public void setIashome( File iashome ) + { + this.iashome = iashome; + } + + /** + * Sets whether or not the Java source files which are generated by the ejbc + * process should be retained or automatically deleted. + * + * @param keepgenerated A boolean indicating if the Java source files for + * the stubs and skeletons should be retained. + */ + public void setKeepgenerated( boolean keepgenerated ) + { + this.keepgenerated = keepgenerated; + } + + /** + * Creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Does the work. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + checkConfiguration(); + + executeEjbc( getParser() ); + } + + /** + * Returns the CLASSPATH to be used when calling EJBc. If no user CLASSPATH + * is specified, the System classpath is returned instead. + * + * @return Path The classpath to be used for EJBc. + */ + private Path getClasspath() + { + if( classpath == null ) + { + classpath = Path.systemClasspath; + } + + return classpath; + } + + /** + * Returns a SAXParser that may be used to process the XML descriptors. + * + * @return Parser which may be used to process the EJB descriptors. + * @throws BuildException If the parser cannot be created or configured. + */ + private SAXParser getParser() + throws BuildException + { + + SAXParser saxParser = null; + try + { + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setValidating( true ); + saxParser = saxParserFactory.newSAXParser(); + } + catch( SAXException e ) + { + String msg = "Unable to create a SAXParser: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + catch( ParserConfigurationException e ) + { + String msg = "Unable to create a SAXParser: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + + return saxParser; + } + + /** + * Verifies that the user selections are valid. + * + * @throws BuildException If the user selections are invalid. + */ + private void checkConfiguration() + throws BuildException + { + + if( ejbdescriptor == null ) + { + String msg = "The standard EJB descriptor must be specified using " + + "the \"ejbdescriptor\" attribute."; + throw new BuildException( msg, location ); + } + if( ( !ejbdescriptor.exists() ) || ( !ejbdescriptor.isFile() ) ) + { + String msg = "The standard EJB descriptor (" + ejbdescriptor + + ") was not found or isn't a file."; + throw new BuildException( msg, location ); + } + + if( iasdescriptor == null ) + { + String msg = "The iAS-speific XML descriptor must be specified using" + + " the \"iasdescriptor\" attribute."; + throw new BuildException( msg, location ); + } + if( ( !iasdescriptor.exists() ) || ( !iasdescriptor.isFile() ) ) + { + String msg = "The iAS-specific XML descriptor (" + iasdescriptor + + ") was not found or isn't a file."; + throw new BuildException( msg, location ); + } + + if( dest == null ) + { + String msg = "The destination directory must be specified using " + + "the \"dest\" attribute."; + throw new BuildException( msg, location ); + } + if( ( !dest.exists() ) || ( !dest.isDirectory() ) ) + { + String msg = "The destination directory (" + dest + ") was not " + + "found or isn't a directory."; + throw new BuildException( msg, location ); + } + + if( ( iashome != null ) && ( !iashome.isDirectory() ) ) + { + String msg = "If \"iashome\" is specified, it must be a valid " + + "directory (it was set to " + iashome + ")."; + throw new BuildException( msg, getLocation() ); + } + } + + /** + * Executes the EJBc utility using the SAXParser provided. + * + * @param saxParser SAXParser that may be used to process the EJB + * descriptors + * @throws BuildException If there is an error reading or parsing the XML + * descriptors + */ + private void executeEjbc( SAXParser saxParser ) + throws BuildException + { + IPlanetEjbc ejbc = new IPlanetEjbc( ejbdescriptor, + iasdescriptor, + dest, + getClasspath().toString(), + saxParser ); + ejbc.setRetainSource( keepgenerated ); + ejbc.setDebugOutput( debug ); + if( iashome != null ) + { + ejbc.setIasHomeDir( iashome ); + } + + try + { + ejbc.execute(); + } + catch( IOException e ) + { + String msg = "An IOException occurred while trying to read the XML " + + "descriptor file: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + catch( SAXException e ) + { + String msg = "A SAXException occurred while trying to read the XML " + + "descriptor file: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + catch( IPlanetEjbc.EjbcException e ) + { + String msg = "An exception occurred while trying to run the ejbc " + + "utility: " + e.getMessage(); + throw new BuildException( msg, e, location ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java new file mode 100644 index 000000000..a5a7d70f0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/InnerClassFilenameFilter.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FilenameFilter; + +public class InnerClassFilenameFilter implements FilenameFilter +{ + private String baseClassName; + + InnerClassFilenameFilter( String baseclass ) + { + int extidx = baseclass.lastIndexOf( ".class" ); + if( extidx == -1 ) + { + extidx = baseclass.length() - 1; + } + baseClassName = baseclass.substring( 0, extidx ); + } + + public boolean accept( File Dir, String filename ) + { + if( ( filename.lastIndexOf( "." ) != filename.lastIndexOf( ".class" ) ) + || ( filename.indexOf( baseClassName + "$" ) != 0 ) ) + { + return false; + } + return true; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java new file mode 100644 index 000000000..06fd4ad05 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/JbossDeploymentTool.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.util.Hashtable; +import org.apache.tools.ant.Project; + +/** + * The deployment tool to add the jboss specific deployment descriptor to the + * ejb jar file. Jboss only requires one additional file jboss.xml and does not + * require any additional compilation. + * + * @author Paul Austin + * @version 1.0 + * @see EjbJar#createJboss + */ +public class JbossDeploymentTool extends GenericDeploymentTool +{ + protected final static String JBOSS_DD = "jboss.xml"; + protected final static String JBOSS_CMPD = "jaws.xml"; + + /** + * Instance variable that stores the suffix for the jboss jarfile. + */ + private String jarSuffix = ".jar"; + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + File jbossDD = new File( getConfig().descriptorDir, ddPrefix + JBOSS_DD ); + if( jbossDD.exists() ) + { + ejbFiles.put( META_DIR + JBOSS_DD, jbossDD ); + } + else + { + log( "Unable to locate jboss deployment descriptor. It was expected to be in " + jbossDD.getPath(), Project.MSG_WARN ); + return; + } + + File jbossCMPD = new File( getConfig().descriptorDir, ddPrefix + JBOSS_CMPD ); + if( jbossCMPD.exists() ) + { + ejbFiles.put( META_DIR + JBOSS_CMPD, jbossCMPD ); + } + } + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( getDestDir(), baseName + jarSuffix ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java new file mode 100644 index 000000000..e4d43714a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLRun.java @@ -0,0 +1,424 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; + +/** + * Execute a Weblogic server. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class WLRun extends Task +{ + protected final static String DEFAULT_WL51_POLICY_FILE = "weblogic.policy"; + protected final static String DEFAULT_WL60_POLICY_FILE = "lib/weblogic.policy"; + protected final static String DEFAULT_PROPERTIES_FILE = "weblogic.properties"; + + private String weblogicMainClass = "weblogic.Server"; + + /** + * Addional arguments to pass to the JVM used to run weblogic + */ + private String additionalArgs = ""; + + /** + * The name of the weblogic server - used to select the server's directory + * in the weblogic home directory. + */ + private String weblogicSystemName = "myserver"; + + /** + * The file containing the weblogic properties for this server. + */ + private String weblogicPropertiesFile = null; + + /** + * additional args to pass to the spawned jvm + */ + private String additionalJvmArgs = ""; + + /** + * The location of the BEA Home under which this server is run. WL6 only + */ + private File beaHome = null; + + /** + * The management username + */ + private String managementUsername = "system"; + + /** + * The management password + */ + private String managementPassword = null; + + /** + * The provate key password - used for SSL + */ + private String pkPassword = null; + + /** + * The classpath to be used when running the Java VM. It must contain the + * weblogic classes and the implementation classes of the home and + * remote interfaces. + */ + private Path classpath; + + /** + * The security policy to use when running the weblogic server + */ + private String securityPolicy; + + /** + * The weblogic classpath to the be used when running weblogic. + */ + private Path weblogicClasspath; + + /** + * The weblogic domain + */ + private String weblogicDomainName; + + /** + * The weblogic system home directory + */ + private File weblogicSystemHome; + + public void setArgs( String args ) + { + additionalArgs = args; + } + + /** + * The location of the BEA Home. + * + * @param beaHome the BEA Home directory. + */ + public void setBEAHome( File beaHome ) + { + this.beaHome = beaHome; + } + + + /** + * Set the classpath to be used for this execution. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + this.classpath = classpath; + } + + /** + * Set the Domain to run in + * + * @param domain the domain + */ + public void setDomain( String domain ) + { + this.weblogicDomainName = domain; + } + + /** + * The location where weblogic lives. + * + * @param weblogicHome the home directory of weblogic. + */ + public void setHome( File weblogicHome ) + { + weblogicSystemHome = weblogicHome; + } + + /** + * Set the additional arguments to pass to the weblogic JVM + * + * @param args the arguments to be passed to the JVM + */ + public void setJvmargs( String args ) + { + this.additionalJvmArgs = args; + } + + /** + * Set the name of the server to run + * + * @param serverName The new Name value + */ + public void setName( String serverName ) + { + this.weblogicSystemName = serverName; + } + + /** + * Set the private key password so the server can decrypt the SSL private + * key file. + * + * @param pkpassword the private key password, + */ + public void setPKPassword( String pkpassword ) + { + this.pkPassword = pkpassword; + } + + + /** + * Set the management password of the server + * + * @param password the management pasword of the server. + */ + public void setPassword( String password ) + { + this.managementPassword = password; + } + + /** + * Set the security policy for this invocation of weblogic. + * + * @param securityPolicy the security policy to use. + */ + public void setPolicy( String securityPolicy ) + { + this.securityPolicy = securityPolicy; + } + + /** + * Set the properties file to use. The location of the properties file is + * relative to the weblogi system home + * + * @param propertiesFilename the properties file name + */ + public void setProperties( String propertiesFilename ) + { + this.weblogicPropertiesFile = propertiesFilename; + } + + /** + * Set the management username to run the server + * + * @param username the management username of the server. + */ + public void setUsername( String username ) + { + this.managementUsername = username; + } + + + public void setWeblogicMainClass( String c ) + { + weblogicMainClass = c; + } + + /** + * Set the weblogic classpath. The weblogic classpath is used by weblogic to + * support dynamic class loading. + * + * @param weblogicClasspath the weblogic classpath + */ + public void setWlclasspath( Path weblogicClasspath ) + { + this.weblogicClasspath = weblogicClasspath; + } + + /** + * Add the classpath for the user classes + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Get the classpath to the weblogic classpaths + * + * @return Description of the Returned Value + */ + public Path createWLClasspath() + { + if( weblogicClasspath == null ) + { + weblogicClasspath = new Path( project ); + } + return weblogicClasspath.createPath(); + } + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * a helper task. This approach allows the classpath of the helper task to + * be set. Since the weblogic tools require the class files of the project's + * home and remote interfaces to be available in the classpath, this also + * avoids having to start ant with the class path of the project it is + * building. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( weblogicSystemHome == null ) + { + throw new BuildException( "weblogic home must be set" ); + } + if( !weblogicSystemHome.isDirectory() ) + { + throw new BuildException( "weblogic home directory " + weblogicSystemHome.getPath() + + " is not valid" ); + } + + if( beaHome != null ) + { + executeWLS6(); + } + else + { + executeWLS(); + } + } + + private void executeWLS() + { + File securityPolicyFile = findSecurityPolicyFile( DEFAULT_WL51_POLICY_FILE ); + File propertiesFile = null; + + if( weblogicPropertiesFile == null ) + { + weblogicPropertiesFile = DEFAULT_PROPERTIES_FILE; + } + propertiesFile = new File( weblogicSystemHome, weblogicPropertiesFile ); + if( !propertiesFile.exists() ) + { + // OK, properties file may be absolute + propertiesFile = project.resolveFile( weblogicPropertiesFile ); + if( !propertiesFile.exists() ) + { + throw new BuildException( "Properties file " + weblogicPropertiesFile + + " not found in weblogic home " + weblogicSystemHome + + " or as absolute file" ); + } + } + + Java weblogicServer = ( Java )project.createTask( "java" ); + weblogicServer.setTaskName( getTaskName() ); + weblogicServer.setFork( true ); + weblogicServer.setClassname( weblogicMainClass ); + + String jvmArgs = additionalJvmArgs; + + if( weblogicClasspath != null ) + { + jvmArgs += " -Dweblogic.class.path=" + weblogicClasspath; + } + + jvmArgs += " -Djava.security.manager -Djava.security.policy==" + securityPolicyFile; + jvmArgs += " -Dweblogic.system.home=" + weblogicSystemHome; + jvmArgs += " -Dweblogic.system.name=" + weblogicSystemName; + jvmArgs += " -Dweblogic.system.propertiesFile=" + weblogicPropertiesFile; + + weblogicServer.createJvmarg().setLine( jvmArgs ); + weblogicServer.createArg().setLine( additionalArgs ); + + if( classpath != null ) + { + weblogicServer.setClasspath( classpath ); + } + if( weblogicServer.executeJava() != 0 ) + { + throw new BuildException( "Execution of weblogic server failed" ); + } + } + + private void executeWLS6() + { + File securityPolicyFile = findSecurityPolicyFile( DEFAULT_WL60_POLICY_FILE ); + if( !beaHome.isDirectory() ) + { + throw new BuildException( "BEA home " + beaHome.getPath() + + " is not valid" ); + } + + File configFile = new File( weblogicSystemHome, "config/" + weblogicDomainName + "/config.xml" ); + if( !configFile.exists() ) + { + throw new BuildException( "Server config file " + configFile + " not found." ); + } + + if( managementPassword == null ) + { + throw new BuildException( "You must supply a management password to start the server" ); + } + + Java weblogicServer = ( Java )project.createTask( "java" ); + weblogicServer.setTaskName( getTaskName() ); + weblogicServer.setFork( true ); + weblogicServer.setDir( weblogicSystemHome ); + weblogicServer.setClassname( weblogicMainClass ); + + String jvmArgs = additionalJvmArgs; + + jvmArgs += " -Dweblogic.Domain=" + weblogicDomainName; + jvmArgs += " -Dweblogic.Name=" + weblogicSystemName; + jvmArgs += " -Dweblogic.system.home=" + weblogicSystemHome; + + jvmArgs += " -Dbea.home=" + beaHome; + jvmArgs += " -Djava.security.policy==" + securityPolicyFile; + + jvmArgs += " -Dweblogic.management.username=" + managementUsername; + jvmArgs += " -Dweblogic.management.password=" + managementPassword; + if( pkPassword != null ) + { + jvmArgs += " -Dweblogic.pkpassword=" + pkPassword; + } + + weblogicServer.createJvmarg().setLine( jvmArgs ); + weblogicServer.createArg().setLine( additionalArgs ); + + if( classpath != null ) + { + weblogicServer.setClasspath( classpath ); + } + + if( weblogicServer.executeJava() != 0 ) + { + throw new BuildException( "Execution of weblogic server failed" ); + } + } + + private File findSecurityPolicyFile( String defaultSecurityPolicy ) + { + String securityPolicy = this.securityPolicy; + if( securityPolicy == null ) + { + securityPolicy = defaultSecurityPolicy; + } + File securityPolicyFile = new File( weblogicSystemHome, securityPolicy ); + // If an explicit securityPolicy file was specified, it maybe an + // absolute path. Use the project to resolve it. + if( this.securityPolicy != null && !securityPolicyFile.exists() ) + { + securityPolicyFile = project.resolveFile( securityPolicy ); + } + // If we still can't find it, complain + if( !securityPolicyFile.exists() ) + { + throw new BuildException( "Security policy " + securityPolicy + + " was not found." ); + } + return securityPolicyFile; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java new file mode 100644 index 000000000..076e057ed --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WLStop.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; + +/** + * Shutdown a Weblogic server. + * + * @author Conor MacNeill , + * Cortex ebusiness Pty Limited + */ +public class WLStop extends Task +{ + + /** + * The delay (in seconds) to wait before shutting down. + */ + private int delay = 0; + + /** + * The location of the BEA Home under which this server is run. WL6 only + */ + private File beaHome = null; + /** + * The classpath to be used. It must contains the weblogic.Admin class. + */ + private Path classpath; + + /** + * The password to use to shutdown the weblogic server. + */ + private String password; + + /** + * The URL which the weblogic server is listening on. + */ + private String serverURL; + + /** + * The weblogic username to use to request the shutdown. + */ + private String username; + + /** + * The location of the BEA Home. + * + * @param beaHome the BEA Home directory. + */ + public void setBEAHome( File beaHome ) + { + this.beaHome = beaHome; + } + + /** + * Set the classpath to be used for this compilation. + * + * @param path The new Classpath value + */ + public void setClasspath( Path path ) + { + this.classpath = path; + } + + + /** + * Set the delay (in seconds) before shutting down the server. + * + * @param s the selay. + */ + public void setDelay( String s ) + { + delay = Integer.parseInt( s ); + } + + /** + * Set the password to use to request shutdown of the server. + * + * @param s the password. + */ + public void setPassword( String s ) + { + this.password = s; + } + + /** + * Set the URL to which the weblogic server is listening. + * + * @param s the url. + */ + public void setUrl( String s ) + { + this.serverURL = s; + } + + /** + * Set the username to use to request shutdown of the server. + * + * @param s the username. + */ + public void setUser( String s ) + { + this.username = s; + } + + /** + * Add the classpath for the user classes + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + { + classpath = new Path( project ); + } + return classpath.createPath(); + } + + /** + * Do the work. The work is actually done by creating a separate JVM to run + * the weblogic admin task This approach allows the classpath of the helper + * task to be set. + * + * @exception BuildException if someting goes wrong with the build + */ + public void execute() + throws BuildException + { + if( username == null || password == null ) + { + throw new BuildException( "weblogic username and password must both be set" ); + } + + if( serverURL == null ) + { + throw new BuildException( "The url of the weblogic server must be provided." ); + } + + Java weblogicAdmin = ( Java )project.createTask( "java" ); + weblogicAdmin.setFork( true ); + weblogicAdmin.setClassname( "weblogic.Admin" ); + String args; + + if( beaHome == null ) + { + args = serverURL + " SHUTDOWN " + username + " " + password + " " + delay; + } + else + { + args = " -url " + serverURL + + " -username " + username + + " -password " + password + + " SHUTDOWN " + " " + delay; + } + + weblogicAdmin.setArgs( args ); + weblogicAdmin.setClasspath( classpath ); + weblogicAdmin.execute(); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java new file mode 100644 index 000000000..cc7d965ad --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicDeploymentTool.java @@ -0,0 +1,852 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.types.Path; +import org.xml.sax.InputSource; + +public class WeblogicDeploymentTool extends GenericDeploymentTool +{ + public final static String PUBLICID_EJB11 + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"; + public final static String PUBLICID_EJB20 + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"; + public final static String PUBLICID_WEBLOGIC_EJB510 + = "-//BEA Systems, Inc.//DTD WebLogic 5.1.0 EJB//EN"; + public final static String PUBLICID_WEBLOGIC_EJB600 + = "-//BEA Systems, Inc.//DTD WebLogic 6.0.0 EJB//EN"; + + protected final static String DEFAULT_WL51_EJB11_DTD_LOCATION + = "/weblogic/ejb/deployment/xml/ejb-jar.dtd"; + protected final static String DEFAULT_WL60_EJB11_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/ejb11-jar.dtd"; + protected final static String DEFAULT_WL60_EJB20_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/ejb20-jar.dtd"; + + protected final static String DEFAULT_WL51_DTD_LOCATION + = "/weblogic/ejb/deployment/xml/weblogic-ejb-jar.dtd"; + protected final static String DEFAULT_WL60_51_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/weblogic510-ejb-jar.dtd"; + protected final static String DEFAULT_WL60_DTD_LOCATION + = "/weblogic/ejb20/dd/xml/weblogic600-ejb-jar.dtd"; + + protected final static String DEFAULT_COMPILER = "default"; + + protected final static String WL_DD = "weblogic-ejb-jar.xml"; + protected final static String WL_CMP_DD = "weblogic-cmp-rdbms-jar.xml"; + + protected final static String COMPILER_EJB11 = "weblogic.ejbc"; + protected final static String COMPILER_EJB20 = "weblogic.ejbc20"; + + /** + * Instance variable that stores the suffix for the weblogic jarfile. + */ + private String jarSuffix = ".jar"; + + /** + * Instance variable that determines whether generic ejb jars are kept. + */ + private boolean keepgenerated = false; + + /** + * Instance variable that stores the fully qualified classname of the + * weblogic EJBC compiler + */ + private String ejbcClass = null; + + private String additionalArgs = ""; + + private boolean keepGeneric = false; + + private String compiler = null; + + private boolean alwaysRebuild = true; + + /** + * controls whether ejbc is run on the generated jar + */ + private boolean noEJBC = false; + + /** + * Indicates if the old CMP location convention is to be used. + */ + private boolean newCMP = false; + + /** + * The classpath to the weblogic classes. + */ + private Path wlClasspath = null; + + /** + * The weblogic.StdoutSeverityLevel to use when running the JVM that + * executes ejbc. Set to 16 to avoid the warnings about EJB Home and Remotes + * being in the classpath + */ + private Integer jvmDebugLevel = null; + + /** + * Instance variable that stores the location of the ejb 1.1 DTD file. + */ + private String ejb11DTD; + + /** + * Instance variable that stores the location of the weblogic DTD file. + */ + private String weblogicDTD; + + /** + * sets some additional args to send to ejbc. + * + * @param args The new Args value + */ + public void setArgs( String args ) + { + this.additionalArgs = args; + } + + /** + * The compiler (switch -compiler) to use + * + * @param compiler The new Compiler value + */ + public void setCompiler( String compiler ) + { + this.compiler = compiler; + } + + /** + * Setter used to store the location of the Sun's Generic EJB DTD. This can + * be a file on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setEJBdtd( String inString ) + { + this.ejb11DTD = inString; + } + + /** + * Set the classname of the ejbc compiler + * + * @param ejbcClass The new EjbcClass value + */ + public void setEjbcClass( String ejbcClass ) + { + this.ejbcClass = ejbcClass; + } + + /** + * Sets the weblogic.StdoutSeverityLevel to use when running the JVM that + * executes ejbc. Set to 16 to avoid the warnings about EJB Home and Remotes + * being in the classpath + * + * @param jvmDebugLevel The new JvmDebugLevel value + */ + public void setJvmDebugLevel( Integer jvmDebugLevel ) + { + this.jvmDebugLevel = jvmDebugLevel; + } + + /** + * Sets whether -keepgenerated is passed to ejbc (that is, the .java source + * files are kept). + * + * @param inValue either 'true' or 'false' + */ + public void setKeepgenerated( String inValue ) + { + this.keepgenerated = Boolean.valueOf( inValue ).booleanValue(); + } + + /** + * Setter used to store the value of keepGeneric + * + * @param inValue a string, either 'true' or 'false'. + */ + public void setKeepgeneric( boolean inValue ) + { + this.keepGeneric = inValue; + } + + /** + * Set the value of the newCMP scheme. The old CMP scheme locates the + * weblogic CMP descriptor based on the naming convention where the weblogic + * CMP file is expected to be named with the bean name as the prefix. Under + * this scheme the name of the CMP descriptor does not match the name + * actually used in the main weblogic EJB descriptor. Also, descriptors + * which contain multiple CMP references could not be used. + * + * @param newCMP The new NewCMP value + */ + public void setNewCMP( boolean newCMP ) + { + this.newCMP = newCMP; + } + + /** + * Do not EJBC the jar after it has been put together. + * + * @param noEJBC The new NoEJBC value + */ + public void setNoEJBC( boolean noEJBC ) + { + this.noEJBC = noEJBC; + } + + /** + * Set the value of the oldCMP scheme. This is an antonym for newCMP + * + * @param oldCMP The new OldCMP value + */ + public void setOldCMP( boolean oldCMP ) + { + this.newCMP = !oldCMP; + } + + /** + * Set the rebuild flag to false to only update changes in the jar rather + * than rerunning ejbc + * + * @param rebuild The new Rebuild value + */ + public void setRebuild( boolean rebuild ) + { + this.alwaysRebuild = rebuild; + } + + + /** + * Setter used to store the suffix for the generated weblogic jar file. + * + * @param inString the string to use as the suffix. + */ + public void setSuffix( String inString ) + { + this.jarSuffix = inString; + } + + public void setWLClasspath( Path wlClasspath ) + { + this.wlClasspath = wlClasspath; + } + + /** + * Setter used to store the location of the weblogic DTD. This can be a file + * on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setWLdtd( String inString ) + { + this.weblogicDTD = inString; + } + + + /** + * Setter used to store the location of the ejb-jar DTD. This can be a file + * on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + public void setWeblogicdtd( String inString ) + { + setEJBdtd( inString ); + } + + /** + * Get the ejbc compiler class + * + * @return The EjbcClass value + */ + public String getEjbcClass() + { + return ejbcClass; + } + + public Integer getJvmDebugLevel() + { + return jvmDebugLevel; + } + + /** + * Get the classpath to the weblogic classpaths + * + * @return Description of the Returned Value + */ + public Path createWLClasspath() + { + if( wlClasspath == null ) + { + wlClasspath = new Path( getTask().getProject() ); + } + return wlClasspath.createPath(); + } + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + public void validateConfigured() + throws BuildException + { + super.validateConfigured(); + } + + /** + * Helper method invoked by isRebuildRequired to get a ClassLoader for a Jar + * File passed to it. + * + * @param classjar java.io.File representing jar file to get classes from. + * @return The ClassLoaderFromJar value + * @exception IOException Description of Exception + */ + protected ClassLoader getClassLoaderFromJar( File classjar ) + throws IOException + { + Path lookupPath = new Path( getTask().getProject() ); + lookupPath.setLocation( classjar ); + + Path classpath = getCombinedClasspath(); + if( classpath != null ) + { + lookupPath.append( classpath ); + } + + return new AntClassLoader( getTask().getProject(), lookupPath ); + } + + protected DescriptorHandler getWeblogicDescriptorHandler( final File srcDir ) + { + DescriptorHandler handler = + new DescriptorHandler( getTask(), srcDir ) + { + protected void processElement() + { + if( currentElement.equals( "type-storage" ) ) + { + // Get the filename of vendor specific descriptor + String fileNameWithMETA = currentText; + //trim the META_INF\ off of the file name + String fileName = fileNameWithMETA.substring( META_DIR.length(), + fileNameWithMETA.length() ); + File descriptorFile = new File( srcDir, fileName ); + + ejbFiles.put( fileNameWithMETA, descriptorFile ); + } + } + }; + + handler.registerDTD( PUBLICID_WEBLOGIC_EJB510, DEFAULT_WL51_DTD_LOCATION ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB510, DEFAULT_WL60_51_DTD_LOCATION ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB600, DEFAULT_WL60_DTD_LOCATION ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB510, weblogicDTD ); + handler.registerDTD( PUBLICID_WEBLOGIC_EJB600, weblogicDTD ); + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + } + return handler; + } + + + /** + * Helper method to check to see if a weblogic EBJ1.1 jar needs to be + * rebuilt using ejbc. Called from writeJar it sees if the "Bean" classes + * are the only thing that needs to be updated and either updates the Jar + * with the Bean classfile or returns true, saying that the whole weblogic + * jar needs to be regened with ejbc. This allows faster build times for + * working developers.

              + * + * The way weblogic ejbc works is it creates wrappers for the publicly + * defined methods as they are exposed in the remote interface. If the + * actual bean changes without changing the the method signatures then only + * the bean classfile needs to be updated and the rest of the weblogic jar + * file can remain the same. If the Interfaces, ie. the method signatures + * change or if the xml deployment dicriptors changed, the whole jar needs + * to be rebuilt with ejbc. This is not strictly true for the xml files. If + * the JNDI name changes then the jar doesnt have to be rebuild, but if the + * resources references change then it does. At this point the weblogic jar + * gets rebuilt if the xml files change at all. + * + * @param genericJarFile java.io.File The generic jar file. + * @param weblogicJarFile java.io.File The weblogic jar file to check to see + * if it needs to be rebuilt. + * @return The RebuildRequired value + */ + protected boolean isRebuildRequired( File genericJarFile, File weblogicJarFile ) + { + boolean rebuild = false; + + JarFile genericJar = null; + JarFile wlJar = null; + File newWLJarFile = null; + JarOutputStream newJarStream = null; + + try + { + log( "Checking if weblogic Jar needs to be rebuilt for jar " + weblogicJarFile.getName(), + Project.MSG_VERBOSE ); + // Only go forward if the generic and the weblogic file both exist + if( genericJarFile.exists() && genericJarFile.isFile() + && weblogicJarFile.exists() && weblogicJarFile.isFile() ) + { + //open jar files + genericJar = new JarFile( genericJarFile ); + wlJar = new JarFile( weblogicJarFile ); + + Hashtable genericEntries = new Hashtable(); + Hashtable wlEntries = new Hashtable(); + Hashtable replaceEntries = new Hashtable(); + + //get the list of generic jar entries + for( Enumeration e = genericJar.entries(); e.hasMoreElements(); ) + { + JarEntry je = ( JarEntry )e.nextElement(); + genericEntries.put( je.getName().replace( '\\', '/' ), je ); + } + //get the list of weblogic jar entries + for( Enumeration e = wlJar.entries(); e.hasMoreElements(); ) + { + JarEntry je = ( JarEntry )e.nextElement(); + wlEntries.put( je.getName(), je ); + } + + //Cycle Through generic and make sure its in weblogic + ClassLoader genericLoader = getClassLoaderFromJar( genericJarFile ); + for( Enumeration e = genericEntries.keys(); e.hasMoreElements(); ) + { + String filepath = ( String )e.nextElement(); + if( wlEntries.containsKey( filepath ) ) + {// File name/path match + + // Check files see if same + JarEntry genericEntry = ( JarEntry )genericEntries.get( filepath ); + JarEntry wlEntry = ( JarEntry )wlEntries.get( filepath ); + if( ( genericEntry.getCrc() != wlEntry.getCrc() ) || // Crc's Match + ( genericEntry.getSize() != wlEntry.getSize() ) ) + {// Size Match + + if( genericEntry.getName().endsWith( ".class" ) ) + { + //File are different see if its an object or an interface + String classname = genericEntry.getName().replace( File.separatorChar, '.' ); + classname = classname.substring( 0, classname.lastIndexOf( ".class" ) ); + Class genclass = genericLoader.loadClass( classname ); + if( genclass.isInterface() ) + { + //Interface changed rebuild jar. + log( "Interface " + genclass.getName() + " has changed", Project.MSG_VERBOSE ); + rebuild = true; + break; + } + else + { + //Object class Changed update it. + replaceEntries.put( filepath, genericEntry ); + } + } + else + { + // is it the manifest. If so ignore it + if( !genericEntry.getName().equals( "META-INF/MANIFEST.MF" ) ) + { + //File other then class changed rebuild + log( "Non class file " + genericEntry.getName() + " has changed", Project.MSG_VERBOSE ); + rebuild = true; + break; + } + } + } + } + else + {// a file doesnt exist rebuild + + log( "File " + filepath + " not present in weblogic jar", Project.MSG_VERBOSE ); + rebuild = true; + break; + } + } + + if( !rebuild ) + { + log( "No rebuild needed - updating jar", Project.MSG_VERBOSE ); + newWLJarFile = new File( weblogicJarFile.getAbsolutePath() + ".temp" ); + if( newWLJarFile.exists() ) + { + newWLJarFile.delete(); + } + + newJarStream = new JarOutputStream( new FileOutputStream( newWLJarFile ) ); + newJarStream.setLevel( 0 ); + + //Copy files from old weblogic jar + for( Enumeration e = wlEntries.elements(); e.hasMoreElements(); ) + { + byte[] buffer = new byte[1024]; + int bytesRead; + InputStream is; + JarEntry je = ( JarEntry )e.nextElement(); + if( je.getCompressedSize() == -1 || + je.getCompressedSize() == je.getSize() ) + { + newJarStream.setLevel( 0 ); + } + else + { + newJarStream.setLevel( 9 ); + } + + // Update with changed Bean class + if( replaceEntries.containsKey( je.getName() ) ) + { + log( "Updating Bean class from generic Jar " + je.getName(), Project.MSG_VERBOSE ); + // Use the entry from the generic jar + je = ( JarEntry )replaceEntries.get( je.getName() ); + is = genericJar.getInputStream( je ); + } + else + {//use fle from original weblogic jar + + is = wlJar.getInputStream( je ); + } + newJarStream.putNextEntry( new JarEntry( je.getName() ) ); + + while( ( bytesRead = is.read( buffer ) ) != -1 ) + { + newJarStream.write( buffer, 0, bytesRead ); + } + is.close(); + } + } + else + { + log( "Weblogic Jar rebuild needed due to changed interface or XML", Project.MSG_VERBOSE ); + } + } + else + { + rebuild = true; + } + } + catch( ClassNotFoundException cnfe ) + { + String cnfmsg = "ClassNotFoundException while processing ejb-jar file" + + ". Details: " + + cnfe.getMessage(); + throw new BuildException( cnfmsg, cnfe ); + } + catch( IOException ioe ) + { + String msg = "IOException while processing ejb-jar file " + + ". Details: " + + ioe.getMessage(); + throw new BuildException( msg, ioe ); + } + finally + { + // need to close files and perhaps rename output + if( genericJar != null ) + { + try + { + genericJar.close(); + } + catch( IOException closeException ) + {} + } + + if( wlJar != null ) + { + try + { + wlJar.close(); + } + catch( IOException closeException ) + {} + } + + if( newJarStream != null ) + { + try + { + newJarStream.close(); + } + catch( IOException closeException ) + {} + + weblogicJarFile.delete(); + newWLJarFile.renameTo( weblogicJarFile ); + if( !weblogicJarFile.exists() ) + { + rebuild = true; + } + } + } + + return rebuild; + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + File weblogicDD = new File( getConfig().descriptorDir, ddPrefix + WL_DD ); + + if( weblogicDD.exists() ) + { + ejbFiles.put( META_DIR + WL_DD, + weblogicDD ); + } + else + { + log( "Unable to locate weblogic deployment descriptor. It was expected to be in " + + weblogicDD.getPath(), Project.MSG_WARN ); + return; + } + + if( !newCMP ) + { + log( "The old method for locating CMP files has been DEPRECATED.", Project.MSG_VERBOSE ); + log( "Please adjust your weblogic descriptor and set newCMP=\"true\" " + + "to use the new CMP descriptor inclusion mechanism. ", Project.MSG_VERBOSE ); + // The the weblogic cmp deployment descriptor + File weblogicCMPDD = new File( getConfig().descriptorDir, ddPrefix + WL_CMP_DD ); + + if( weblogicCMPDD.exists() ) + { + ejbFiles.put( META_DIR + WL_CMP_DD, + weblogicCMPDD ); + } + } + else + { + // now that we have the weblogic descriptor, we parse the file + // to find other descriptors needed to deploy the bean. + // this could be the weblogic-cmp-rdbms.xml or any other O/R + // mapping tool descriptors. + try + { + File ejbDescriptor = ( File )ejbFiles.get( META_DIR + EJB_DD ); + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setValidating( true ); + SAXParser saxParser = saxParserFactory.newSAXParser(); + DescriptorHandler handler = getWeblogicDescriptorHandler( ejbDescriptor.getParentFile() ); + saxParser.parse( new InputSource + ( new FileInputStream + ( weblogicDD ) ), + handler ); + + Hashtable ht = handler.getFiles(); + Enumeration e = ht.keys(); + while( e.hasMoreElements() ) + { + String key = ( String )e.nextElement(); + ejbFiles.put( key, ht.get( key ) ); + } + } + catch( Exception e ) + { + String msg = "Exception while adding Vendor specific files: " + e.toString(); + throw new BuildException( msg, e ); + } + } + } + + protected void registerKnownDTDs( DescriptorHandler handler ) + { + // register all the known DTDs + handler.registerDTD( PUBLICID_EJB11, DEFAULT_WL51_EJB11_DTD_LOCATION ); + handler.registerDTD( PUBLICID_EJB11, DEFAULT_WL60_EJB11_DTD_LOCATION ); + handler.registerDTD( PUBLICID_EJB11, ejb11DTD ); + handler.registerDTD( PUBLICID_EJB20, DEFAULT_WL60_EJB20_DTD_LOCATION ); + } + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarFile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + protected void writeJar( String baseName, File jarFile, Hashtable files, + String publicId ) + throws BuildException + { + // need to create a generic jar first. + File genericJarFile = super.getVendorOutputJarFile( baseName ); + super.writeJar( baseName, genericJarFile, files, publicId ); + + if( alwaysRebuild || isRebuildRequired( genericJarFile, jarFile ) ) + { + buildWeblogicJar( genericJarFile, jarFile, publicId ); + } + if( !keepGeneric ) + { + log( "deleting generic jar " + genericJarFile.toString(), + Project.MSG_VERBOSE ); + genericJarFile.delete(); + } + } + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + File getVendorOutputJarFile( String baseName ) + { + return new File( getDestDir(), baseName + jarSuffix ); + } + + /** + * Helper method invoked by execute() for each WebLogic jar to be built. + * Encapsulates the logic of constructing a java task for calling + * weblogic.ejbc and executing it. + * + * @param sourceJar java.io.File representing the source (EJB1.1) jarfile. + * @param destJar java.io.File representing the destination, WebLogic + * jarfile. + * @param publicId Description of Parameter + */ + private void buildWeblogicJar( File sourceJar, File destJar, String publicId ) + { + org.apache.tools.ant.taskdefs.Java javaTask = null; + + if( noEJBC ) + { + try + { + getTask().getProject().copyFile( sourceJar, destJar ); + if( !keepgenerated ) + { + sourceJar.delete(); + } + return; + } + catch( IOException e ) + { + throw new BuildException( "Unable to write EJB jar", e ); + } + } + + String ejbcClassName = ejbcClass; + + try + { + javaTask = ( Java )getTask().getProject().createTask( "java" ); + javaTask.setTaskName( "ejbc" ); + + if( getJvmDebugLevel() != null ) + { + javaTask.createJvmarg().setLine( " -Dweblogic.StdoutSeverityLevel=" + jvmDebugLevel ); + } + + if( ejbcClassName == null ) + { + // try to determine it from publicId + if( PUBLICID_EJB11.equals( publicId ) ) + { + ejbcClassName = COMPILER_EJB11; + } + else if( PUBLICID_EJB20.equals( publicId ) ) + { + ejbcClassName = COMPILER_EJB20; + } + else + { + log( "Unrecognized publicId " + publicId + " - using EJB 1.1 compiler", Project.MSG_WARN ); + ejbcClassName = COMPILER_EJB11; + } + } + + javaTask.setClassname( ejbcClassName ); + javaTask.createArg().setLine( additionalArgs ); + if( keepgenerated ) + { + javaTask.createArg().setValue( "-keepgenerated" ); + } + if( compiler == null ) + { + // try to use the compiler specified by build.compiler. Right now we are just going + // to allow Jikes + String buildCompiler = getTask().getProject().getProperty( "build.compiler" ); + if( buildCompiler != null && buildCompiler.equals( "jikes" ) ) + { + javaTask.createArg().setValue( "-compiler" ); + javaTask.createArg().setValue( "jikes" ); + } + } + else + { + if( !compiler.equals( DEFAULT_COMPILER ) ) + { + javaTask.createArg().setValue( "-compiler" ); + javaTask.createArg().setLine( compiler ); + } + } + javaTask.createArg().setValue( sourceJar.getPath() ); + javaTask.createArg().setValue( destJar.getPath() ); + + Path classpath = wlClasspath; + if( classpath == null ) + { + classpath = getCombinedClasspath(); + } + + javaTask.setFork( true ); + if( classpath != null ) + { + javaTask.setClasspath( classpath ); + } + + log( "Calling " + ejbcClassName + " for " + sourceJar.toString(), + Project.MSG_VERBOSE ); + + if( javaTask.executeJava() != 0 ) + { + throw new BuildException( "Ejbc reported an error" ); + } + } + catch( Exception e ) + { + // Have to catch this because of the semantics of calling main() + String msg = "Exception while calling " + ejbcClassName + ". Details: " + e.toString(); + throw new BuildException( msg, e ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java new file mode 100644 index 000000000..3fab8bd54 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WeblogicTOPLinkDeploymentTool.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.File; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +public class WeblogicTOPLinkDeploymentTool extends WeblogicDeploymentTool +{ + + private final static String TL_DTD_LOC = "http://www.objectpeople.com/tlwl/dtd/toplink-cmp_2_5_1.dtd"; + private String toplinkDTD; + + private String toplinkDescriptor; + + /** + * Setter used to store the name of the toplink descriptor. + * + * @param inString the string to use as the descriptor name. + */ + public void setToplinkdescriptor( String inString ) + { + this.toplinkDescriptor = inString; + } + + /** + * Setter used to store the location of the toplink DTD file. This is + * expected to be an URL (file or otherwise). If running this on NT using a + * file URL, the safest thing would be to not use a drive spec in the URL + * and make sure the file resides on the drive that ANT is running from. + * This will keep the setting in the build XML platform independent. + * + * @param inString the string to use as the DTD location. + */ + public void setToplinkdtd( String inString ) + { + this.toplinkDTD = inString; + } + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + public void validateConfigured() + throws BuildException + { + super.validateConfigured(); + if( toplinkDescriptor == null ) + { + throw new BuildException( "The toplinkdescriptor attribute must be specified" ); + } + } + + protected DescriptorHandler getDescriptorHandler( File srcDir ) + { + DescriptorHandler handler = super.getDescriptorHandler( srcDir ); + if( toplinkDTD != null ) + { + handler.registerDTD( "-//The Object People, Inc.//DTD TOPLink for WebLogic CMP 2.5.1//EN", + toplinkDTD ); + } + else + { + handler.registerDTD( "-//The Object People, Inc.//DTD TOPLink for WebLogic CMP 2.5.1//EN", + TL_DTD_LOC ); + } + return handler; + } + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param ddPrefix The feature to be added to the VendorFiles attribute + */ + protected void addVendorFiles( Hashtable ejbFiles, String ddPrefix ) + { + super.addVendorFiles( ejbFiles, ddPrefix ); + // Then the toplink deployment descriptor + + // Setup a naming standard here?. + + + File toplinkDD = new File( getConfig().descriptorDir, ddPrefix + toplinkDescriptor ); + + if( toplinkDD.exists() ) + { + ejbFiles.put( META_DIR + toplinkDescriptor, + toplinkDD ); + } + else + { + log( "Unable to locate toplink deployment descriptor. It was expected to be in " + + toplinkDD.getPath(), Project.MSG_WARN ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java new file mode 100644 index 000000000..8272c3e20 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ejb/WebsphereDeploymentTool.java @@ -0,0 +1,1633 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ejb; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.jar.*; +import javax.xml.parsers.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.taskdefs.*; +import org.apache.tools.ant.taskdefs.ExecTask; +import org.apache.tools.ant.types.*; +import org.xml.sax.*; + + +/** + * Websphere deployment tool that augments the ejbjar task. + * + * @author Maneesh Sahu + */ + +public class WebsphereDeploymentTool extends GenericDeploymentTool +{ + + + + public final static String PUBLICID_EJB11 + + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"; + + public final static String PUBLICID_EJB20 + + = "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"; + + protected final static String SCHEMA_DIR = "Schema/"; + + + + protected final static String WAS_EXT = "ibm-ejb-jar-ext.xmi"; + + protected final static String WAS_BND = "ibm-ejb-jar-bnd.xmi"; + + protected final static String WAS_CMP_MAP = "Map.mapxmi"; + + protected final static String WAS_CMP_SCHEMA = "Schema.dbxmi"; + + + + /** + * Instance variable that stores the suffix for the websphere jarfile. + */ + + private String jarSuffix = ".jar"; + + + + /** + * Instance variable that determines whether generic ejb jars are kept. + */ + + + + private boolean keepgenerated = false; + + + + private String additionalArgs = ""; + + + + private boolean keepGeneric = false; + + + + private String compiler = null; + + + + private boolean alwaysRebuild = true; + + + + private boolean ejbdeploy = true; + + + + /** + * Indicates if the old CMP location convention is to be used. + */ + + private boolean newCMP = false; + + + + /** + * The classpath to the websphere classes. + */ + + private Path wasClasspath = null; + + + + /** + * true - Only output error messages, suppress informational messages + */ + + private boolean quiet = true; + + + + /** + * the scratchdir for the ejbdeploy operation + */ + + private String tempdir = "_ejbdeploy_temp"; + + + + /** + * true - Only generate the deployment code, do not run RMIC or Javac + */ + + private boolean codegen; + + + + /** + * The name of the database to create. (For top-down mapping only) + */ + + private String dbName; + + + + /** + * The name of the schema to create. (For top-down mappings only) + */ + + private String dbSchema; + + + + /** + * The DB Vendor name, the EJB is persisted against + */ + + private String dbVendor; + + + + /** + * Instance variable that stores the location of the ejb 1.1 DTD file. + */ + + private String ejb11DTD; + + + + /** + * true - Disable informational messages + */ + + private boolean noinform; + + + + /** + * true - Disable the validation steps + */ + + private boolean novalidate; + + + + /** + * true - Disable warning and informational messages + */ + + private boolean nowarn; + + + + /** + * Additional options for RMIC + */ + + private String rmicOptions; + + + + /** + * true - Enable internal tracing + */ + + private boolean trace; + + + + /** + * true- Use the WebSphere 3.5 compatible mapping rules + */ + + private boolean use35MappingRules; + + + + /** + * sets some additional args to send to ejbdeploy. + * + * @param args The new Args value + */ + + public void setArgs( String args ) + { + + this.additionalArgs = args; + + } + + + + /** + * (true) Only generate the deployment code, do not run RMIC or Javac + * + * @param codegen The new Codegen value + */ + + public void setCodegen( boolean codegen ) + { + + this.codegen = codegen; + + } + + + + /** + * The compiler (switch -compiler) to use + * + * @param compiler The new Compiler value + */ + + public void setCompiler( String compiler ) + { + + this.compiler = compiler; + + } + + + + /** + * Sets the name of the Database to create + * + * @param dbName The new Dbname value + */ + + public void setDbname( String dbName ) + { + + this.dbName = dbName; + + } + + + + /** + * Sets the name of the schema to create + * + * @param dbSchema The new Dbschema value + */ + + public void setDbschema( String dbSchema ) + { + + this.dbSchema = dbSchema; + + } + + + + /** + * Sets the DB Vendor for the Entity Bean mapping + * + * @param dbvendor The new Dbvendor value + */ + + public void setDbvendor( DBVendor dbvendor ) + { + + this.dbVendor = dbvendor.getValue(); + + } + + + + /** + * Setter used to store the location of the Sun's Generic EJB DTD. This can + * be a file on the system or a resource on the classpath. + * + * @param inString the string to use as the DTD location. + */ + + public void setEJBdtd( String inString ) + { + + this.ejb11DTD = inString; + + } + + + + /** + * Decide, wether ejbdeploy should be called or not + * + * @param ejbdeploy + */ + + public void setEjbdeploy( boolean ejbdeploy ) + { + + this.ejbdeploy = ejbdeploy; + + } + + + + /** + * Sets whether -keepgenerated is passed to ejbdeploy (that is, the .java + * source files are kept). + * + * @param inValue either 'true' or 'false' + */ + + public void setKeepgenerated( String inValue ) + { + + this.keepgenerated = Boolean.valueOf( inValue ).booleanValue(); + + } + + + + /** + * Setter used to store the value of keepGeneric + * + * @param inValue a string, either 'true' or 'false'. + */ + + public void setKeepgeneric( boolean inValue ) + { + + this.keepGeneric = inValue; + + } + + + + /** + * Set the value of the newCMP scheme. The old CMP scheme locates the + * websphere CMP descriptor based on the naming convention where the + * websphere CMP file is expected to be named with the bean name as the + * prefix. Under this scheme the name of the CMP descriptor does not match + * the name actually used in the main websphere EJB descriptor. Also, + * descriptors which contain multiple CMP references could not be used. + * + * @param newCMP The new NewCMP value + */ + + public void setNewCMP( boolean newCMP ) + { + + this.newCMP = newCMP; + + } + + + + /** + * (true) Disable informational messages + * + * @param noinfom The new Noinform value + */ + + public void setNoinform( boolean noinfom ) + { + + this.noinform = noinform; + + } + + + + /** + * (true) Disable the validation steps + * + * @param novalidate The new Novalidate value + */ + + public void setNovalidate( boolean novalidate ) + { + + this.novalidate = novalidate; + + } + + + + /** + * (true) Disable warning and informational messages + * + * @param nowarn The new Nowarn value + */ + + public void setNowarn( boolean nowarn ) + { + + this.nowarn = nowarn; + + } + + + + /** + * Set the value of the oldCMP scheme. This is an antonym for newCMP + * + * @param oldCMP The new OldCMP value + */ + + public void setOldCMP( boolean oldCMP ) + { + + this.newCMP = !oldCMP; + + } + + + + /** + * (true) Only output error messages, suppress informational messages + * + * @param quiet The new Quiet value + */ + + public void setQuiet( boolean quiet ) + { + + this.quiet = quiet; + + } + + + + /** + * Set the rebuild flag to false to only update changes in the jar rather + * than rerunning ejbdeploy + * + * @param rebuild The new Rebuild value + */ + + public void setRebuild( boolean rebuild ) + { + + this.alwaysRebuild = rebuild; + + } + + + + + + /** + * Setter used to store the suffix for the generated websphere jar file. + * + * @param inString the string to use as the suffix. + */ + + public void setSuffix( String inString ) + { + + this.jarSuffix = inString; + + } + + + + /** + * Sets the temporary directory for the ejbdeploy task + * + * @param tempdir The new Tempdir value + */ + + public void setTempdir( String tempdir ) + { + + this.tempdir = tempdir; + + } + + + + /** + * (true) Enable internal tracing + * + * @param trace The new Trace value + */ + + public void setTrace( boolean trace ) + { + + this.trace = trace; + + } + + + + /** + * (true) Use the WebSphere 3.5 compatible mapping rules + * + * @param attr The new Use35 value + */ + + public void setUse35( boolean attr ) + { + + use35MappingRules = attr; + + } + + + + public void setWASClasspath( Path wasClasspath ) + { + + this.wasClasspath = wasClasspath; + + } + + + + /** + * Get the classpath to the websphere classpaths + * + * @return Description of the Returned Value + */ + + public Path createWASClasspath() + { + + if( wasClasspath == null ) + { + + wasClasspath = new Path( getTask().getProject() ); + + } + + return wasClasspath.createPath(); + + } + + + + /** + * Called to validate that the tool parameters have been configured. + * + * @exception BuildException Description of Exception + */ + + public void validateConfigured() + throws BuildException + { + + super.validateConfigured(); + + } + + + + /** + * Helper method invoked by isRebuildRequired to get a ClassLoader for a Jar + * File passed to it. + * + * @param classjar java.io.File representing jar file to get classes from. + * @return The ClassLoaderFromJar value + * @exception IOException Description of Exception + */ + + protected ClassLoader getClassLoaderFromJar( File classjar ) + throws IOException + { + + Path lookupPath = new Path( getTask().getProject() ); + + lookupPath.setLocation( classjar ); + + + + Path classpath = getCombinedClasspath(); + + if( classpath != null ) + { + + lookupPath.append( classpath ); + + } + + + + return new AntClassLoader( getTask().getProject(), lookupPath ); + + } + + + + protected DescriptorHandler getDescriptorHandler( File srcDir ) + { + + DescriptorHandler handler = new DescriptorHandler( getTask(), srcDir ); + + // register all the DTDs, both the ones that are known and + + + // any supplied by the user + + handler.registerDTD( PUBLICID_EJB11, ejb11DTD ); + + + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + + } + + + + return handler; + + } + + + + /** + * Gets the options for the EJB Deploy operation + * + * @return String + */ + + protected String getOptions() + { + + // Set the options + + + StringBuffer options = new StringBuffer(); + + if( dbVendor != null ) + { + + options.append( " -dbvendor " ).append( dbVendor ); + + } + + if( dbName != null ) + { + + options.append( " -dbname \"" ).append( dbName ).append( "\"" ); + + } + + + + if( dbSchema != null ) + { + + options.append( " -dbschema \"" ).append( dbSchema ).append( "\"" ); + + } + + + + if( codegen ) + { + + options.append( " -codegen" ); + + } + + + + if( quiet ) + { + + options.append( " -quiet" ); + + } + + + + if( novalidate ) + { + + options.append( " -novalidate" ); + + } + + + + if( nowarn ) + { + + options.append( " -nowarn" ); + + } + + + + if( noinform ) + { + + options.append( " -noinform" ); + + } + + + + if( trace ) + { + + options.append( " -trace" ); + + } + + + + if( use35MappingRules ) + { + + options.append( " -35" ); + + } + + + + if( rmicOptions != null ) + { + + options.append( " -rmic \"" ).append( rmicOptions ).append( "\"" ); + + } + + + + return options.toString(); + + } + + + + protected DescriptorHandler getWebsphereDescriptorHandler( final File srcDir ) + { + + DescriptorHandler handler = + new DescriptorHandler( getTask(), srcDir ) + { + + protected void processElement() { } + + }; + + + + for( Iterator i = getConfig().dtdLocations.iterator(); i.hasNext(); ) + { + + EjbJar.DTDLocation dtdLocation = ( EjbJar.DTDLocation )i.next(); + + handler.registerDTD( dtdLocation.getPublicId(), dtdLocation.getLocation() ); + + } + + return handler; + + } + + + + + + /** + * Helper method to check to see if a websphere EBJ1.1 jar needs to be + * rebuilt using ejbdeploy. Called from writeJar it sees if the "Bean" + * classes are the only thing that needs to be updated and either updates + * the Jar with the Bean classfile or returns true, saying that the whole + * websphere jar needs to be regened with ejbdeploy. This allows faster + * build times for working developers.

              + * + * The way websphere ejbdeploy works is it creates wrappers for the publicly + * defined methods as they are exposed in the remote interface. If the + * actual bean changes without changing the the method signatures then only + * the bean classfile needs to be updated and the rest of the websphere jar + * file can remain the same. If the Interfaces, ie. the method signatures + * change or if the xml deployment dicriptors changed, the whole jar needs + * to be rebuilt with ejbdeploy. This is not strictly true for the xml + * files. If the JNDI name changes then the jar doesnt have to be rebuild, + * but if the resources references change then it does. At this point the + * websphere jar gets rebuilt if the xml files change at all. + * + * @param genericJarFile java.io.File The generic jar file. + * @param websphereJarFile java.io.File The websphere jar file to check to + * see if it needs to be rebuilt. + * @return The RebuildRequired value + */ + + protected boolean isRebuildRequired( File genericJarFile, File websphereJarFile ) + { + + boolean rebuild = false; + + + + JarFile genericJar = null; + + JarFile wasJar = null; + + File newwasJarFile = null; + + JarOutputStream newJarStream = null; + + + + try + { + + log( "Checking if websphere Jar needs to be rebuilt for jar " + websphereJarFile.getName(), + + Project.MSG_VERBOSE ); + + // Only go forward if the generic and the websphere file both exist + + + if( genericJarFile.exists() && genericJarFile.isFile() + + && websphereJarFile.exists() && websphereJarFile.isFile() ) + { + + //open jar files + + + genericJar = new JarFile( genericJarFile ); + + wasJar = new JarFile( websphereJarFile ); + + + + Hashtable genericEntries = new Hashtable(); + + Hashtable wasEntries = new Hashtable(); + + Hashtable replaceEntries = new Hashtable(); + + + + //get the list of generic jar entries + + for( Enumeration e = genericJar.entries(); e.hasMoreElements(); ) + { + + JarEntry je = ( JarEntry )e.nextElement(); + + genericEntries.put( je.getName().replace( '\\', '/' ), je ); + + } + + //get the list of websphere jar entries + + + for( Enumeration e = wasJar.entries(); e.hasMoreElements(); ) + { + + JarEntry je = ( JarEntry )e.nextElement(); + + wasEntries.put( je.getName(), je ); + + } + + + + //Cycle Through generic and make sure its in websphere + + ClassLoader genericLoader = getClassLoaderFromJar( genericJarFile ); + + for( Enumeration e = genericEntries.keys(); e.hasMoreElements(); ) + { + + String filepath = ( String )e.nextElement(); + + if( wasEntries.containsKey( filepath ) ) + {// File name/path match + + + // Check files see if same + + JarEntry genericEntry = ( JarEntry )genericEntries.get( filepath ); + + JarEntry wasEntry = ( JarEntry )wasEntries.get( filepath ); + + if( ( genericEntry.getCrc() != wasEntry.getCrc() ) || // Crc's Match + + ( genericEntry.getSize() != wasEntry.getSize() ) ) + {// Size Match + + + if( genericEntry.getName().endsWith( ".class" ) ) + { + + //File are different see if its an object or an interface + + + String classname = genericEntry.getName().replace( File.separatorChar, '.' ); + + classname = classname.substring( 0, classname.lastIndexOf( ".class" ) ); + + Class genclass = genericLoader.loadClass( classname ); + + if( genclass.isInterface() ) + { + + //Interface changed rebuild jar. + + + log( "Interface " + genclass.getName() + " has changed", Project.MSG_VERBOSE ); + + rebuild = true; + + break; + + } + + else + { + + //Object class Changed update it. + + + replaceEntries.put( filepath, genericEntry ); + + } + + } + + else + { + + // is it the manifest. If so ignore it + + + if( !genericEntry.getName().equals( "META-INF/MANIFEST.MF" ) ) + { + + //File other then class changed rebuild + + + log( "Non class file " + genericEntry.getName() + " has changed", Project.MSG_VERBOSE ); + + rebuild = true; + + } + + break; + + } + + } + + } + + else + {// a file doesnt exist rebuild + + + log( "File " + filepath + " not present in websphere jar", Project.MSG_VERBOSE ); + + rebuild = true; + + break; + + } + + } + + + + if( !rebuild ) + { + + log( "No rebuild needed - updating jar", Project.MSG_VERBOSE ); + + newwasJarFile = new File( websphereJarFile.getAbsolutePath() + ".temp" ); + + if( newwasJarFile.exists() ) + { + + newwasJarFile.delete(); + + } + + + + newJarStream = new JarOutputStream( new FileOutputStream( newwasJarFile ) ); + + newJarStream.setLevel( 0 ); + + + + //Copy files from old websphere jar + + for( Enumeration e = wasEntries.elements(); e.hasMoreElements(); ) + { + + byte[] buffer = new byte[1024]; + + int bytesRead; + + InputStream is; + + JarEntry je = ( JarEntry )e.nextElement(); + + if( je.getCompressedSize() == -1 || + + je.getCompressedSize() == je.getSize() ) + { + + newJarStream.setLevel( 0 ); + + } + + else + { + + newJarStream.setLevel( 9 ); + + } + + + + // Update with changed Bean class + + if( replaceEntries.containsKey( je.getName() ) ) + { + + log( "Updating Bean class from generic Jar " + je.getName(), + + Project.MSG_VERBOSE ); + + // Use the entry from the generic jar + + + je = ( JarEntry )replaceEntries.get( je.getName() ); + + is = genericJar.getInputStream( je ); + + } + + else + {//use fle from original websphere jar + + + is = wasJar.getInputStream( je ); + + } + + newJarStream.putNextEntry( new JarEntry( je.getName() ) ); + + + + while( ( bytesRead = is.read( buffer ) ) != -1 ) + { + + newJarStream.write( buffer, 0, bytesRead ); + + } + + is.close(); + + } + + } + + else + { + + log( "websphere Jar rebuild needed due to changed interface or XML", Project.MSG_VERBOSE ); + + } + + } + + else + { + + rebuild = true; + + } + + } + + catch( ClassNotFoundException cnfe ) + { + + String cnfmsg = "ClassNotFoundException while processing ejb-jar file" + + + ". Details: " + + + cnfe.getMessage(); + + throw new BuildException( cnfmsg, cnfe ); + + } + + catch( IOException ioe ) + { + + String msg = "IOException while processing ejb-jar file " + + + ". Details: " + + + ioe.getMessage(); + + throw new BuildException( msg, ioe ); + + } + + finally + { + + // need to close files and perhaps rename output + + + if( genericJar != null ) + { + + try + { + + genericJar.close(); + + } + + catch( IOException closeException ) + {} + + } + + + + if( wasJar != null ) + { + + try + { + + wasJar.close(); + + } + + catch( IOException closeException ) + {} + + } + + + + if( newJarStream != null ) + { + + try + { + + newJarStream.close(); + + } + + catch( IOException closeException ) + {} + + + + websphereJarFile.delete(); + + newwasJarFile.renameTo( websphereJarFile ); + + if( !websphereJarFile.exists() ) + { + + rebuild = true; + + } + + } + + } + + + + return rebuild; + + } + + + + /** + * Add any vendor specific files which should be included in the EJB Jar. + * + * @param ejbFiles The feature to be added to the VendorFiles attribute + * @param baseName The feature to be added to the VendorFiles attribute + */ + + protected void addVendorFiles( Hashtable ejbFiles, String baseName ) + { + + + + String ddPrefix = ( usingBaseJarName() ? "" : baseName ); + + String dbPrefix = ( dbVendor == null ) ? "" : dbVendor + "-"; + + + + // Get the Extensions document + + File websphereEXT = new File( getConfig().descriptorDir, ddPrefix + WAS_EXT ); + + if( websphereEXT.exists() ) + { + + ejbFiles.put( META_DIR + WAS_EXT, + + websphereEXT ); + + } + else + { + + log( "Unable to locate websphere extensions. It was expected to be in " + + + websphereEXT.getPath(), Project.MSG_VERBOSE ); + + } + + + + File websphereBND = new File( getConfig().descriptorDir, ddPrefix + WAS_BND ); + + if( websphereBND.exists() ) + { + + ejbFiles.put( META_DIR + WAS_BND, + + websphereBND ); + + } + else + { + + log( "Unable to locate websphere bindings. It was expected to be in " + + + websphereBND.getPath(), Project.MSG_VERBOSE ); + + } + + + + if( !newCMP ) + { + + log( "The old method for locating CMP files has been DEPRECATED.", Project.MSG_VERBOSE ); + + log( "Please adjust your websphere descriptor and set newCMP=\"true\" " + + + "to use the new CMP descriptor inclusion mechanism. ", Project.MSG_VERBOSE ); + + } + + else + { + + // We attempt to put in the MAP and Schema files of CMP beans + + + try + { + + // Add the Map file + + + File websphereMAP = new File( getConfig().descriptorDir, + + ddPrefix + dbPrefix + WAS_CMP_MAP ); + + if( websphereMAP.exists() ) + { + + ejbFiles.put( META_DIR + WAS_CMP_MAP, + + websphereMAP ); + + } + else + { + + log( "Unable to locate the websphere Map: " + + + websphereMAP.getPath(), Project.MSG_VERBOSE ); + + } + + File websphereSchema = new File( getConfig().descriptorDir, + + ddPrefix + dbPrefix + WAS_CMP_SCHEMA ); + + if( websphereSchema.exists() ) + { + + ejbFiles.put( META_DIR + SCHEMA_DIR + WAS_CMP_SCHEMA, + + websphereSchema ); + + } + else + { + + log( "Unable to locate the websphere Schema: " + + + websphereSchema.getPath(), Project.MSG_VERBOSE ); + + } + + // Theres nothing else to see here...keep moving sonny + + + } + + catch( Exception e ) + { + + String msg = "Exception while adding Vendor specific files: " + + + e.toString(); + + throw new BuildException( msg, e ); + + } + + } + + } + + + + /** + * Method used to encapsulate the writing of the JAR file. Iterates over the + * filenames/java.io.Files in the Hashtable stored on the instance variable + * ejbFiles. + * + * @param baseName Description of Parameter + * @param jarFile Description of Parameter + * @param files Description of Parameter + * @param publicId Description of Parameter + * @exception BuildException Description of Exception + */ + + protected void writeJar( String baseName, File jarFile, Hashtable files, String publicId ) + + throws BuildException + { + + if( ejbdeploy ) + { + + // create the -generic.jar, if required + + + File genericJarFile = super.getVendorOutputJarFile( baseName ); + + super.writeJar( baseName, genericJarFile, files, publicId ); + + + + // create the output .jar, if required + + if( alwaysRebuild || isRebuildRequired( genericJarFile, jarFile ) ) + { + + buildWebsphereJar( genericJarFile, jarFile ); + + } + + if( !keepGeneric ) + { + + log( "deleting generic jar " + genericJarFile.toString(), + + Project.MSG_VERBOSE ); + + genericJarFile.delete(); + + } + + } + + else + { + + // create the "undeployed" output .jar, if required + + + super.writeJar( baseName, jarFile, files, publicId ); + + } + + /* + * / need to create a generic jar first. + * File genericJarFile = super.getVendorOutputJarFile(baseName); + * super.writeJar(baseName, genericJarFile, files, publicId); + * if (alwaysRebuild || isRebuildRequired(genericJarFile, jarFile)) { + * buildWebsphereJar(genericJarFile, jarFile); + * } + * if (!keepGeneric) { + * log("deleting generic jar " + genericJarFile.toString(), + * Project.MSG_VERBOSE); + * genericJarFile.delete(); + * } + */ + + } + + + + /** + * Get the vendor specific name of the Jar that will be output. The + * modification date of this jar will be checked against the dependent bean + * classes. + * + * @param baseName Description of Parameter + * @return The VendorOutputJarFile value + */ + + File getVendorOutputJarFile( String baseName ) + { + + return new File( getDestDir(), baseName + jarSuffix ); + + }// end getOptions + + + + /** + * Helper method invoked by execute() for each websphere jar to be built. + * Encapsulates the logic of constructing a java task for calling + * websphere.ejbdeploy and executing it. + * + * @param sourceJar java.io.File representing the source (EJB1.1) jarfile. + * @param destJar java.io.File representing the destination, websphere + * jarfile. + */ + + private void buildWebsphereJar( File sourceJar, File destJar ) + { + + try + { + + if( ejbdeploy ) + { + + String args = + + " " + sourceJar.getPath() + + + " " + tempdir + + + " " + destJar.getPath() + + + " " + getOptions(); + + + + if( getCombinedClasspath() != null && getCombinedClasspath().toString().length() > 0 ) + + args += " -cp " + getCombinedClasspath(); + + + + // Why do my ""'s get stripped away??? + + log( "EJB Deploy Options: " + args, Project.MSG_VERBOSE ); + + + + Java javaTask = ( Java )getTask().getProject().createTask( "java" ); + + // Set the JvmArgs + + + javaTask.createJvmarg().setValue( "-Xms64m" ); + + javaTask.createJvmarg().setValue( "-Xmx128m" ); + + + + // Set the Environment variable + + Environment.Variable var = new Environment.Variable(); + + var.setKey( "websphere.lib.dir" ); + + var.setValue( getTask().getProject().getProperty( "websphere.home" ) + "/lib" ); + + javaTask.addSysproperty( var ); + + + + // Set the working directory + + javaTask.setDir( new File( getTask().getProject().getProperty( "websphere.home" ) ) ); + + + + // Set the Java class name + + javaTask.setTaskName( "ejbdeploy" ); + + javaTask.setClassname( "com.ibm.etools.ejbdeploy.EJBDeploy" ); + + + + Commandline.Argument arguments = javaTask.createArg(); + + arguments.setLine( args ); + + + + Path classpath = wasClasspath; + + if( classpath == null ) + { + + classpath = getCombinedClasspath(); + + } + + + + if( classpath != null ) + { + + javaTask.setClasspath( classpath ); + + javaTask.setFork( true ); + + } + + else + { + + javaTask.setFork( true ); + + } + + + + log( "Calling websphere.ejbdeploy for " + sourceJar.toString(), + + Project.MSG_VERBOSE ); + + + + javaTask.execute(); + + } + + } + + catch( Exception e ) + { + + // Have to catch this because of the semantics of calling main() + + + String msg = "Exception while calling ejbdeploy. Details: " + e.toString(); + + throw new BuildException( msg, e ); + + } + + } + + + /** + * Enumerated attribute with the values for the database vendor types + * + * @author RT + */ + + public static class DBVendor extends EnumeratedAttribute + { + + public String[] getValues() + { + + return new String[]{ + + "SQL92", "SQL99", "DB2UDBWIN_V71", "DB2UDBOS390_V6", "DB2UDBAS400_V4R5", + + "ORACLE_V8", "INFORMIX_V92", "SYBASE_V1192", "MSSQLSERVER_V7", "MYSQL_V323" + + }; + + } + + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java new file mode 100644 index 000000000..ddd1d9e6e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java @@ -0,0 +1,645 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.i18n; +import java.io.*; +import java.util.*; +import org.apache.tools.ant.*; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.*; +import org.apache.tools.ant.util.*; + +/** + * Translates text embedded in files using Resource Bundle files. + * + * @author Magesh Umasankar + */ +public class Translate extends MatchingTask +{ + /** + * Vector to hold source file sets. + */ + private Vector filesets = new Vector(); + /** + * Holds key value pairs loaded from resource bundle file + */ + private Hashtable resourceMap = new Hashtable(); + /** + * Used to resolve file names. + */ + private FileUtils fileUtils = FileUtils.newFileUtils(); + /** + * Last Modified Timestamp of resource bundle file being used. + */ + private long[] bundleLastModified = new long[7]; + /** + * Has at least one file from the bundle been loaded? + */ + private boolean loaded = false; + + /** + * Family name of resource bundle + */ + private String bundle; + /** + * Locale specific country of the resource bundle + */ + private String bundleCountry; + /** + * Resource Bundle file encoding scheme, defaults to srcEncoding + */ + private String bundleEncoding; + /** + * Locale specific language of the resource bundle + */ + private String bundleLanguage; + /** + * Locale specific variant of the resource bundle + */ + private String bundleVariant; + /** + * Destination file encoding scheme + */ + private String destEncoding; + /** + * Last Modified Timestamp of destination file being used. + */ + private long destLastModified; + /** + * Ending token to identify keys + */ + private String endToken; + /** + * Create new destination file? Defaults to false. + */ + private boolean forceOverwrite; + /** + * Generated locale based on user attributes + */ + private Locale locale; + /** + * Source file encoding scheme + */ + private String srcEncoding; + /** + * Last Modified Timestamp of source file being used. + */ + private long srcLastModified; + /** + * Starting token to identify keys + */ + private String startToken; + /** + * Destination directory + */ + private File toDir; + + /** + * Sets Family name of resource bundle + * + * @param bundle The new Bundle value + */ + public void setBundle( String bundle ) + { + this.bundle = bundle; + } + + /** + * Sets locale specific country of resource bundle + * + * @param bundleCountry The new BundleCountry value + */ + public void setBundleCountry( String bundleCountry ) + { + this.bundleCountry = bundleCountry; + } + + /** + * Sets Resource Bundle file encoding scheme + * + * @param bundleEncoding The new BundleEncoding value + */ + public void setBundleEncoding( String bundleEncoding ) + { + this.bundleEncoding = bundleEncoding; + } + + /** + * Sets locale specific language of resource bundle + * + * @param bundleLanguage The new BundleLanguage value + */ + public void setBundleLanguage( String bundleLanguage ) + { + this.bundleLanguage = bundleLanguage; + } + + /** + * Sets locale specific variant of resource bundle + * + * @param bundleVariant The new BundleVariant value + */ + public void setBundleVariant( String bundleVariant ) + { + this.bundleVariant = bundleVariant; + } + + /** + * Sets destination file encoding scheme. Defaults to source file encoding + * + * @param destEncoding The new DestEncoding value + */ + public void setDestEncoding( String destEncoding ) + { + this.destEncoding = destEncoding; + } + + /** + * Sets ending token to identify keys + * + * @param endToken The new EndToken value + */ + public void setEndToken( String endToken ) + { + this.endToken = endToken; + } + + /** + * Overwrite existing file irrespective of whether it is newer than the + * source file as well as the resource bundle file? Defaults to false. + * + * @param forceOverwrite The new ForceOverwrite value + */ + public void setForceOverwrite( boolean forceOverwrite ) + { + this.forceOverwrite = forceOverwrite; + } + + /** + * Sets source file encoding scheme + * + * @param srcEncoding The new SrcEncoding value + */ + public void setSrcEncoding( String srcEncoding ) + { + this.srcEncoding = srcEncoding; + } + + /** + * Sets starting token to identify keys + * + * @param startToken The new StartToken value + */ + public void setStartToken( String startToken ) + { + this.startToken = startToken; + } + + /** + * Sets Destination directory + * + * @param toDir The new ToDir value + */ + public void setToDir( File toDir ) + { + this.toDir = toDir; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Check attributes values, load resource map and translate + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( bundle == null ) + { + throw new BuildException( "The bundle attribute must be set.", + location ); + } + + if( startToken == null ) + { + throw new BuildException( "The starttoken attribute must be set.", + location ); + } + + if( startToken.length() != 1 ) + { + throw new BuildException( + "The starttoken attribute must be a single character.", + location ); + } + + if( endToken == null ) + { + throw new BuildException( "The endtoken attribute must be set.", + location ); + } + + if( endToken.length() != 1 ) + { + throw new BuildException( + "The endtoken attribute must be a single character.", + location ); + } + + if( bundleLanguage == null ) + { + Locale l = Locale.getDefault(); + bundleLanguage = l.getLanguage(); + } + + if( bundleCountry == null ) + { + bundleCountry = Locale.getDefault().getCountry(); + } + + locale = new Locale( bundleLanguage, bundleCountry ); + + if( bundleVariant == null ) + { + Locale l = new Locale( bundleLanguage, bundleCountry ); + bundleVariant = l.getVariant(); + } + + if( toDir == null ) + { + throw new BuildException( "The todir attribute must be set.", + location ); + } + + if( !toDir.exists() ) + { + toDir.mkdirs(); + } + else + { + if( toDir.isFile() ) + { + throw new BuildException( toDir + " is not a directory" ); + } + } + + if( srcEncoding == null ) + { + srcEncoding = System.getProperty( "file.encoding" ); + } + + if( destEncoding == null ) + { + destEncoding = srcEncoding; + } + + if( bundleEncoding == null ) + { + bundleEncoding = srcEncoding; + } + + loadResourceMaps(); + + translate(); + } + + /** + * Load resourceMap with key value pairs. Values of existing keys are not + * overwritten. Bundle's encoding scheme is used. + * + * @param ins Description of Parameter + * @exception BuildException Description of Exception + */ + private void loadResourceMap( FileInputStream ins ) + throws BuildException + { + try + { + BufferedReader in = null; + InputStreamReader isr = new InputStreamReader( ins, bundleEncoding ); + in = new BufferedReader( isr ); + String line = null; + while( ( line = in.readLine() ) != null ) + { + //So long as the line isn't empty and isn't a comment... + if( line.trim().length() > 1 && + ( '#' != line.charAt( 0 ) || '!' != line.charAt( 0 ) ) ) + { + //Legal Key-Value separators are :, = and white space. + int sepIndex = line.indexOf( '=' ); + if( -1 == sepIndex ) + { + sepIndex = line.indexOf( ':' ); + } + if( -1 == sepIndex ) + { + for( int k = 0; k < line.length(); k++ ) + { + if( Character.isSpaceChar( line.charAt( k ) ) ) + { + sepIndex = k; + break; + } + } + } + //Only if we do have a key is there going to be a value + if( -1 != sepIndex ) + { + String key = line.substring( 0, sepIndex ).trim(); + String value = line.substring( sepIndex + 1 ).trim(); + //Handle line continuations, if any + while( value.endsWith( "\\" ) ) + { + value = value.substring( 0, value.length() - 1 ); + if( ( line = in.readLine() ) != null ) + { + value = value + line.trim(); + } + else + { + break; + } + } + if( key.length() > 0 ) + { + //Has key already been loaded into resourceMap? + if( resourceMap.get( key ) == null ) + { + resourceMap.put( key, value ); + } + } + } + } + } + if( in != null ) + { + in.close(); + } + } + catch( IOException ioe ) + { + throw new BuildException( ioe.getMessage(), location ); + } + } + + /** + * Load resource maps based on resource bundle encoding scheme. The resource + * bundle lookup searches for resource files with various suffixes on the + * basis of (1) the desired locale and (2) the default locale + * (basebundlename), in the following order from lower-level (more specific) + * to parent-level (less specific): basebundlename + "_" + language1 + "_" + + * country1 + "_" + variant1 basebundlename + "_" + language1 + "_" + + * country1 basebundlename + "_" + language1 basebundlename basebundlename + + * "_" + language2 + "_" + country2 + "_" + variant2 basebundlename + "_" + + * language2 + "_" + country2 basebundlename + "_" + language2 To the + * generated name, a ".properties" string is appeneded and once this file is + * located, it is treated just like a properties file but with bundle + * encoding also considered while loading. + * + * @exception BuildException Description of Exception + */ + private void loadResourceMaps() + throws BuildException + { + Locale locale = new Locale( bundleLanguage, + bundleCountry, + bundleVariant ); + String language = locale.getLanguage().length() > 0 ? + "_" + locale.getLanguage() : + ""; + String country = locale.getCountry().length() > 0 ? + "_" + locale.getCountry() : + ""; + String variant = locale.getVariant().length() > 0 ? + "_" + locale.getVariant() : + ""; + String bundleFile = bundle + language + country + variant; + processBundle( bundleFile, 0, false ); + + bundleFile = bundle + language + country; + processBundle( bundleFile, 1, false ); + + bundleFile = bundle + language; + processBundle( bundleFile, 2, false ); + + bundleFile = bundle; + processBundle( bundleFile, 3, false ); + + //Load default locale bundle files + //using default file encoding scheme. + locale = Locale.getDefault(); + + language = locale.getLanguage().length() > 0 ? + "_" + locale.getLanguage() : + ""; + country = locale.getCountry().length() > 0 ? + "_" + locale.getCountry() : + ""; + variant = locale.getVariant().length() > 0 ? + "_" + locale.getVariant() : + ""; + bundleEncoding = System.getProperty( "file.encoding" ); + + bundleFile = bundle + language + country + variant; + processBundle( bundleFile, 4, false ); + + bundleFile = bundle + language + country; + processBundle( bundleFile, 5, false ); + + bundleFile = bundle + language; + processBundle( bundleFile, 6, true ); + } + + /** + * Process each file that makes up this bundle. + * + * @param bundleFile Description of Parameter + * @param i Description of Parameter + * @param checkLoaded Description of Parameter + * @exception BuildException Description of Exception + */ + private void processBundle( String bundleFile, int i, + boolean checkLoaded ) + throws BuildException + { + bundleFile += ".properties"; + FileInputStream ins = null; + try + { + ins = new FileInputStream( bundleFile ); + loaded = true; + bundleLastModified[i] = new File( bundleFile ).lastModified(); + log( "Using " + bundleFile, Project.MSG_DEBUG ); + loadResourceMap( ins ); + } + catch( IOException ioe ) + { + log( bundleFile + " not found.", Project.MSG_DEBUG ); + //if all resource files associated with this bundle + //have been scanned for and still not able to + //find a single resrouce file, throw exception + if( !loaded && checkLoaded ) + { + throw new BuildException( ioe.getMessage(), location ); + } + } + } + + /** + * Reads source file line by line using the source encoding and searches for + * keys that are sandwiched between the startToken and endToken. The values + * for these keys are looked up from the hashtable and substituted. If the + * hashtable doesn't contain the key, they key itself is used as the value. + * Detination files and directories are created as needed. The destination + * file is overwritten only if the forceoverwritten attribute is set to true + * if the source file or any associated bundle resource file is newer than + * the destination file. + * + * @exception BuildException Description of Exception + */ + private void translate() + throws BuildException + { + for( int i = 0; i < filesets.size(); i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + String[] srcFiles = ds.getIncludedFiles(); + for( int j = 0; j < srcFiles.length; j++ ) + { + try + { + File dest = fileUtils.resolveFile( toDir, srcFiles[j] ); + //Make sure parent dirs exist, else, create them. + try + { + File destDir = new File( dest.getParent() ); + if( !destDir.exists() ) + { + destDir.mkdirs(); + } + } + catch( Exception e ) + { + log( "Exception occured while trying to check/create " + + " parent directory. " + e.getMessage(), + Project.MSG_DEBUG ); + } + destLastModified = dest.lastModified(); + srcLastModified = new File( srcFiles[i] ).lastModified(); + //Check to see if dest file has to be recreated + if( forceOverwrite + || destLastModified < srcLastModified + || destLastModified < bundleLastModified[0] + || destLastModified < bundleLastModified[1] + || destLastModified < bundleLastModified[2] + || destLastModified < bundleLastModified[3] + || destLastModified < bundleLastModified[4] + || destLastModified < bundleLastModified[5] + || destLastModified < bundleLastModified[6] ) + { + log( "Processing " + srcFiles[j], + Project.MSG_DEBUG ); + FileOutputStream fos = new FileOutputStream( dest ); + BufferedWriter out = new BufferedWriter( + new OutputStreamWriter( fos, + destEncoding ) ); + FileInputStream fis = new FileInputStream( srcFiles[j] ); + BufferedReader in = new BufferedReader( + new InputStreamReader( fis, + srcEncoding ) ); + String line; + while( ( line = in.readLine() ) != null ) + { + StringBuffer newline = new StringBuffer( line ); + int startIndex = -1; + int endIndex = -1; + outer : + while( true ) + { + startIndex = line.indexOf( startToken, endIndex + 1 ); + if( startIndex < 0 || + startIndex + 1 >= line.length() ) + { + break; + } + endIndex = line.indexOf( endToken, startIndex + 1 ); + if( endIndex < 0 ) + { + break; + } + String matches = line.substring( startIndex + 1, + endIndex ); + //If there is a white space or = or :, then + //it isn't to be treated as a valid key. + for( int k = 0; k < matches.length(); k++ ) + { + char c = matches.charAt( k ); + if( c == ':' || + c == '=' || + Character.isSpaceChar( c ) ) + { + endIndex = endIndex - 1; + continue outer; + } + } + String replace = null; + replace = ( String )resourceMap.get( matches ); + //If the key hasn't been loaded into resourceMap, + //use the key itself as the value also. + if( replace == null ) + { + log( "Warning: The key: " + matches + + " hasn't been defined.", + Project.MSG_DEBUG ); + replace = matches; + } + line = line.substring( 0, startIndex ) + + replace + + line.substring( endIndex + 1 ); + endIndex = startIndex + replace.length() + 1; + if( endIndex + 1 >= line.length() ) + { + break; + } + } + out.write( line ); + out.newLine(); + } + if( in != null ) + { + in.close(); + } + if( out != null ) + { + out.close(); + } + } + else + { + log( "Skipping " + srcFiles[j] + + " as destination file is up to date", + Project.MSG_VERBOSE ); + } + } + catch( IOException ioe ) + { + throw new BuildException( ioe.getMessage(), location ); + } + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java new file mode 100644 index 000000000..1526cd393 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntTool.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import com.ibm.ivj.util.base.Project; +import com.ibm.ivj.util.base.ToolData; +import org.apache.tools.ant.BuildException; + + +/** + * This class is the equivalent to org.apache.tools.ant.Main for the VAJ tool + * environment. It's main is called when the user selects Tools->Ant Build from + * the VAJ project menu. Additionally this class provides methods to save build + * info for a project in the repository and load it from the repository + * + * @author RT + * @author: Wolf Siberski + */ +public class VAJAntTool +{ + private final static String TOOL_DATA_KEY = "AntTool"; + + + /** + * Loads the BuildInfo for the specified VAJ project from the tool data for + * this project. If there is no build info stored for that project, a new + * default BuildInfo is returned + * + * @param projectName String project name + * @return BuildInfo buildInfo build info for the specified project + */ + public static VAJBuildInfo loadBuildData( String projectName ) + { + VAJBuildInfo result = null; + try + { + Project project = + VAJLocalUtil.getWorkspace().loadedProjectNamed( projectName ); + if( project.testToolRepositoryData( TOOL_DATA_KEY ) ) + { + ToolData td = project.getToolRepositoryData( TOOL_DATA_KEY ); + String data = ( String )td.getData(); + result = VAJBuildInfo.parse( data ); + } + else + { + result = new VAJBuildInfo(); + } + result.setVAJProjectName( projectName ); + } + catch( Throwable t ) + { + throw new BuildException( "BuildInfo for Project " + + projectName + " could not be loaded" + t ); + } + return result; + } + + + /** + * Starts the application. + * + * @param args an array of command-line arguments. VAJ puts the VAJ project + * name into args[1] when starting the tool from the project context + * menu + */ + public static void main( java.lang.String[] args ) + { + try + { + VAJBuildInfo info; + if( args.length >= 2 && args[1] instanceof String ) + { + String projectName = ( String )args[1]; + info = loadBuildData( projectName ); + } + else + { + info = new VAJBuildInfo(); + } + + VAJAntToolGUI mainFrame = new VAJAntToolGUI( info ); + mainFrame.show(); + } + catch( Throwable t ) + { + // if all error handling fails, output at least + // something on the console + t.printStackTrace(); + } + } + + + /** + * Saves the BuildInfo for a project in the VAJ repository. + * + * @param info BuildInfo build info to save + */ + public static void saveBuildData( VAJBuildInfo info ) + { + String data = info.asDataString(); + try + { + ToolData td = new ToolData( TOOL_DATA_KEY, data ); + VAJLocalUtil.getWorkspace().loadedProjectNamed( + info.getVAJProjectName() ).setToolRepositoryData( td ); + } + catch( Throwable t ) + { + throw new BuildException( "BuildInfo for Project " + + info.getVAJProjectName() + " could not be saved", t ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java new file mode 100644 index 000000000..7aa8916e0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJAntToolGUI.java @@ -0,0 +1,1803 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.awt.BorderLayout; +import java.awt.Button; +import java.awt.Choice; +import java.awt.Dialog; +import java.awt.FileDialog; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Label; +import java.awt.List; +import java.awt.Menu; +import java.awt.MenuBar; +import java.awt.MenuItem; +import java.awt.Panel; +import java.awt.SystemColor; +import java.awt.TextArea; +import java.awt.TextField; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.TextEvent; +import java.awt.event.TextListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.beans.PropertyChangeListener; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Vector; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.StringUtils; + +/** + * This is a simple grafical user interface to provide the information needed by + * ANT and to start the build-process within IBM VisualAge for Java.

              + * + * I was using AWT to make it independent from the JDK-version. Please don't ask + * me for a Swing-version:I am very familiar with Swing and I really think that + * it's not necessary for such a simple gui!

              + * + * It is completely developed in VAJ using the visual composition editor. About + * 90% of the code is generated by VAJ, but in fact I did a lot of + * code-beautification ;-).

              + * + * + * + * @author RT + * @version 1.0 h + * @author: Christoph Wilhelms, TUI Infotec GmbH + */ +public class VAJAntToolGUI extends Frame +{ + private final static String lineSeparator = "\r\n"; + /** + * Members + */ + private VAJBuildLogger logger = new VAJBuildLogger(); + private PrivateEventHandler iEventHandler = new PrivateEventHandler(); + + /** + * Members of the main-window + */ + // main model + private VAJBuildInfo iBuildInfo = null; + // Menue + private MenuBar iAntMakeMenuBar = null; + private Menu iFileMenu = null; + private MenuItem iSaveMenuItem = null; + private MenuItem iMenuSeparator = null; + private MenuItem iShowLogMenuItem = null; + private Menu iHelpMenu = null; + private MenuItem iAboutMenuItem = null; + // Container + private Panel iContentsPane = null; + private Panel iOptionenPanel = null; + private Panel iCommandButtonPanel = null; + private FlowLayout iCommandButtonPanelFlowLayout = null; + // Project name + private Label iProjectLabel = null; + private Label iProjectText = null; + // XML-file + private Label iBuildFileLabel = null; + private TextField iBuildFileTextField = null; + private boolean iConnPtoP2Aligning = false; + private Button iBrowseButton = null; + private FileDialog iFileDialog = null; + // Options + private Choice iMessageOutputLevelChoice = null; + private Label iMessageOutputLevelLabel = null; + private Label iTargetLabel = null; + private List iTargetList = null; + // Command-buttons + private Button iBuildButton = null; + private Button iReloadButton = null; + private Button iCloseButton = null; + /** + * log-Window + */ + // Container + private Frame iMessageFrame = null; + private Panel iMessageCommandPanel = null; + private Panel iMessageContentPanel = null; + // Components + private TextArea iMessageTextArea = null; + private Button iMessageOkButton = null; + private Button iMessageClearLogButton = null; + /** + * About-dialog + */ + // Container + private Dialog iAboutDialog = null; + private Panel iAboutDialogContentPanel = null; + private Panel iAboutInfoPanel = null; + private Panel iAboutCommandPanel = null; + // Labels + private Label iAboutTitleLabel = null; + private Label iAboutDevLabel = null; + private Label iAboutContactLabel = null; + // Buttons + private Button iAboutOkButton = null; + + private Button iStopButton = null; + + /** + * AntMake constructor called by VAJAntTool integration. + * + * @param newBuildInfo Description of Parameter + */ + + public VAJAntToolGUI( VAJBuildInfo newBuildInfo ) + { + super(); + setBuildInfo( newBuildInfo ); + initialize(); + } + + /** + * AntMake default-constructor. + */ + private VAJAntToolGUI() + { + super(); + initialize(); + } + + /** + * This method is used to center dialogs. + * + * @param dialog Description of Parameter + */ + public static void centerDialog( Dialog dialog ) + { + dialog.setLocation( ( Toolkit.getDefaultToolkit().getScreenSize().width / 2 ) - ( dialog.getSize().width / 2 ), ( java.awt.Toolkit.getDefaultToolkit().getScreenSize().height / 2 ) - ( dialog.getSize().height / 2 ) ); + } + + /** + * Copied from DefaultLogger to provide the same time-format. + * + * @param millis Description of Parameter + * @return Description of the Returned Value + */ + public static String formatTime( long millis ) + { + long seconds = millis / 1000; + long minutes = seconds / 60; + + if( minutes > 0 ) + { + return Long.toString( minutes ) + " minute" + + ( minutes == 1 ? " " : "s " ) + + Long.toString( seconds % 60 ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + else + { + return Long.toString( seconds ) + " second" + + ( seconds % 60 == 1 ? "" : "s" ); + } + } + + /** + * Set the BuildInfo to a new value. + * + * @param newValue org.apache.tools.ant.taskdefs.optional.vaj.VAJBuildInfo + */ + private void setBuildInfo( VAJBuildInfo newValue ) + { + if( iBuildInfo != newValue ) + { + try + { + /* + * Stop listening for events from the current object + */ + if( iBuildInfo != null ) + { + iBuildInfo.removePropertyChangeListener( iEventHandler ); + } + iBuildInfo = newValue; + + /* + * Listen for events from the new object + */ + if( iBuildInfo != null ) + { + iBuildInfo.addPropertyChangeListener( iEventHandler ); + } + connectProjectNameToLabel(); + connectBuildFileNameToTextField(); + + // Select the log-level given by BuildInfo + getMessageOutputLevelChoice().select( iBuildInfo.getOutputMessageLevel() ); + fillList(); + // BuildInfo can conly be saved to a VAJ project if tool API is called via the projects context-menu + if( ( iBuildInfo.getVAJProjectName() == null ) || ( iBuildInfo.getVAJProjectName().equals( "" ) ) ) + { + getSaveMenuItem().setEnabled( false ); + } + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + } + + /** + * Return the AboutCommandPanel property value. + * + * @return java.awt.Panel + */ + private Panel getAboutCommandPanel() + { + if( iAboutCommandPanel == null ) + { + try + { + iAboutCommandPanel = new Panel(); + iAboutCommandPanel.setName( "AboutCommandPanel" ); + iAboutCommandPanel.setLayout( new java.awt.FlowLayout() ); + getAboutCommandPanel().add( getAboutOkButton(), getAboutOkButton().getName() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutCommandPanel; + } + + /** + * Return the AboutContactLabel property value. + * + * @return java.awt.Label + */ + private Label getAboutContactLabel() + { + if( iAboutContactLabel == null ) + { + try + { + iAboutContactLabel = new Label(); + iAboutContactLabel.setName( "AboutContactLabel" ); + iAboutContactLabel.setAlignment( java.awt.Label.CENTER ); + iAboutContactLabel.setText( "contact: wolf.siberski@tui.de or christoph.wilhelms@tui.de" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutContactLabel; + } + + /** + * Return the AboutDevLabel property value. + * + * @return java.awt.Label + */ + private Label getAboutDevLabel() + { + if( iAboutDevLabel == null ) + { + try + { + iAboutDevLabel = new Label(); + iAboutDevLabel.setName( "AboutDevLabel" ); + iAboutDevLabel.setAlignment( java.awt.Label.CENTER ); + iAboutDevLabel.setText( "developed by Wolf Siberski & Christoph Wilhelms" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutDevLabel; + } + + /** + * Return the AboutDialog property value. + * + * @return java.awt.Dialog + */ + private Dialog getAboutDialog() + { + if( iAboutDialog == null ) + { + try + { + iAboutDialog = new Dialog( this ); + iAboutDialog.setName( "AboutDialog" ); + iAboutDialog.setResizable( false ); + iAboutDialog.setLayout( new java.awt.BorderLayout() ); + iAboutDialog.setBounds( 550, 14, 383, 142 ); + iAboutDialog.setModal( true ); + iAboutDialog.setTitle( "About..." ); + getAboutDialog().add( getAboutDialogContentPanel(), "Center" ); + iAboutDialog.pack(); + centerDialog( iAboutDialog ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutDialog; + } + + /** + * Return the AboutDialogContentPanel property value. + * + * @return java.awt.Panel + */ + private Panel getAboutDialogContentPanel() + { + if( iAboutDialogContentPanel == null ) + { + try + { + iAboutDialogContentPanel = new Panel(); + iAboutDialogContentPanel.setName( "AboutDialogContentPanel" ); + iAboutDialogContentPanel.setLayout( new java.awt.BorderLayout() ); + getAboutDialogContentPanel().add( getAboutCommandPanel(), "South" ); + getAboutDialogContentPanel().add( getAboutInfoPanel(), "Center" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutDialogContentPanel; + } + + /** + * Return the AboutInfoPanel property value. + * + * @return java.awt.Panel + */ + private Panel getAboutInfoPanel() + { + if( iAboutInfoPanel == null ) + { + try + { + iAboutInfoPanel = new Panel(); + iAboutInfoPanel.setName( "AboutInfoPanel" ); + iAboutInfoPanel.setLayout( new GridBagLayout() ); + + GridBagConstraints constraintsAboutTitleLabel = new GridBagConstraints(); + constraintsAboutTitleLabel.gridx = 0; + constraintsAboutTitleLabel.gridy = 0; + constraintsAboutTitleLabel.fill = GridBagConstraints.HORIZONTAL; + constraintsAboutTitleLabel.weightx = 1.0; + constraintsAboutTitleLabel.weighty = 1.0; + constraintsAboutTitleLabel.insets = new Insets( 4, 0, 4, 0 ); + getAboutInfoPanel().add( getAboutTitleLabel(), constraintsAboutTitleLabel ); + + GridBagConstraints constraintsAboutDevLabel = new GridBagConstraints(); + constraintsAboutDevLabel.gridx = 0; + constraintsAboutDevLabel.gridy = 1; + constraintsAboutDevLabel.fill = GridBagConstraints.HORIZONTAL; + constraintsAboutDevLabel.weightx = 1.0; + constraintsAboutDevLabel.insets = new Insets( 4, 0, 0, 0 ); + getAboutInfoPanel().add( getAboutDevLabel(), constraintsAboutDevLabel ); + + GridBagConstraints constraintsAboutContactLabel = new GridBagConstraints(); + constraintsAboutContactLabel.gridx = 0; + constraintsAboutContactLabel.gridy = 2; + constraintsAboutContactLabel.fill = GridBagConstraints.HORIZONTAL; + constraintsAboutContactLabel.weightx = 1.0; + constraintsAboutContactLabel.insets = new Insets( 2, 0, 4, 0 ); + getAboutInfoPanel().add( getAboutContactLabel(), constraintsAboutContactLabel ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutInfoPanel; + } + + /** + * Return the AboutMenuItem property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getAboutMenuItem() + { + if( iAboutMenuItem == null ) + { + try + { + iAboutMenuItem = new MenuItem(); + iAboutMenuItem.setLabel( "About..." ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutMenuItem; + } + + /** + * Return the AboutOkButton property value. + * + * @return java.awt.Button + */ + private Button getAboutOkButton() + { + if( iAboutOkButton == null ) + { + try + { + iAboutOkButton = new Button(); + iAboutOkButton.setName( "AboutOkButton" ); + iAboutOkButton.setLabel( "OK" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutOkButton; + } + + /** + * Return the AboutTitleLabel property value. + * + * @return java.awt.Label + */ + private Label getAboutTitleLabel() + { + if( iAboutTitleLabel == null ) + { + try + { + iAboutTitleLabel = new Label(); + iAboutTitleLabel.setName( "AboutTitleLabel" ); + iAboutTitleLabel.setFont( new Font( "Arial", 1, 12 ) ); + iAboutTitleLabel.setAlignment( Label.CENTER ); + iAboutTitleLabel.setText( "Ant VisualAge for Java Tool-Integration" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAboutTitleLabel; + } + + /** + * Return the AntMakeMenuBar property value. + * + * @return java.awt.MenuBar + */ + private MenuBar getAntMakeMenuBar() + { + if( iAntMakeMenuBar == null ) + { + try + { + iAntMakeMenuBar = new MenuBar(); + iAntMakeMenuBar.add( getFileMenu() ); + iAntMakeMenuBar.add( getHelpMenu() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iAntMakeMenuBar; + } + + /** + * Return the BrowseButton property value. + * + * @return Button + */ + private Button getBrowseButton() + { + if( iBrowseButton == null ) + { + try + { + iBrowseButton = new Button(); + iBrowseButton.setName( "BrowseButton" ); + iBrowseButton.setLabel( "..." ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBrowseButton; + } + + /** + * Return the BuildButton property value. + * + * @return java.awt.Button + */ + private Button getBuildButton() + { + if( iBuildButton == null ) + { + try + { + iBuildButton = new Button(); + iBuildButton.setName( "BuildButton" ); + iBuildButton.setLabel( "Execute" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBuildButton; + } + + /** + * Return the BuildFileLabel property value. + * + * @return java.awt.Label + */ + private Label getBuildFileLabel() + { + if( iBuildFileLabel == null ) + { + try + { + iBuildFileLabel = new Label(); + iBuildFileLabel.setName( "BuildFileLabel" ); + iBuildFileLabel.setText( "Ant-Buildfile:" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBuildFileLabel; + } + + /** + * Return the BuildFileTextField property value. + * + * @return java.awt.TextField + */ + private TextField getBuildFileTextField() + { + if( iBuildFileTextField == null ) + { + try + { + iBuildFileTextField = new TextField(); + iBuildFileTextField.setName( "BuildFileTextField" ); + iBuildFileTextField.setBackground( SystemColor.textHighlightText ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iBuildFileTextField; + } + + /** + * Return the BuildInfo property value. + * + * @return org.apache.tools.ant.taskdefs.optional.ide.VAJBuildInfo + */ + private VAJBuildInfo getBuildInfo() + { + return iBuildInfo; + } + + /** + * Return the CloseButton property value. + * + * @return java.awt.Button + */ + private Button getCloseButton() + { + if( iCloseButton == null ) + { + try + { + iCloseButton = new Button(); + iCloseButton.setName( "CloseButton" ); + iCloseButton.setLabel( "Close" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iCloseButton; + } + + /** + * Return the CommandButtonPanel property value. + * + * @return java.awt.Panel + */ + private Panel getCommandButtonPanel() + { + if( iCommandButtonPanel == null ) + { + try + { + iCommandButtonPanel = new Panel(); + iCommandButtonPanel.setName( "CommandButtonPanel" ); + iCommandButtonPanel.setLayout( getCommandButtonPanelFlowLayout() ); + iCommandButtonPanel.setBackground( SystemColor.control ); + iCommandButtonPanel.add( getReloadButton() ); + iCommandButtonPanel.add( getBuildButton() ); + iCommandButtonPanel.add( getStopButton() ); + iCommandButtonPanel.add( getCloseButton() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iCommandButtonPanel; + } + + /** + * Return the CommandButtonPanelFlowLayout property value. + * + * @return java.awt.FlowLayout + */ + private FlowLayout getCommandButtonPanelFlowLayout() + { + FlowLayout iCommandButtonPanelFlowLayout = null; + try + { + /* + * Create part + */ + iCommandButtonPanelFlowLayout = new FlowLayout(); + iCommandButtonPanelFlowLayout.setAlignment( FlowLayout.RIGHT ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + ; + return iCommandButtonPanelFlowLayout; + } + + /** + * Return the ContentsPane property value. + * + * @return java.awt.Panel + */ + private Panel getContentsPane() + { + if( iContentsPane == null ) + { + try + { + iContentsPane = new Panel(); + iContentsPane.setName( "ContentsPane" ); + iContentsPane.setLayout( new BorderLayout() ); + getContentsPane().add( getCommandButtonPanel(), "South" ); + getContentsPane().add( getOptionenPanel(), "Center" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iContentsPane; + } + + /** + * Return the FileDialog property value. + * + * @return java.awt.FileDialog + */ + private FileDialog getFileDialog() + { + if( iFileDialog == null ) + { + try + { + iFileDialog = new FileDialog( this ); + iFileDialog.setName( "FileDialog" ); + iFileDialog.setLayout( null ); + centerDialog( iFileDialog ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iFileDialog; + } + + /** + * Return the FileMenu property value. + * + * @return java.awt.Menu + */ + private Menu getFileMenu() + { + if( iFileMenu == null ) + { + try + { + iFileMenu = new Menu(); + iFileMenu.setLabel( "File" ); + iFileMenu.add( getSaveMenuItem() ); + iFileMenu.add( getMenuSeparator() ); + iFileMenu.add( getShowLogMenuItem() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iFileMenu; + } + + /** + * Return the HelpMenu property value. + * + * @return java.awt.Menu + */ + private Menu getHelpMenu() + { + if( iHelpMenu == null ) + { + try + { + iHelpMenu = new Menu(); + iHelpMenu.setLabel( "Help" ); + iHelpMenu.add( getAboutMenuItem() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iHelpMenu; + } + + /** + * Return the MenuSeparator1 property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getMenuSeparator() + { + if( iMenuSeparator == null ) + { + try + { + iMenuSeparator = new MenuItem(); + iMenuSeparator.setLabel( "-" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMenuSeparator; + } + + /** + * Return the MessageClearLogButton property value. + * + * @return java.awt.Button + */ + private Button getMessageClearLogButton() + { + if( iMessageClearLogButton == null ) + { + try + { + iMessageClearLogButton = new Button(); + iMessageClearLogButton.setName( "MessageClearLogButton" ); + iMessageClearLogButton.setLabel( "Clear Log" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageClearLogButton; + } + + /** + * Return the MessageCommandPanel property value. + * + * @return java.awt.Panel + */ + private Panel getMessageCommandPanel() + { + if( iMessageCommandPanel == null ) + { + try + { + iMessageCommandPanel = new Panel(); + iMessageCommandPanel.setName( "MessageCommandPanel" ); + iMessageCommandPanel.setLayout( new FlowLayout() ); + getMessageCommandPanel().add( getMessageClearLogButton(), getMessageClearLogButton().getName() ); + getMessageCommandPanel().add( getMessageOkButton(), getMessageOkButton().getName() ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageCommandPanel; + } + + /** + * Return the MessageContentPanel property value. + * + * @return java.awt.Panel + */ + private Panel getMessageContentPanel() + { + if( iMessageContentPanel == null ) + { + try + { + iMessageContentPanel = new Panel(); + iMessageContentPanel.setName( "MessageContentPanel" ); + iMessageContentPanel.setLayout( new BorderLayout() ); + iMessageContentPanel.setBackground( SystemColor.control ); + getMessageContentPanel().add( getMessageTextArea(), "Center" ); + getMessageContentPanel().add( getMessageCommandPanel(), "South" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageContentPanel; + } + + /** + * Return the MessageFrame property value. + * + * @return java.awt.Frame + */ + private Frame getMessageFrame() + { + if( iMessageFrame == null ) + { + try + { + iMessageFrame = new Frame(); + iMessageFrame.setName( "MessageFrame" ); + iMessageFrame.setLayout( new BorderLayout() ); + iMessageFrame.setBounds( 0, 0, 750, 250 ); + iMessageFrame.setTitle( "Message Log" ); + iMessageFrame.add( getMessageContentPanel(), "Center" ); + iMessageFrame.setLocation( ( Toolkit.getDefaultToolkit().getScreenSize().width / 2 ) - ( iMessageFrame.getSize().width / 2 ), ( java.awt.Toolkit.getDefaultToolkit().getScreenSize().height / 2 ) ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageFrame; + } + + /** + * Return the MessageOkButton property value. + * + * @return java.awt.Button + */ + private Button getMessageOkButton() + { + if( iMessageOkButton == null ) + { + try + { + iMessageOkButton = new Button(); + iMessageOkButton.setName( "MessageOkButton" ); + iMessageOkButton.setLabel( "Close" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageOkButton; + } + + /** + * Return the MessageOutputLevelChoice property value. + * + * @return java.awt.Choice + */ + private Choice getMessageOutputLevelChoice() + { + if( iMessageOutputLevelChoice == null ) + { + try + { + iMessageOutputLevelChoice = new Choice(); + iMessageOutputLevelChoice.setName( "MessageOutputLevelChoice" ); + iMessageOutputLevelChoice.add( "Error" ); + iMessageOutputLevelChoice.add( "Warning" ); + iMessageOutputLevelChoice.add( "Info" ); + iMessageOutputLevelChoice.add( "Verbose" ); + iMessageOutputLevelChoice.add( "Debug" ); + iMessageOutputLevelChoice.select( 2 ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageOutputLevelChoice; + } + + /** + * Return the MessageOutputLevelLabel property value. + * + * @return java.awt.Label + */ + private Label getMessageOutputLevelLabel() + { + if( iMessageOutputLevelLabel == null ) + { + try + { + iMessageOutputLevelLabel = new Label(); + iMessageOutputLevelLabel.setName( "MessageOutputLevelLabel" ); + iMessageOutputLevelLabel.setText( "Message Level:" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageOutputLevelLabel; + } + + /** + * Return the MessageTextArea property value. + * + * @return java.awt.TextArea + */ + private TextArea getMessageTextArea() + { + if( iMessageTextArea == null ) + { + try + { + iMessageTextArea = new TextArea(); + iMessageTextArea.setName( "MessageTextArea" ); + iMessageTextArea.setFont( new Font( "monospaced", 0, 12 ) ); + iMessageTextArea.setText( "" ); + iMessageTextArea.setEditable( false ); + iMessageTextArea.setEnabled( true ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iMessageTextArea; + } + + /** + * Return the Panel1 property value. + * + * @return java.awt.Panel + */ + private Panel getOptionenPanel() + { + if( iOptionenPanel == null ) + { + try + { + iOptionenPanel = new Panel(); + iOptionenPanel.setName( "OptionenPanel" ); + iOptionenPanel.setLayout( new GridBagLayout() ); + iOptionenPanel.setBackground( SystemColor.control ); + + GridBagConstraints constraintsProjectLabel = new GridBagConstraints(); + constraintsProjectLabel.gridx = 0; + constraintsProjectLabel.gridy = 0; + constraintsProjectLabel.anchor = GridBagConstraints.WEST; + constraintsProjectLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getProjectLabel(), constraintsProjectLabel ); + + GridBagConstraints constraintsBuildFileLabel = new GridBagConstraints(); + constraintsBuildFileLabel.gridx = 0; + constraintsBuildFileLabel.gridy = 1; + constraintsBuildFileLabel.anchor = GridBagConstraints.WEST; + constraintsBuildFileLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getBuildFileLabel(), constraintsBuildFileLabel ); + + GridBagConstraints constraintsTargetLabel = new GridBagConstraints(); + constraintsTargetLabel.gridx = 0; + constraintsTargetLabel.gridy = 2; + constraintsTargetLabel.anchor = GridBagConstraints.NORTHWEST; + constraintsTargetLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getTargetLabel(), constraintsTargetLabel ); + + GridBagConstraints constraintsProjectText = new GridBagConstraints(); + constraintsProjectText.gridx = 1; + constraintsProjectText.gridy = 0; + constraintsProjectText.gridwidth = 2; + constraintsProjectText.fill = GridBagConstraints.HORIZONTAL; + constraintsProjectText.anchor = GridBagConstraints.WEST; + constraintsProjectText.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getProjectText(), constraintsProjectText ); + + GridBagConstraints constraintsBuildFileTextField = new GridBagConstraints(); + constraintsBuildFileTextField.gridx = 1; + constraintsBuildFileTextField.gridy = 1; + constraintsBuildFileTextField.fill = GridBagConstraints.HORIZONTAL; + constraintsBuildFileTextField.anchor = GridBagConstraints.WEST; + constraintsBuildFileTextField.weightx = 1.0; + constraintsBuildFileTextField.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getBuildFileTextField(), constraintsBuildFileTextField ); + + GridBagConstraints constraintsBrowseButton = new GridBagConstraints(); + constraintsBrowseButton.gridx = 2; + constraintsBrowseButton.gridy = 1; + constraintsBrowseButton.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getBrowseButton(), constraintsBrowseButton ); + + GridBagConstraints constraintsTargetList = new GridBagConstraints(); + constraintsTargetList.gridx = 1; + constraintsTargetList.gridy = 2; + constraintsTargetList.gridheight = 2; + constraintsTargetList.fill = GridBagConstraints.BOTH; + constraintsTargetList.weightx = 1.0; + constraintsTargetList.weighty = 1.0; + constraintsTargetList.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getTargetList(), constraintsTargetList ); + + GridBagConstraints constraintsMessageOutputLevelLabel = new GridBagConstraints(); + constraintsMessageOutputLevelLabel.gridx = 0; + constraintsMessageOutputLevelLabel.gridy = 4; + constraintsMessageOutputLevelLabel.anchor = GridBagConstraints.WEST; + constraintsMessageOutputLevelLabel.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getMessageOutputLevelLabel(), constraintsMessageOutputLevelLabel ); + + GridBagConstraints constraintsMessageOutputLevelChoice = new GridBagConstraints(); + constraintsMessageOutputLevelChoice.gridx = 1; + constraintsMessageOutputLevelChoice.gridy = 4; + constraintsMessageOutputLevelChoice.fill = GridBagConstraints.HORIZONTAL; + constraintsMessageOutputLevelChoice.anchor = GridBagConstraints.WEST; + constraintsMessageOutputLevelChoice.weightx = 1.0; + constraintsMessageOutputLevelChoice.insets = new Insets( 4, 4, 4, 4 ); + getOptionenPanel().add( getMessageOutputLevelChoice(), constraintsMessageOutputLevelChoice ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iOptionenPanel; + } + + /** + * Return the ProjectLabel property value. + * + * @return java.awt.Label + */ + private Label getProjectLabel() + { + if( iProjectLabel == null ) + { + try + { + iProjectLabel = new Label(); + iProjectLabel.setName( "ProjectLabel" ); + iProjectLabel.setText( "Projectname:" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iProjectLabel; + } + + /** + * Return the ProjectText property value. + * + * @return java.awt.Label + */ + private Label getProjectText() + { + if( iProjectText == null ) + { + try + { + iProjectText = new Label(); + iProjectText.setName( "ProjectText" ); + iProjectText.setText( " " ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iProjectText; + } + + /** + * Return the ReloadButton property value. + * + * @return java.awt.Button + */ + private Button getReloadButton() + { + if( iReloadButton == null ) + { + try + { + iReloadButton = new Button(); + iReloadButton.setName( "ReloadButton" ); + iReloadButton.setLabel( "(Re)Load" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iReloadButton; + } + + /** + * Return the SaveMenuItem property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getSaveMenuItem() + { + if( iSaveMenuItem == null ) + { + try + { + iSaveMenuItem = new MenuItem(); + iSaveMenuItem.setLabel( "Save BuildInfo To Repository" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iSaveMenuItem; + } + + /** + * Return the ShowLogMenuItem property value. + * + * @return java.awt.MenuItem + */ + private MenuItem getShowLogMenuItem() + { + if( iShowLogMenuItem == null ) + { + try + { + iShowLogMenuItem = new MenuItem(); + iShowLogMenuItem.setLabel( "Log" ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iShowLogMenuItem; + } + + /** + * Return the StopButton property value. + * + * @return java.awt.Button + */ + private Button getStopButton() + { + if( iStopButton == null ) + { + try + { + iStopButton = new Button(); + iStopButton.setName( "StopButton" ); + iStopButton.setLabel( "Stop" ); + iStopButton.setEnabled( false ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iStopButton; + } + + /** + * Return the TargetLabel property value. + * + * @return java.awt.Label + */ + private Label getTargetLabel() + { + if( iTargetLabel == null ) + { + try + { + iTargetLabel = new Label(); + iTargetLabel.setName( "TargetLabel" ); + iTargetLabel.setText( "Target:" ); + iTargetLabel.setEnabled( true ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iTargetLabel; + } + + /** + * Return the TargetList property value. + * + * @return java.awt.List + */ + private List getTargetList() + { + if( iTargetList == null ) + { + try + { + iTargetList = new List(); + iTargetList.setName( "TargetList" ); + iTargetList.setEnabled( true ); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + return iTargetList; + } + + /** + * connectBuildFileNameToTextField: (BuildInfo.buildFileName <--> + * BuildFileTextField.text) + */ + private void connectBuildFileNameToTextField() + { + /* + * Set the target from the source + */ + try + { + if( iConnPtoP2Aligning == false ) + { + iConnPtoP2Aligning = true; + if( ( getBuildInfo() != null ) ) + { + getBuildFileTextField().setText( getBuildInfo().getBuildFileName() ); + } + iConnPtoP2Aligning = false; + } + } + catch( Throwable iExc ) + { + iConnPtoP2Aligning = false; + handleException( iExc ); + } + } + + /** + * connectProjectNameToLabel: (BuildInfo.vajProjectName <--> + * ProjectText.text) + */ + private void connectProjectNameToLabel() + { + /* + * Set the target from the source + */ + try + { + if( ( getBuildInfo() != null ) ) + { + getProjectText().setText( getBuildInfo().getVAJProjectName() ); + } + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + } + + /** + * connectTextFieldToBuildFileName: (BuildInfo.buildFileName <--> + * BuildFileTextField.text) + */ + private void connectTextFieldToBuildFileName() + { + /* + * Set the source from the target + */ + try + { + if( iConnPtoP2Aligning == false ) + { + iConnPtoP2Aligning = true; + if( ( getBuildInfo() != null ) ) + { + getBuildInfo().setBuildFileName( getBuildFileTextField().getText() ); + } + iConnPtoP2Aligning = false; + } + } + catch( Throwable iExc ) + { + iConnPtoP2Aligning = false; + handleException( iExc ); + } + } + + /** + * external build of a .jar-file + */ + private void executeTarget() + { + try + { + getMessageFrame().show(); + getBuildInfo().executeProject( logger ); + } + catch( Throwable exc ) + { + logger.logException( exc ); + } + return; + } + + /** + * Fills the taget-list with project-targets + */ + private void fillList() + { + getTargetList().removeAll(); + Vector targets = getBuildInfo().getProjectTargets(); + for( int i = 0; i < targets.size(); i++ ) + { + getTargetList().add( targets.elementAt( i ).toString() ); + } + getTargetList().select( iBuildInfo.getProjectTargets().indexOf( iBuildInfo.getTarget() ) ); + if( getTargetList().getSelectedIndex() >= 0 ) + { + getBuildButton().setEnabled( true ); + } + } + + /** + * Called whenever the part throws an exception. + * + * @param exception Throwable + */ + private void handleException( Throwable exception ) + { + // Write exceptions to the log-window + String trace = StringUtils.getStackTrace( exception ); + + getMessageTextArea().append( lineSeparator + lineSeparator + trace ); + getMessageFrame().show(); + + } + + /** + * Initializes connections + * + * @exception Exception The exception description. + */ + private void initConnections() + throws Exception + { + this.addWindowListener( iEventHandler ); + getBrowseButton().addActionListener( iEventHandler ); + getCloseButton().addActionListener( iEventHandler ); + getBuildButton().addActionListener( iEventHandler ); + getStopButton().addActionListener( iEventHandler ); + getSaveMenuItem().addActionListener( iEventHandler ); + getAboutOkButton().addActionListener( iEventHandler ); + getAboutMenuItem().addActionListener( iEventHandler ); + getMessageOkButton().addActionListener( iEventHandler ); + getMessageClearLogButton().addActionListener( iEventHandler ); + getMessageOkButton().addActionListener( iEventHandler ); + getShowLogMenuItem().addActionListener( iEventHandler ); + getAboutDialog().addWindowListener( iEventHandler ); + getMessageFrame().addWindowListener( iEventHandler ); + getReloadButton().addActionListener( iEventHandler ); + getTargetList().addItemListener( iEventHandler ); + getMessageOutputLevelChoice().addItemListener( iEventHandler ); + getBuildFileTextField().addTextListener( iEventHandler ); + connectProjectNameToLabel(); + connectBuildFileNameToTextField(); + } + + /** + * Initialize the class. + */ + private void initialize() + { + try + { + setName( "AntMake" ); + setMenuBar( getAntMakeMenuBar() ); + setLayout( new java.awt.BorderLayout() ); + setSize( 389, 222 ); + setTitle( "Ant VisualAge for Java Tool-Integration" ); + add( getContentsPane(), "Center" ); + initConnections(); + } + catch( Throwable iExc ) + { + handleException( iExc ); + } + setLocation( ( Toolkit.getDefaultToolkit().getScreenSize().width / 2 ) - ( getSize().width / 2 ), ( java.awt.Toolkit.getDefaultToolkit().getScreenSize().height / 2 ) - ( getSize().height ) ); + if( ( getTargetList().getItemCount() == 0 ) || ( getTargetList().getSelectedIndex() < 0 ) ) + { + getBuildButton().setEnabled( false ); + } + } + + /** + * Saves the build-informations to repository + */ + private void saveBuildInfo() + { + try + { + VAJAntTool.saveBuildData( getBuildInfo() ); + } + catch( Throwable exc ) + { + // This Exception occurs when you try to write into a versioned project + handleException( exc ); + } + return; + } + + /** + * Eventhandler to handle all AWT-events + * + * @author RT + */ + private class PrivateEventHandler implements ActionListener, ItemListener, TextListener, WindowListener, PropertyChangeListener + { + /** + * ActionListener method + * + * @param e Description of Parameter + */ + public void actionPerformed( ActionEvent e ) + { + try + { + /* + * #### Main App-Frame #### + */ + // browse XML-File with filechooser + if( e.getSource() == VAJAntToolGUI.this.getBrowseButton() ) + { + getFileDialog().setDirectory( getBuildFileTextField().getText().substring( 0, getBuildFileTextField().getText().lastIndexOf( '\\' ) + 1 ) ); + getFileDialog().setFile( "*.xml" ); + getFileDialog().show(); + if( !getFileDialog().getFile().equals( "" ) ) + { + getBuildFileTextField().setText( getFileDialog().getDirectory() + getFileDialog().getFile() ); + } + } + // dispose and exit application + if( e.getSource() == VAJAntToolGUI.this.getCloseButton() ) + { + dispose(); + System.exit( 0 ); + } + // start build-process + if( e.getSource() == VAJAntToolGUI.this.getBuildButton() ) + { + executeTarget(); + } + if( e.getSource() == VAJAntToolGUI.this.getStopButton() ) + { + getBuildInfo().cancelBuild(); + } + if( e.getSource() == VAJAntToolGUI.this.getReloadButton() ) + { + try + { + getBuildInfo().updateTargetList(); + fillList(); + } + catch( Throwable fileNotFound ) + { + handleException( fileNotFound ); + getTargetList().removeAll(); + getBuildButton().setEnabled( false ); + } + } + // MenuItems + if( e.getSource() == VAJAntToolGUI.this.getSaveMenuItem() ) + saveBuildInfo(); + if( e.getSource() == VAJAntToolGUI.this.getAboutMenuItem() ) + getAboutDialog().show(); + if( e.getSource() == VAJAntToolGUI.this.getShowLogMenuItem() ) + getMessageFrame().show(); + /* + * #### About dialog #### + */ + if( e.getSource() == VAJAntToolGUI.this.getAboutOkButton() ) + getAboutDialog().dispose(); + /* + * #### Log frame #### + */ + if( e.getSource() == VAJAntToolGUI.this.getMessageOkButton() ) + getMessageFrame().dispose(); + if( e.getSource() == VAJAntToolGUI.this.getMessageClearLogButton() ) + getMessageTextArea().setText( "" ); + if( e.getSource() == VAJAntToolGUI.this.getMessageOkButton() ) + getMessageFrame().dispose(); + } + catch( Throwable exc ) + { + handleException( exc ); + } + } + + /** + * ItemListener method + * + * @param e Description of Parameter + */ + public void itemStateChanged( ItemEvent e ) + { + try + { + if( e.getSource() == VAJAntToolGUI.this.getTargetList() ) + getBuildButton().setEnabled( true ); + if( e.getSource() == VAJAntToolGUI.this.getMessageOutputLevelChoice() ) + getBuildInfo().setOutputMessageLevel( getMessageOutputLevelChoice().getSelectedIndex() ); + if( e.getSource() == VAJAntToolGUI.this.getTargetList() ) + getBuildInfo().setTarget( getTargetList().getSelectedItem() ); + } + catch( Throwable exc ) + { + handleException( exc ); + } + } + + /** + * PropertyChangeListener method + * + * @param evt Description of Parameter + */ + public void propertyChange( java.beans.PropertyChangeEvent evt ) + { + if( evt.getSource() == VAJAntToolGUI.this.getBuildInfo() && ( evt.getPropertyName().equals( "projectName" ) ) ) + connectProjectNameToLabel(); + if( evt.getSource() == VAJAntToolGUI.this.getBuildInfo() && ( evt.getPropertyName().equals( "buildFileName" ) ) ) + connectBuildFileNameToTextField(); + } + + /** + * TextListener method + * + * @param e Description of Parameter + */ + public void textValueChanged( TextEvent e ) + { + if( e.getSource() == VAJAntToolGUI.this.getBuildFileTextField() ) + connectTextFieldToBuildFileName(); + } + + public void windowActivated( WindowEvent e ) { } + + public void windowClosed( WindowEvent e ) { } + + /** + * WindowListener methods + * + * @param e Description of Parameter + */ + public void windowClosing( WindowEvent e ) + { + try + { + if( e.getSource() == VAJAntToolGUI.this ) + { + dispose(); + System.exit( 0 ); + } + if( e.getSource() == VAJAntToolGUI.this.getAboutDialog() ) + getAboutDialog().dispose(); + if( e.getSource() == VAJAntToolGUI.this.getMessageFrame() ) + getMessageFrame().dispose(); + } + catch( Throwable exc ) + { + handleException( exc ); + } + } + + public void windowDeactivated( WindowEvent e ) { } + + public void windowDeiconified( WindowEvent e ) { } + + public void windowIconified( WindowEvent e ) { } + + public void windowOpened( WindowEvent e ) { } + } + + /** + * This internal BuildLogger, to be honest, is just a BuildListener. It does + * nearly the same as the DefaultLogger, but uses the Loggin-Window for + * output. + * + * @author RT + */ + private class VAJBuildLogger implements BuildListener + { + private long startTime = System.currentTimeMillis(); + + /** + * VAJBuildLogger constructor comment. + */ + public VAJBuildLogger() + { + super(); + } + + /** + * Fired after the last target has finished. This event will still be + * thrown if an error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + public void buildFinished( BuildEvent event ) + { + getStopButton().setEnabled( false ); + getBuildButton().setEnabled( true ); + getBuildButton().requestFocus(); + + Throwable error = event.getException(); + + if( error == null ) + { + getMessageTextArea().append( lineSeparator + "BUILD SUCCESSFUL" ); + } + else + { + logException( error ); + } + + getMessageTextArea().append( lineSeparator + "Total time: " + formatTime( System.currentTimeMillis() - startTime ) ); + } + + /** + * Fired before any targets are started. + * + * @param event Description of Parameter + */ + public void buildStarted( BuildEvent event ) + { + getStopButton().setEnabled( true ); + getBuildButton().setEnabled( false ); + getStopButton().requestFocus(); + + startTime = System.currentTimeMillis(); + getMessageTextArea().append( lineSeparator ); + } + + + /** + * Outputs an exception. + * + * @param error Description of Parameter + */ + public void logException( Throwable error ) + { + getMessageTextArea().append( lineSeparator + "BUILD FAILED" + lineSeparator ); + + if( error instanceof BuildException ) + { + getMessageTextArea().append( error.toString() ); + + Throwable nested = ( ( BuildException )error ).getCause(); + if( nested != null ) + { + nested.printStackTrace( System.err ); + } + } + else + { + error.printStackTrace( System.err ); + } + } + + /** + * Fired whenever a message is logged. + * + * @param event Description of Parameter + * @see BuildEvent#getMessage() + * @see BuildEvent#getPriority() + */ + public void messageLogged( BuildEvent event ) + { + if( event.getPriority() <= getBuildInfo().getOutputMessageLevel() ) + { + String msg = ""; + if( event.getTask() != null ) + msg = "[" + event.getTask().getTaskName() + "] "; + getMessageTextArea().append( lineSeparator + msg + event.getMessage() ); + } + } + + /** + * Fired when a target has finished. This event will still be thrown if + * an error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + public void targetFinished( BuildEvent event ) { } + + /** + * Fired when a target is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTarget() + */ + public void targetStarted( BuildEvent event ) + { + if( getBuildInfo().getOutputMessageLevel() <= Project.MSG_INFO ) + { + getMessageTextArea().append( lineSeparator + event.getTarget().getName() + ":" ); + } + } + + /** + * Fired when a task has finished. This event will still be throw if an + * error occured during the build. + * + * @param event Description of Parameter + * @see BuildEvent#getException() + */ + public void taskFinished( BuildEvent event ) { } + + /** + * Fired when a task is started. + * + * @param event Description of Parameter + * @see BuildEvent#getTask() + */ + public void taskStarted( BuildEvent event ) { } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java new file mode 100644 index 000000000..a504426bb --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJBuildInfo.java @@ -0,0 +1,585 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.File; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildEvent; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.Target; + +/** + * This class wraps the Ant project information needed to start Ant from Visual + * Age. It serves the following purposes: - acts as model for AntMakeFrame - + * converts itself to/from String (to store the information as ToolData in the + * VA repository) - wraps Project functions for the GUI (get target list, + * execute target) - manages a seperate thread for Ant project execution this + * allows interrupting a running build from a GUI + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +class VAJBuildInfo implements Runnable +{ + + // name of the VA project this BuildInfo belongs to + private String vajProjectName = ""; + + // name of the Ant build file + private String buildFileName = ""; + + // main targets found in the build file + private Vector projectTargets = new Vector(); + + // target selected for execution + private java.lang.String target = ""; + + // log level + private int outputMessageLevel = Project.MSG_INFO; + + // is true if Project initialization was successful + private transient boolean projectInitialized = false; + + // Support for bound properties + protected transient PropertyChangeSupport propertyChange; + + // thread for Ant build execution + private Thread buildThread; + + // Ant Project created from build file + private transient Project project; + + // the listener used to log output. + private BuildListener projectLogger; + + /** + * Creates a BuildInfo object from a String The String must be in the format + * outputMessageLevel'|'buildFileName'|'defaultTarget'|'(project target'|')* + * + * @param data java.lang.String + * @return org.apache.tools.ant.taskdefs.optional.vaj.BuildInfo + */ + public static VAJBuildInfo parse( String data ) + { + VAJBuildInfo result = new VAJBuildInfo(); + + try + { + StringTokenizer tok = new StringTokenizer( data, "|" ); + result.setOutputMessageLevel( tok.nextToken() ); + result.setBuildFileName( tok.nextToken() ); + result.setTarget( tok.nextToken() ); + while( tok.hasMoreTokens() ) + { + result.projectTargets.addElement( tok.nextToken() ); + } + } + catch( Throwable t ) + { + // if parsing the info fails, just return + // an empty VAJBuildInfo + } + return result; + } + + /** + * Search for the insert position to keep names a sorted list of Strings + * This method has been copied from org.apache.tools.ant.Main + * + * @param names Description of Parameter + * @param name Description of Parameter + * @return Description of the Returned Value + */ + private static int findTargetPosition( Vector names, String name ) + { + int res = names.size(); + for( int i = 0; i < names.size() && res == names.size(); i++ ) + { + if( name.compareTo( ( String )names.elementAt( i ) ) < 0 ) + { + res = i; + } + } + return res; + } + + /** + * Sets the build file name + * + * @param newBuildFileName The new BuildFileName value + */ + public void setBuildFileName( String newBuildFileName ) + { + String oldValue = buildFileName; + buildFileName = newBuildFileName; + setProjectInitialized( false ); + firePropertyChange( "buildFileName", oldValue, buildFileName ); + } + + /** + * Sets the log level (value must be one of the constants in Project) + * + * @param newOutputMessageLevel The new OutputMessageLevel value + */ + public void setOutputMessageLevel( int newOutputMessageLevel ) + { + int oldValue = outputMessageLevel; + outputMessageLevel = newOutputMessageLevel; + firePropertyChange( "outputMessageLevel", + new Integer( oldValue ), new Integer( outputMessageLevel ) ); + } + + /** + * Sets the target to execute when executeBuild is called + * + * @param newTarget build target + */ + public void setTarget( String newTarget ) + { + String oldValue = target; + target = newTarget; + firePropertyChange( "target", oldValue, target ); + } + + /** + * Sets the name of the Visual Age for Java project where this BuildInfo + * belongs to + * + * @param newVAJProjectName The new VAJProjectName value + */ + public void setVAJProjectName( String newVAJProjectName ) + { + String oldValue = vajProjectName; + vajProjectName = newVAJProjectName; + firePropertyChange( "VAJProjectName", oldValue, vajProjectName ); + } + + /** + * Returns the build file name. + * + * @return build file name. + */ + public String getBuildFileName() + { + return buildFileName; + } + + /** + * Returns the log level + * + * @return log level. + */ + public int getOutputMessageLevel() + { + return outputMessageLevel; + } + + /** + * return a list of all targets in the current buildfile + * + * @return The ProjectTargets value + */ + public Vector getProjectTargets() + { + return projectTargets; + } + + /** + * returns the selected target. + * + * @return The Target value + */ + public java.lang.String getTarget() + { + return target; + } + + /** + * returns the VA project name + * + * @return The VAJProjectName value + */ + public String getVAJProjectName() + { + return vajProjectName; + } + + /** + * Returns true, if the Ant project is initialized (i.e. buildfile loaded) + * + * @return The ProjectInitialized value + */ + public boolean isProjectInitialized() + { + return projectInitialized; + } + + + /** + * The addPropertyChangeListener method was generated to support the + * propertyChange field. + * + * @param listener The feature to be added to the PropertyChangeListener + * attribute + */ + public synchronized void addPropertyChangeListener( PropertyChangeListener listener ) + { + getPropertyChange().addPropertyChangeListener( listener ); + } + + /** + * Returns the BuildInfo information as String. The BuildInfo can be rebuilt + * from that String by calling parse(). + * + * @return java.lang.String + */ + public String asDataString() + { + String result = getOutputMessageLevel() + "|" + getBuildFileName() + + "|" + getTarget(); + for( Enumeration e = getProjectTargets().elements(); + e.hasMoreElements(); ) + { + result = result + "|" + e.nextElement(); + } + + return result; + } + + + /** + * cancels a build. + */ + public void cancelBuild() + { + buildThread.interrupt(); + } + + /** + * Executes the target set by setTarget(). + * + * @param logger Description of Parameter + */ + public void executeProject( BuildListener logger ) + { + Throwable error; + projectLogger = logger; + try + { + buildThread = new Thread( this ); + buildThread.setPriority( Thread.MIN_PRIORITY ); + buildThread.start(); + } + catch( RuntimeException exc ) + { + error = exc; + throw exc; + } + catch( Error err ) + { + error = err; + throw err; + } + } + + /** + * The firePropertyChange method was generated to support the propertyChange + * field. + * + * @param propertyName Description of Parameter + * @param oldValue Description of Parameter + * @param newValue Description of Parameter + */ + public void firePropertyChange( java.lang.String propertyName, java.lang.Object oldValue, java.lang.Object newValue ) + { + getPropertyChange().firePropertyChange( propertyName, oldValue, newValue ); + } + + /** + * The removePropertyChangeListener method was generated to support the + * propertyChange field. + * + * @param listener Description of Parameter + */ + public synchronized void removePropertyChangeListener( PropertyChangeListener listener ) + { + getPropertyChange().removePropertyChangeListener( listener ); + } + + /** + * Executes a build. This method is executed by the Ant execution thread + */ + public void run() + { + try + { + InterruptedChecker ic = new InterruptedChecker( projectLogger ); + BuildEvent e = new BuildEvent( getProject() ); + try + { + ic.buildStarted( e ); + + if( !isProjectInitialized() ) + { + initProject(); + } + + project.addBuildListener( ic ); + project.executeTarget( target ); + + ic.buildFinished( e ); + } + catch( Throwable t ) + { + e.setException( t ); + ic.buildFinished( e ); + } + finally + { + project.removeBuildListener( ic ); + } + } + catch( Throwable t2 ) + { + System.out.println( "unexpected exception!" ); + t2.printStackTrace(); + } + } + + /** + * reloads the build file and updates the target list + */ + public void updateTargetList() + { + project = new Project(); + initProject(); + projectTargets.removeAllElements(); + Enumeration ptargets = project.getTargets().elements(); + while( ptargets.hasMoreElements() ) + { + Target currentTarget = ( Target )ptargets.nextElement(); + if( currentTarget.getDescription() != null ) + { + String targetName = currentTarget.getName(); + int pos = findTargetPosition( projectTargets, targetName ); + projectTargets.insertElementAt( targetName, pos ); + } + } + } + + /** + * Accessor for the propertyChange field. + * + * @return The PropertyChange value + */ + protected PropertyChangeSupport getPropertyChange() + { + if( propertyChange == null ) + { + propertyChange = new PropertyChangeSupport( this ); + } + return propertyChange; + } + + /** + * Sets the log level (value must be one of the constants in Project) + * + * @param outputMessageLevel log level as String. + */ + private void setOutputMessageLevel( String outputMessageLevel ) + { + int level = Integer.parseInt( outputMessageLevel ); + setOutputMessageLevel( level ); + } + + /** + * sets the initialized flag + * + * @param initialized The new ProjectInitialized value + */ + private void setProjectInitialized( boolean initialized ) + { + Boolean oldValue = new Boolean( projectInitialized ); + projectInitialized = initialized; + firePropertyChange( "projectInitialized", oldValue, new Boolean( projectInitialized ) ); + } + + /** + * Returns the Ant project + * + * @return org.apache.tools.ant.Project + */ + private Project getProject() + { + if( project == null ) + { + project = new Project(); + } + return project; + } + + /** + * Initializes the Ant project. Assumes that the project attribute is + * already set. + */ + private void initProject() + { + try + { + project.init(); + File buildFile = new File( getBuildFileName() ); + project.setUserProperty( "ant.file", buildFile.getAbsolutePath() ); + ProjectHelper.configureProject( project, buildFile ); + setProjectInitialized( true ); + } + catch( RuntimeException exc ) + { + setProjectInitialized( false ); + throw exc; + } + catch( Error err ) + { + setProjectInitialized( false ); + throw err; + } + } + + /** + * This exception is thrown when a build is interrupted + * + * @author RT + */ + public static class BuildInterruptedException extends BuildException + { + public String toString() + { + return "BUILD INTERRUPTED"; + } + } + + /** + * BuildListener which checks for interruption and throws Exception if build + * process is interrupted. This class is a wrapper around a 'real' listener. + * + * @author RT + */ + private class InterruptedChecker implements BuildListener + { + // the real listener + BuildListener wrappedListener; + + /** + * Can only be constructed as wrapper around a real listener + * + * @param listener the real listener + */ + public InterruptedChecker( BuildListener listener ) + { + super(); + wrappedListener = listener; + } + + /** + * Fired after the last target has finished. This event will still be + * thrown if an error occured during the build. + * + * @param event Description of Parameter + */ + public void buildFinished( BuildEvent event ) + { + wrappedListener.buildFinished( event ); + checkInterrupted(); + } + + /** + * Fired before any targets are started. + * + * @param event Description of Parameter + */ + public void buildStarted( BuildEvent event ) + { + wrappedListener.buildStarted( event ); + checkInterrupted(); + } + + /** + * Fired whenever a message is logged. + * + * @param event Description of Parameter + */ + public void messageLogged( BuildEvent event ) + { + wrappedListener.messageLogged( event ); + checkInterrupted(); + } + + /** + * Fired when a target has finished. This event will still be thrown if + * an error occured during the build. + * + * @param event Description of Parameter + */ + public void targetFinished( BuildEvent event ) + { + wrappedListener.targetFinished( event ); + checkInterrupted(); + } + + /** + * Fired when a target is started. + * + * @param event Description of Parameter + */ + public void targetStarted( BuildEvent event ) + { + wrappedListener.targetStarted( event ); + checkInterrupted(); + } + + /** + * Fired when a task has finished. This event will still be throw if an + * error occured during the build. + * + * @param event Description of Parameter + */ + public void taskFinished( BuildEvent event ) + { + wrappedListener.taskFinished( event ); + checkInterrupted(); + } + + /** + * Fired when a task is started. + * + * @param event Description of Parameter + */ + public void taskStarted( BuildEvent event ) + { + wrappedListener.taskStarted( event ); + checkInterrupted(); + } + + /** + * checks if the thread was interrupted. When an interrupt occured, + * throw an Exception to stop the execution. + */ + protected void checkInterrupted() + { + if( buildThread.isInterrupted() ) + { + throw new BuildInterruptedException(); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java new file mode 100644 index 000000000..4d49c923d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExport.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.PatternSet; + +/** + * Export packages from the Visual Age for Java workspace. The packages are + * specified similar to all other MatchingTasks. Since the VA Workspace is not + * file based, this task is simulating a directory hierarchy for the workspace: + * The 'root' contains all project 'dir's, and the projects contain their + * respective package 'dir's. Example:

              <vajexport + * destdir="C:/builddir/source">  <include + * name="/MyVAProject/org/foo/subsystem1/**" />  <exclude + * name="/MyVAProject/org/foo/subsystem1/test/**"/> </vajexport> + *
              exports all packages in the project MyVAProject which start + * with 'org.foo.subsystem1' except of these starting with + * 'org.foo.subsystem1.test'. There are flags to choose which items to export: + * exportSources: export Java sources exportResources: export project resources + * exportClasses: export class files exportDebugInfo: export class files with + * debug info (use with exportClasses) default is exporting Java files and + * resources. + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +public class VAJExport extends VAJTask +{ + protected boolean exportSources = true; + protected boolean exportResources = true; + protected boolean exportClasses = false; + protected boolean exportDebugInfo = false; + protected boolean useDefaultExcludes = true; + protected boolean overwrite = true; + + protected PatternSet patternSet = new PatternSet(); + //set set... method comments for description + protected File destDir; + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + /** + * Set the destination directory into which the selected items should be + * exported + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /** + * Sets the set of exclude patterns. Patterns may be separated by a comma or + * a space. Currently only patterns denoting packages are supported + * + * @param excludes the string containing the exclude patterns + */ + public void setExcludes( String excludes ) + { + patternSet.setExcludes( excludes ); + } + + /** + * if exportClasses is set, class files are exported + * + * @param doExport The new ExportClasses value + */ + public void setExportClasses( boolean doExport ) + { + exportClasses = doExport; + } + + /** + * if exportDebugInfo is set, the exported class files contain debug info + * + * @param doExport The new ExportDebugInfo value + */ + public void setExportDebugInfo( boolean doExport ) + { + exportDebugInfo = doExport; + } + + /** + * if exportResources is set, resource file will be exported + * + * @param doExport The new ExportResources value + */ + public void setExportResources( boolean doExport ) + { + exportResources = doExport; + } + + /** + * if exportSources is set, java files will be exported + * + * @param doExport The new ExportSources value + */ + public void setExportSources( boolean doExport ) + { + exportSources = doExport; + } + + /** + * Sets the set of include patterns. Patterns may be separated by a comma or + * a space.Currently only patterns denoting packages are supported + * + * @param includes the string containing the include patterns + */ + public void setIncludes( String includes ) + { + patternSet.setIncludes( includes ); + } + + /** + * if Overwrite is set, files will be overwritten during export + * + * @param doOverwrite The new Overwrite value + */ + public void setOverwrite( boolean doOverwrite ) + { + overwrite = doOverwrite; + } + + /** + * add a name entry on the exclude list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createExclude() + { + return patternSet.createExclude(); + } + + /** + * add a name entry on the include list + * + * @return Description of the Returned Value + */ + public PatternSet.NameEntry createInclude() + { + return patternSet.createInclude(); + } + + /** + * do the export + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a destdir + if( destDir == null ) + { + throw new BuildException( "destdir attribute must be set!" ); + } + + // delegate the export to the VAJUtil object. + getUtil().exportPackages( destDir, + patternSet.getIncludePatterns( getProject() ), + patternSet.getExcludePatterns( getProject() ), + exportClasses, exportDebugInfo, + exportResources, exportSources, + useDefaultExcludes, overwrite ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java new file mode 100644 index 000000000..a78a51efc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJExportServlet.java @@ -0,0 +1,256 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; + +/** + * A Remote Access to Tools Servlet to extract package sets from the Workbench + * to the local file system. The following table describes the servlet + * parameters. + * + * + * + * + * + * Parameter + * + * + * + * Values + * + * + * + * Description + * + * + * + * + * + * + * + * dir + * + * + * + * Any valid directory name on the server. + * + * + * + * The directory to export the files to on the machine where the servlet + * is being run. If the directory doesn't exist, it will be created.

              + * + * Relative paths are relative to IBMVJava/ide/tools/com-ibm-ivj-toolserver, + * where IBMVJava is the VisualAge for Java installation directory. + * + * + * + * + * + * + * + * include + * + * + * + * See below. + * + * + * + * The pattern used to indicate which projects and packages to export. + * + * + * + * + * + * + * + * + * exclude + * + * + * + * See below + * + * + * + * The pattern used to indicate which projects and packages not + * to export. + * + * + * + * + * + * + * + * cls + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Export class files. Defaults to "no". + * + * + * + * + * + * + * + * src + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Export source files. Defaults to "yes". + * + * + * + * + * + * + * + * res + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Export resource files associated with the included project(s). Defaults + * to "yes". + * + * + * + * + * + * + * + * dex + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Use the default exclusion patterns. Defaults to "yes". See below for an + * explanation of default excludes. + * + * + * + * + * + * + * + * owr + * + * + * + * "yes" or "no" (without the quotes) + * + * + * + * Overwrite any existing files. Defaults to "yes". + * + * + * + * + * + *

              + * + * The vajexport servlet uses include and exclude parameters to form the + * criteria for selecting packages to export. The parameter is broken up into + * ProjectName/packageNameSegments, where ProjectName is what you expect, and + * packageNameSegments is a partial (or complete) package name, separated by + * forward slashes, rather than periods. Each packageNameSegment can have + * wildcard characters.

              + * + * + * + * + * + * Wildcard Characters + * + * + * + * Description + * + * + * + * + * + * + * + * * + * + * + * + * Match zero or more characters in that segment. + * + * + * + * + * + * + * + * ? + * + * + * + * Match one character in that segment. + * + * + * + * + * + * + * + * ** + * + * + * + * Matches all characters in zero or more segments. + * + * + * + * + * + * + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public class VAJExportServlet extends VAJToolsServlet +{ + // constants for servlet param names + public final static String WITH_DEBUG_INFO = "deb"; + public final static String OVERWRITE_PARAM = "owr"; + + /** + * Respond to a request to export packages from the Workbench. + */ + protected void executeRequest() + { + getUtil().exportPackages( + new File( getFirstParamValueString( DIR_PARAM ) ), + getParamValues( INCLUDE_PARAM ), + getParamValues( EXCLUDE_PARAM ), + getBooleanParam( CLASSES_PARAM, false ), + getBooleanParam( WITH_DEBUG_INFO, false ), + getBooleanParam( RESOURCES_PARAM, true ), + getBooleanParam( SOURCES_PARAM, true ), + getBooleanParam( DEFAULT_EXCLUDES_PARAM, true ), + getBooleanParam( OVERWRITE_PARAM, true ) + ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java new file mode 100644 index 000000000..36d63bbf8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImport.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.lang.reflect.Field; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.types.FileSet; + +/** + * Import source, class files, and resources to the Visual Age for Java + * workspace using FileSets.

              + * + * Example:

              + * <vajimport project="MyVAProject">
              + *   <fileset dir="src">
              + *     <include name="org/foo/subsystem1/**" />
              + *     <exclude name="/org/foo/subsystem1/test/**" />
              + *  </fileset>
              + * </vajexport>
              + * 
              import all source and resource files from the "src" directory which + * start with 'org.foo.subsystem1', except of these starting with + * 'org.foo.subsystem1.test' into the project MyVAProject.

              + * + * If MyVAProject isn't loaded into the Workspace, a new edition is created in + * the repository and automatically loaded into the Workspace. There has to be + * at least one nested FileSet element.

              + * + * There are attributes to choose which items to export: + * + * + * + * + * + * Attribute + * + * + * + * Description + * + * + * + * Required + * + * + * + * + * + * + * + * project + * + * + * + * the name of the Project to import to + * + * + * + * Yes + * + * + * + * + * + * + * + * importSources + * + * + * + * import Java sources, defaults to "yes" + * + * + * + * No + * + * + * + * + * + * + * + * importResources + * + * + * + * import resource files (anything that doesn't end with .java or .class), + * defaults to "yes" + * + * + * + * No + * + * + * + * + * + * + * + * importClasses + * + * + * + * import class files, defaults to "no" + * + * + * + * No + * + * + * + * + * + * + * + * @author RT + * @author: Glenn McAllister, inspired by a similar task written by Peter Kelley + */ +public class VAJImport extends VAJTask +{ + protected Vector filesets = new Vector(); + protected boolean importSources = true; + protected boolean importResources = true; + protected boolean importClasses = false; + protected String importProject = null; + protected boolean useDefaultExcludes = true; + + /** + * Sets whether default exclusions should be used or not. + * + * @param useDefaultExcludes "true"|"on"|"yes" when default exclusions + * should be used, "false"|"off"|"no" when they shouldn't be used. + */ + public void setDefaultexcludes( boolean useDefaultExcludes ) + { + this.useDefaultExcludes = useDefaultExcludes; + } + + /** + * Import .class files. + * + * @param importClasses The new ImportClasses value + */ + public void setImportClasses( boolean importClasses ) + { + this.importClasses = importClasses; + } + + /** + * Import resource files (anything that doesn't end in .class or .java) + * + * @param importResources The new ImportResources value + */ + public void setImportResources( boolean importResources ) + { + this.importResources = importResources; + } + + /** + * Import .java files + * + * @param importSources The new ImportSources value + */ + public void setImportSources( boolean importSources ) + { + this.importSources = importSources; + } + + + /** + * The VisualAge for Java Project name to import into. + * + * @param projectName The new Project value + */ + public void setProject( String projectName ) + { + this.importProject = projectName; + } + + /** + * Adds a set of files (nested fileset attribute). + * + * @param set The feature to be added to the Fileset attribute + */ + public void addFileset( FileSet set ) + { + filesets.addElement( set ); + } + + /** + * Do the import. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + if( filesets.size() == 0 ) + { + throw new BuildException( "At least one fileset is required!" ); + } + + if( importProject == null || "".equals( importProject ) ) + { + throw new BuildException( "The VisualAge for Java Project name is required!" ); + } + + for( Enumeration e = filesets.elements(); e.hasMoreElements(); ) + { + importFileset( ( FileSet )e.nextElement() ); + } + } + + /** + * Import all files from the fileset into the Project in the Workspace. + * + * @param fileset Description of Parameter + */ + protected void importFileset( FileSet fileset ) + { + DirectoryScanner ds = fileset.getDirectoryScanner( this.project ); + if( ds.getIncludedFiles().length == 0 ) + { + return; + } + + String[] includes = null; + String[] excludes = null; + + // Hack to get includes and excludes. We could also use getIncludedFiles, + // but that would result in very long HTTP-requests. + // Therefore we want to send the patterns only to the remote tool server + // and let him figure out the files. + try + { + Class directoryScanner = ds.getClass(); + + Field includesField = directoryScanner.getDeclaredField( "includes" ); + includesField.setAccessible( true ); + includes = ( String[] )includesField.get( ds ); + + Field excludesField = directoryScanner.getDeclaredField( "excludes" ); + excludesField.setAccessible( true ); + excludes = ( String[] )excludesField.get( ds ); + } + catch( NoSuchFieldException nsfe ) + { + throw new BuildException( + "DirectoryScanner.includes or .excludes missing" + nsfe.getMessage() ); + } + catch( IllegalAccessException iae ) + { + throw new BuildException( + "Access to DirectoryScanner.includes or .excludes not allowed" ); + } + + getUtil().importFiles( importProject, ds.getBasedir(), + includes, excludes, + importClasses, importResources, importSources, + useDefaultExcludes ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java new file mode 100644 index 000000000..9ea01067e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJImportServlet.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; + + +/** + * A Remote Access to Tools Servlet to import a Project from files into the + * Repository. The following table describes the servlet parameters. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
              + * Parameter + * + * Description + *
              + * project + * + * The name of the project where you want the imported items to go. + *
              + * dir + * + * The directory you want to import from. + *
              + * + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public class VAJImportServlet extends VAJToolsServlet +{ + /** + * Respond to a request to import files to the Repository + */ + protected void executeRequest() + { + getUtil().importFiles( + getFirstParamValueString( PROJECT_NAME_PARAM ), + new File( getFirstParamValueString( DIR_PARAM ) ), + getParamValues( INCLUDE_PARAM ), + getParamValues( EXCLUDE_PARAM ), + getBooleanParam( CLASSES_PARAM, false ), + getBooleanParam( RESOURCES_PARAM, true ), + getBooleanParam( SOURCES_PARAM, true ), + false// no default excludes, because they + // are already added on client side + // getBooleanParam(DEFAULT_EXCLUDES_PARAM, true) + ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java new file mode 100644 index 000000000..d42912f1f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoad.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.util.Vector; + +/** + * Load specific project versions into the Visual Age for Java workspace. Each + * project and version name has to be specified completely. Example: + *

              <vajload>  <project name="MyVAProject" + * version="2.1"/>  <project name="Apache Xerces" version="1.2.0"/> + * </vajload>
              + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +public class VAJLoad extends VAJTask +{ + Vector projectDescriptions = new Vector(); + + /** + * Add a project description entry on the project list. + * + * @return Description of the Returned Value + */ + public VAJProjectDescription createVAJProject() + { + VAJProjectDescription d = new VAJProjectDescription(); + projectDescriptions.addElement( d ); + return d; + } + + /** + * Load specified projects. + */ + public void execute() + { + getUtil().loadProjects( projectDescriptions ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java new file mode 100644 index 000000000..86c9f8a79 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadProjects.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; + + + + + + +/** + * This is only there for backward compatibility with the default task list and + * will be removed soon + * + * @author Wolf Siberski, TUI Infotec GmbH + */ + +public class VAJLoadProjects extends VAJLoad +{ +} + + + + + + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java new file mode 100644 index 000000000..1fca4d63d --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLoadServlet.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.util.Vector; + +/** + * A Remote Access to Tools Servlet to load a Project from the Repository into + * the Workbench. The following table describes the servlet parameters. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
              + * Parameter + * + * Description + *
              + * project + * + * The name of the Project you want to load into the Workbench. + *
              + * version + * + * The version of the package you want to load into the Workbench. + *
              + * + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public class VAJLoadServlet extends VAJToolsServlet +{ + + // constants for servlet param names + public final static String VERSION_PARAM = "version"; + + /** + * Respond to a request to load a project from the Repository into the + * Workbench. + */ + protected void executeRequest() + { + String[] projectNames = getParamValues( PROJECT_NAME_PARAM ); + String[] versionNames = getParamValues( VERSION_PARAM ); + + Vector projectDescriptions = new Vector( projectNames.length ); + for( int i = 0; i < projectNames.length && i < versionNames.length; i++ ) + { + VAJProjectDescription desc = new VAJProjectDescription(); + desc.setName( projectNames[i] ); + desc.setVersion( versionNames[i] ); + projectDescriptions.addElement( desc ); + } + + util.loadProjects( projectDescriptions ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java new file mode 100644 index 000000000..247c2427a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJLocalUtil.java @@ -0,0 +1,530 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import com.ibm.ivj.util.base.ExportCodeSpec; +import com.ibm.ivj.util.base.ImportCodeSpec; +import com.ibm.ivj.util.base.IvjException; +import com.ibm.ivj.util.base.Package; +import com.ibm.ivj.util.base.Project; +import com.ibm.ivj.util.base.ProjectEdition; +import com.ibm.ivj.util.base.ToolEnv; +import com.ibm.ivj.util.base.Type; +import com.ibm.ivj.util.base.Workspace; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; + + +/** + * Helper class for VAJ tasks. Holds Workspace singleton and wraps IvjExceptions + * into BuildExceptions + * + * @author Wolf Siberski, TUI Infotec GmbH + */ +abstract class VAJLocalUtil implements VAJUtil +{ + // singleton containing the VAJ workspace + private static Workspace workspace; + + /** + * get a project from the Workspace. + * + * @param importProject Description of Parameter + * @return The VAJProject value + */ + static Project getVAJProject( String importProject ) + { + Project found = null; + Project[] currentProjects = getWorkspace().getProjects(); + + for( int i = 0; i < currentProjects.length; i++ ) + { + Project p = currentProjects[i]; + if( p.getName().equals( importProject ) ) + { + found = p; + break; + } + } + + if( found == null ) + { + try + { + found = getWorkspace().createProject( importProject, true ); + } + catch( IvjException e ) + { + throw createBuildException( "Error while creating Project " + + importProject + ": ", e ); + } + } + + return found; + } + + /** + * returns the current VAJ workspace. + * + * @return com.ibm.ivj.util.base.Workspace + */ + static Workspace getWorkspace() + { + if( workspace == null ) + { + workspace = ToolEnv.connectToWorkspace(); + if( workspace == null ) + { + throw new BuildException( + "Unable to connect to Workspace! " + + "Make sure you are running in VisualAge for Java." ); + } + } + + return workspace; + } + + /** + * Wraps IvjException into a BuildException + * + * @param errMsg Additional error message + * @param e IvjException which is wrapped + * @return org.apache.tools.ant.BuildException + */ + static BuildException createBuildException( + String errMsg, IvjException e ) + { + errMsg = errMsg + "\n" + e.getMessage(); + String[] errors = e.getErrors(); + if( errors != null ) + { + for( int i = 0; i < errors.length; i++ ) + { + errMsg = errMsg + "\n" + errors[i]; + } + } + return new BuildException( errMsg, e ); + } + + + //----------------------------------------------------------- + // export + //----------------------------------------------------------- + + /** + * export packages + * + * @param dest Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param exportClasses Description of Parameter + * @param exportDebugInfo Description of Parameter + * @param exportResources Description of Parameter + * @param exportSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @param overwrite Description of Parameter + */ + public void exportPackages( + File dest, + String[] includePatterns, String[] excludePatterns, + boolean exportClasses, boolean exportDebugInfo, + boolean exportResources, boolean exportSources, + boolean useDefaultExcludes, boolean overwrite ) + { + if( includePatterns == null || includePatterns.length == 0 ) + { + log( "You must specify at least one include attribute. " + + "Not exporting", MSG_ERR ); + } + else + { + try + { + VAJWorkspaceScanner scanner = new VAJWorkspaceScanner(); + scanner.setIncludes( includePatterns ); + scanner.setExcludes( excludePatterns ); + if( useDefaultExcludes ) + { + scanner.addDefaultExcludes(); + } + scanner.scan(); + + Package[] packages = scanner.getIncludedPackages(); + + log( "Exporting " + packages.length + " package(s) to " + + dest, MSG_INFO ); + for( int i = 0; i < packages.length; i++ ) + { + log( " " + packages[i].getName(), MSG_VERBOSE ); + } + + ExportCodeSpec exportSpec = new ExportCodeSpec(); + exportSpec.setPackages( packages ); + exportSpec.includeJava( exportSources ); + exportSpec.includeClass( exportClasses ); + exportSpec.includeResources( exportResources ); + exportSpec.includeClassDebugInfo( exportDebugInfo ); + exportSpec.useSubdirectories( true ); + exportSpec.overwriteFiles( overwrite ); + exportSpec.setExportDirectory( dest.getAbsolutePath() ); + + getWorkspace().exportData( exportSpec ); + } + catch( IvjException ex ) + { + throw createBuildException( "Exporting failed!", ex ); + } + } + } + + + //----------------------------------------------------------- + // import + //----------------------------------------------------------- + + + /** + * Do the import. + * + * @param importProject Description of Parameter + * @param srcDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param importClasses Description of Parameter + * @param importResources Description of Parameter + * @param importSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @exception BuildException Description of Exception + */ + public void importFiles( + String importProject, File srcDir, + String[] includePatterns, String[] excludePatterns, + boolean importClasses, boolean importResources, + boolean importSources, boolean useDefaultExcludes ) + throws BuildException + { + + if( importProject == null || "".equals( importProject ) ) + { + throw new BuildException( "The VisualAge for Java project " + + "name is required!" ); + } + + ImportCodeSpec importSpec = new ImportCodeSpec(); + importSpec.setDefaultProject( getVAJProject( importProject ) ); + + DirectoryScanner ds = new DirectoryScanner(); + ds.setBasedir( srcDir ); + ds.setIncludes( includePatterns ); + ds.setExcludes( excludePatterns ); + if( useDefaultExcludes ) + { + ds.addDefaultExcludes(); + } + ds.scan(); + + Vector classes = new Vector(); + Vector sources = new Vector(); + Vector resources = new Vector(); + + scanForImport( srcDir, ds.getIncludedFiles(), classes, sources, resources ); + + StringBuffer summaryLog = new StringBuffer( "Importing " ); + addFilesToImport( importSpec, importClasses, classes, "Class", summaryLog ); + addFilesToImport( importSpec, importSources, sources, "Java", summaryLog ); + addFilesToImport( importSpec, importResources, resources, "Resource", summaryLog ); + importSpec.setResourcePath( srcDir.getAbsolutePath() ); + + summaryLog.append( " into the project '" ); + summaryLog.append( importProject ); + summaryLog.append( "'." ); + + log( summaryLog.toString(), MSG_INFO ); + + try + { + Type[] importedTypes = getWorkspace().importData( importSpec ); + if( importedTypes == null ) + { + throw new BuildException( "Unable to import into Workspace!" ); + } + else + { + log( importedTypes.length + " types imported", MSG_DEBUG ); + for( int i = 0; i < importedTypes.length; i++ ) + { + log( importedTypes[i].getPackage().getName() + + "." + importedTypes[i].getName() + + " into " + importedTypes[i].getProject().getName(), + MSG_DEBUG ); + } + } + } + catch( IvjException ivje ) + { + throw createBuildException( "Error while importing into workspace: ", + ivje ); + } + } + + + //----------------------------------------------------------- + // load + //----------------------------------------------------------- + + /** + * Load specified projects. + * + * @param projectDescriptions Description of Parameter + */ + public void loadProjects( Vector projectDescriptions ) + { + Vector expandedDescs = getExpandedDescriptions( projectDescriptions ); + + // output warnings for projects not found + for( Enumeration e = projectDescriptions.elements(); e.hasMoreElements(); ) + { + VAJProjectDescription d = ( VAJProjectDescription )e.nextElement(); + if( !d.projectFound() ) + { + log( "No Projects match the name " + d.getName(), MSG_WARN ); + } + } + + log( "Loading " + expandedDescs.size() + + " project(s) into workspace", MSG_INFO ); + + for( Enumeration e = expandedDescs.elements(); + e.hasMoreElements(); ) + { + VAJProjectDescription d = ( VAJProjectDescription )e.nextElement(); + + ProjectEdition pe = findProjectEdition( d.getName(), d.getVersion() ); + try + { + log( "Loading '" + d.getName() + "', Version '" + d.getVersion() + + "', into Workspace", MSG_VERBOSE ); + pe.loadIntoWorkspace(); + } + catch( IvjException ex ) + { + throw createBuildException( "Project '" + d.getName() + + "' could not be loaded.", ex ); + } + } + } + + + /** + * return project descriptions containing full project names instead of + * patterns with wildcards. + * + * @param projectDescs Description of Parameter + * @return The ExpandedDescriptions value + */ + private Vector getExpandedDescriptions( Vector projectDescs ) + { + Vector expandedDescs = new Vector( projectDescs.size() ); + try + { + String[] projectNames = + getWorkspace().getRepository().getProjectNames(); + for( int i = 0; i < projectNames.length; i++ ) + { + for( Enumeration e = projectDescs.elements(); + e.hasMoreElements(); ) + { + VAJProjectDescription d = ( VAJProjectDescription )e.nextElement(); + String pattern = d.getName(); + if( VAJWorkspaceScanner.match( pattern, projectNames[i] ) ) + { + d.setProjectFound(); + expandedDescs.addElement( new VAJProjectDescription( + projectNames[i], d.getVersion() ) ); + break; + } + } + } + } + catch( IvjException e ) + { + throw createBuildException( "VA Exception occured: ", e ); + } + + return expandedDescs; + } + + /** + * Adds files to an import specification. Helper method for importFiles() + * + * @param spec import specification + * @param doImport only add files if doImport is true + * @param files the files to add + * @param fileType type of files (Source/Class/Resource) + * @param summaryLog buffer for logging + */ + private void addFilesToImport( + ImportCodeSpec spec, boolean doImport, + Vector files, String fileType, + StringBuffer summaryLog ) + { + + if( doImport ) + { + String[] fileArr = new String[files.size()]; + files.copyInto( fileArr ); + try + { + // here it is assumed that fileType is one of the + // following strings: // "Java", "Class", "Resource" + String methodName = "set" + fileType + "Files"; + Class[] methodParams = new Class[]{fileArr.getClass()}; + java.lang.reflect.Method method = + spec.getClass().getDeclaredMethod( methodName, methodParams ); + method.invoke( spec, new Object[]{fileArr} ); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + if( files.size() > 0 ) + { + logFiles( files, fileType ); + summaryLog.append( files.size() ); + summaryLog.append( " " + fileType.toLowerCase() + " file" ); + summaryLog.append( files.size() > 1 ? "s, " : ", " ); + } + } + } + + /** + * returns a list of project names matching the given pattern + * + * @param pattern Description of Parameter + * @return Description of the Returned Value + */ + private Vector findMatchingProjects( String pattern ) + { + String[] projectNames; + try + { + projectNames = getWorkspace().getRepository().getProjectNames(); + } + catch( IvjException e ) + { + throw createBuildException( "VA Exception occured: ", e ); + } + + Vector matchingProjects = new Vector(); + for( int i = 0; i < projectNames.length; i++ ) + { + if( VAJWorkspaceScanner.match( pattern, projectNames[i] ) ) + { + matchingProjects.addElement( projectNames[i] ); + } + } + + return matchingProjects; + } + + /** + * Finds a specific project edition in the repository. + * + * @param name project name + * @param versionName project version name + * @return com.ibm.ivj.util.base.ProjectEdition the specified edition + */ + private ProjectEdition findProjectEdition( + String name, String versionName ) + { + try + { + ProjectEdition[] editions = null; + editions = getWorkspace().getRepository().getProjectEditions( name ); + + if( editions == null ) + { + throw new BuildException( "Project " + name + " doesn't exist" ); + } + + ProjectEdition pe = null; + for( int i = 0; i < editions.length && pe == null; i++ ) + { + if( versionName.equals( editions[i].getVersionName() ) ) + { + pe = editions[i]; + } + } + if( pe == null ) + { + throw new BuildException( "Version " + versionName + + " of Project " + name + " doesn't exist" ); + } + return pe; + } + catch( IvjException e ) + { + throw createBuildException( "VA Exception occured: ", e ); + } + + } + + /** + * Logs a list of file names to the message log + * + * @param fileNames java.util.Vector file names to be logged + * @param fileType Description of Parameter + */ + private void logFiles( Vector fileNames, String fileType ) + { + log( fileType + " files found for import:", MSG_VERBOSE ); + for( Enumeration e = fileNames.elements(); e.hasMoreElements(); ) + { + log( " " + e.nextElement(), MSG_VERBOSE ); + } + } + + + /** + * Sort the files into classes, sources, and resources. + * + * @param dir Description of Parameter + * @param files Description of Parameter + * @param classes Description of Parameter + * @param sources Description of Parameter + * @param resources Description of Parameter + */ + private void scanForImport( + File dir, + String[] files, + Vector classes, + Vector sources, + Vector resources ) + { + for( int i = 0; i < files.length; i++ ) + { + String file = ( new File( dir, files[i] ) ).getAbsolutePath(); + if( file.endsWith( ".java" ) || file.endsWith( ".JAVA" ) ) + { + sources.addElement( file ); + } + else + if( file.endsWith( ".class" ) || file.endsWith( ".CLASS" ) ) + { + classes.addElement( file ); + } + else + { + // for resources VA expects the path relative to the resource path + resources.addElement( files[i] ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java new file mode 100644 index 000000000..9e2105863 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJProjectDescription.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import org.apache.tools.ant.BuildException; + +/** + * Type class. Holds information about a project edition. + * + * @author RT + * @author: Wolf Siberski + */ +public class VAJProjectDescription +{ + private String name; + private boolean projectFound; + private String version; + + public VAJProjectDescription() { } + + public VAJProjectDescription( String n, String v ) + { + name = n; + version = v; + } + + public void setName( String newName ) + { + if( newName == null || newName.equals( "" ) ) + { + throw new BuildException( "name attribute must be set" ); + } + name = newName; + } + + public void setProjectFound() + { + projectFound = true; + } + + public void setVersion( String newVersion ) + { + if( newVersion == null || newVersion.equals( "" ) ) + { + throw new BuildException( "version attribute must be set" ); + } + version = newVersion; + } + + public String getName() + { + return name; + } + + public String getVersion() + { + return version; + } + + public boolean projectFound() + { + return projectFound; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java new file mode 100644 index 000000000..c14253574 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJRemoteUtil.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + +/** + * Helper class for VAJ tasks. Holds Workspace singleton and wraps IvjExceptions + * into BuildExceptions + * + * @author Wolf Siberski, TUI Infotec GmbH + */ +class VAJRemoteUtil implements VAJUtil +{ + // calling task + Task caller; + + // VAJ remote tool server + String remoteServer; + + public VAJRemoteUtil( Task caller, String remote ) + { + this.caller = caller; + this.remoteServer = remote; + } + + /** + * export the array of Packages + * + * @param destDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param exportClasses Description of Parameter + * @param exportDebugInfo Description of Parameter + * @param exportResources Description of Parameter + * @param exportSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @param overwrite Description of Parameter + */ + public void exportPackages( File destDir, + String[] includePatterns, String[] excludePatterns, + boolean exportClasses, boolean exportDebugInfo, boolean exportResources, + boolean exportSources, boolean useDefaultExcludes, boolean overwrite ) + { + try + { + String request = "http://" + remoteServer + "/servlet/vajexport?" + + VAJExportServlet.WITH_DEBUG_INFO + "=" + exportDebugInfo + "&" + + VAJExportServlet.OVERWRITE_PARAM + "=" + overwrite + "&" + + assembleImportExportParams( destDir, + includePatterns, excludePatterns, + exportClasses, exportResources, + exportSources, useDefaultExcludes ); + sendRequest( request ); + } + catch( Exception ex ) + { + throw new BuildException( ex ); + } + } + + /** + * Do the import. + * + * @param importProject Description of Parameter + * @param srcDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param importClasses Description of Parameter + * @param importResources Description of Parameter + * @param importSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + */ + public void importFiles( + String importProject, File srcDir, + String[] includePatterns, String[] excludePatterns, + boolean importClasses, boolean importResources, + boolean importSources, boolean useDefaultExcludes ) + { + try + { + String request = "http://" + remoteServer + "/servlet/vajimport?" + + VAJImportServlet.PROJECT_NAME_PARAM + "=" + + importProject + "&" + + assembleImportExportParams( srcDir, + includePatterns, excludePatterns, + importClasses, importResources, + importSources, useDefaultExcludes ); + sendRequest( request ); + } + catch( Exception ex ) + { + throw new BuildException( ex ); + } + + } + + /** + * Load specified projects. + * + * @param projectDescriptions Description of Parameter + */ + public void loadProjects( Vector projectDescriptions ) + { + try + { + String request = "http://" + remoteServer + "/servlet/vajload?"; + String delimiter = ""; + for( Enumeration e = projectDescriptions.elements(); e.hasMoreElements(); ) + { + VAJProjectDescription pd = ( VAJProjectDescription )e.nextElement(); + request = request + + delimiter + VAJLoadServlet.PROJECT_NAME_PARAM + + "=" + pd.getName().replace( ' ', '+' ) + + "&" + VAJLoadServlet.VERSION_PARAM + + "=" + pd.getVersion().replace( ' ', '+' ); + //the first param needs no delimiter, but all other + delimiter = "&"; + } + sendRequest( request ); + } + catch( Exception ex ) + { + throw new BuildException( ex ); + } + } + + /** + * logs a message. + * + * @param msg Description of Parameter + * @param level Description of Parameter + */ + public void log( String msg, int level ) + { + caller.log( msg, level ); + } + + /** + * Assemble string for parameters common for import and export Helper method + * to remove double code. + * + * @param dir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param includeClasses Description of Parameter + * @param includeResources Description of Parameter + * @param includeSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @return Description of the Returned Value + */ + private String assembleImportExportParams( + File dir, + String[] includePatterns, String[] excludePatterns, + boolean includeClasses, boolean includeResources, + boolean includeSources, boolean useDefaultExcludes ) + { + String result = + VAJToolsServlet.DIR_PARAM + "=" + + dir.getAbsolutePath().replace( '\\', '/' ) + "&" + + VAJToolsServlet.CLASSES_PARAM + "=" + includeClasses + "&" + + VAJToolsServlet.RESOURCES_PARAM + "=" + includeResources + "&" + + VAJToolsServlet.SOURCES_PARAM + "=" + includeSources + "&" + + VAJToolsServlet.DEFAULT_EXCLUDES_PARAM + "=" + useDefaultExcludes; + + if( includePatterns != null ) + { + for( int i = 0; i < includePatterns.length; i++ ) + { + result = result + "&" + VAJExportServlet.INCLUDE_PARAM + "=" + + includePatterns[i].replace( ' ', '+' ).replace( '\\', '/' ); + } + } + if( excludePatterns != null ) + { + for( int i = 0; i < excludePatterns.length; i++ ) + { + result = result + "&" + VAJExportServlet.EXCLUDE_PARAM + "=" + + excludePatterns[i].replace( ' ', '+' ).replace( '\\', '/' ); + } + } + + return result; + } + + /** + * Sends a servlet request. + * + * @param request Description of Parameter + */ + private void sendRequest( String request ) + { + boolean requestFailed = false; + try + { + log( "Request: " + request, MSG_DEBUG ); + + //must be HTTP connection + URL requestUrl = new URL( request ); + HttpURLConnection connection = + ( HttpURLConnection )requestUrl.openConnection(); + + InputStream is = null; + // retry three times + for( int i = 0; i < 3; i++ ) + { + try + { + is = connection.getInputStream(); + break; + } + catch( IOException ex ) + { + } + } + if( is == null ) + { + log( "Can't get " + request, MSG_ERR ); + throw new BuildException( "Couldn't execute " + request ); + } + + // log the response + BufferedReader br = new BufferedReader( new InputStreamReader( is ) ); + String line = br.readLine(); + while( line != null ) + { + int level = MSG_ERR; + try + { + // the first char of each line contains the log level + level = Integer.parseInt( line.substring( 0, 1 ) ); + if( level == MSG_ERR ) + { + requestFailed = true; + } + } + catch( Exception e ) + { + log( "Response line doesn't contain log level!", MSG_ERR ); + } + log( line.substring( 2 ), level ); + line = br.readLine(); + } + + } + catch( IOException ex ) + { + log( "Error sending tool request to VAJ" + ex, MSG_ERR ); + throw new BuildException( "Couldn't execute " + request ); + } + if( requestFailed ) + { + throw new BuildException( "VAJ tool request failed" ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java new file mode 100644 index 000000000..3c19052ec --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJTask.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +/** + * Super class for all VAJ tasks. Contains common attributes (remoteServer) and + * util methods + * + * @author: Wolf Siberski + */ +import org.apache.tools.ant.Task; + + +public class VAJTask extends Task +{ + + // server name / port of VAJ remote tool api server + protected String remoteServer = null; + + // holds the appropriate VAJUtil implementation + private VAJUtil util = null; + + /** + * Set remote server attribute + * + * @param remoteServer The new Remote value + */ + public void setRemote( String remoteServer ) + { + this.remoteServer = remoteServer; + } + + + /** + * returns the VAJUtil implementation + * + * @return The Util value + */ + protected VAJUtil getUtil() + { + if( util == null ) + { + if( remoteServer == null ) + { + util = new VAJLocalToolUtil(); + } + else + { + util = new VAJRemoteUtil( this, remoteServer ); + } + } + return util; + } + + /** + * Adaption of VAJLocalUtil to Task context. + * + * @author RT + */ + class VAJLocalToolUtil extends VAJLocalUtil + { + public void log( String msg, int level ) + { + VAJTask.this.log( msg, level ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java new file mode 100644 index 000000000..c27e163b9 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJToolsServlet.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.StringUtils; + +/** + * Abstract base class to provide common services for the VAJ tool API servlets + * + * @author Wolf Siberski, based on servlets written by Glenn McAllister + */ +public abstract class VAJToolsServlet extends HttpServlet +{ + + // constants for servlet param names + public final static String DIR_PARAM = "dir"; + public final static String INCLUDE_PARAM = "include"; + public final static String EXCLUDE_PARAM = "exclude"; + public final static String CLASSES_PARAM = "cls"; + public final static String SOURCES_PARAM = "src"; + public final static String RESOURCES_PARAM = "res"; + public final static String DEFAULT_EXCLUDES_PARAM = "dex"; + public final static String PROJECT_NAME_PARAM = "project"; + + // current request + HttpServletRequest request; + + // response to current request + HttpServletResponse response; + + // implementation of VAJUtil used by the servlet + VAJUtil util; + + /** + * Respond to a HTTP request. This method initializes the servlet and + * handles errors. The real work is done in the abstract method + * executeRequest() + * + * @param req Description of Parameter + * @param res Description of Parameter + * @exception ServletException Description of Exception + * @exception IOException Description of Exception + */ + public void doGet( HttpServletRequest req, HttpServletResponse res ) + throws ServletException, IOException + { + try + { + response = res; + request = req; + initRequest(); + executeRequest(); + } + catch( BuildException e ) + { + util.log( "Error occured: " + e.getMessage(), VAJUtil.MSG_ERR ); + } + catch( Exception e ) + { + try + { + if( !( e instanceof BuildException ) ) + { + String trace = StringUtils.getStackTrace( e ); + util.log( "Program error in " + this.getClass().getName() + + ":\n" + trace, VAJUtil.MSG_ERR ); + } + } + catch( Throwable t ) + { + t.printStackTrace(); + } + finally + { + if( !( e instanceof BuildException ) ) + { + throw new ServletException( e.getMessage() ); + } + } + } + } + + /** + * Get the boolean value of a parameter. + * + * @param param Description of Parameter + * @return The BooleanParam value + */ + protected boolean getBooleanParam( String param ) + { + return getBooleanParam( param, false ); + } + + /** + * Get the boolean value of a parameter, with a default value if the + * parameter hasn't been passed to the servlet. + * + * @param param Description of Parameter + * @param defaultValue Description of Parameter + * @return The BooleanParam value + */ + protected boolean getBooleanParam( String param, boolean defaultValue ) + { + String value = getFirstParamValueString( param ); + if( value != null ) + { + return toBoolean( value ); + } + else + { + return defaultValue; + } + } + + /** + * Returns the first encountered value for a parameter. + * + * @param param Description of Parameter + * @return The FirstParamValueString value + */ + protected String getFirstParamValueString( String param ) + { + String[] paramValuesArray = request.getParameterValues( param ); + if( paramValuesArray == null ) + { + return null; + } + return paramValuesArray[0]; + } + + /** + * Returns all values for a parameter. + * + * @param param Description of Parameter + * @return The ParamValues value + */ + protected String[] getParamValues( String param ) + { + return request.getParameterValues( param ); + } + + + /** + * Execute the request by calling the appropriate VAJ tool API methods. This + * method must be implemented by the concrete servlets + */ + protected abstract void executeRequest(); + + /** + * initialize the servlet. + * + * @exception IOException Description of Exception + */ + protected void initRequest() + throws IOException + { + response.setContentType( "text/ascii" ); + if( util == null ) + { + util = new VAJLocalServletUtil(); + } + } + + /** + * A utility method to translate the strings "yes", "true", and "ok" to + * boolean true, and everything else to false. + * + * @param string Description of Parameter + * @return Description of the Returned Value + */ + protected boolean toBoolean( String string ) + { + String lower = string.toLowerCase(); + return ( lower.equals( "yes" ) || lower.equals( "true" ) || lower.equals( "ok" ) ); + } + + /** + * Get the VAJUtil implementation + * + * @return The Util value + */ + VAJUtil getUtil() + { + return util; + } + + /** + * Adaptation of VAJUtil for servlet context. + * + * @author RT + */ + class VAJLocalServletUtil extends VAJLocalUtil + { + public void log( String msg, int level ) + { + try + { + if( msg != null ) + { + msg = msg.replace( '\r', ' ' ); + int i = 0; + while( i < msg.length() ) + { + int nlPos = msg.indexOf( '\n', i ); + if( nlPos == -1 ) + { + nlPos = msg.length(); + } + response.getWriter().println( Integer.toString( level ) + + " " + msg.substring( i, nlPos ) ); + i = nlPos + 1; + } + } + } + catch( IOException e ) + { + throw new BuildException( "logging failed. msg was: " + + e.getMessage() ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java new file mode 100644 index 000000000..203b97252 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJUtil.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import java.io.File; +import java.util.Vector; + +/** + * Helper interface for VAJ tasks. Encapsulates the interface to the VAJ tool + * API. + * + * @author Wolf Siberski, TUI Infotec GmbH + */ +interface VAJUtil +{ + // log levels + public final static int MSG_DEBUG = 4; + public final static int MSG_ERR = 0; + public final static int MSG_INFO = 2; + public final static int MSG_VERBOSE = 3; + public final static int MSG_WARN = 1; + + /** + * export the array of Packages + * + * @param dest Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param exportClasses Description of Parameter + * @param exportDebugInfo Description of Parameter + * @param exportResources Description of Parameter + * @param exportSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + * @param overwrite Description of Parameter + */ + void exportPackages( + File dest, + String[] includePatterns, String[] excludePatterns, + boolean exportClasses, boolean exportDebugInfo, + boolean exportResources, boolean exportSources, + boolean useDefaultExcludes, boolean overwrite ); + + /** + * Do the import. + * + * @param importProject Description of Parameter + * @param srcDir Description of Parameter + * @param includePatterns Description of Parameter + * @param excludePatterns Description of Parameter + * @param importClasses Description of Parameter + * @param importResources Description of Parameter + * @param importSources Description of Parameter + * @param useDefaultExcludes Description of Parameter + */ + void importFiles( + String importProject, File srcDir, + String[] includePatterns, String[] excludePatterns, + boolean importClasses, boolean importResources, + boolean importSources, boolean useDefaultExcludes ); + + /** + * Load specified projects. + * + * @param projectDescriptions Description of Parameter + */ + void loadProjects( Vector projectDescriptions ); + + /** + * Logs a message with the specified log level. + * + * @param msg Description of Parameter + * @param level Description of Parameter + */ + void log( String msg, int level ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java new file mode 100644 index 000000000..9624722af --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/VAJWorkspaceScanner.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.ide; +import com.ibm.ivj.util.base.IvjException; +import com.ibm.ivj.util.base.Package; +import com.ibm.ivj.util.base.Project; +import java.io.File; +import java.util.Enumeration; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.DirectoryScanner; + +/** + * Class for scanning a Visual Age for Java workspace for packages matching a + * certain criteria.

              + * + * These criteria consist of a set of include and exclude patterns. With these + * patterns, you can select which packages you want to have included, and which + * packages you want to have excluded. You can add patterns to be excluded by + * default with the addDefaultExcludes method. The patters that are excluded by + * default include + *

                + *
              • IBM*\**
              • + *
              • Java class libraries\**
              • + *
              • Sun class libraries*\**
              • + *
              • JSP Page Compile Generated Code\**
              • + *
              • VisualAge*\**
              • + *
              + *

              + * + * This class works like DirectoryScanner. + * + * @author Wolf Siberski, TUI Infotec (based on Arnout J. Kuipers + * DirectoryScanner) + * @see org.apache.tools.ant.DirectoryScanner + */ +class VAJWorkspaceScanner extends DirectoryScanner +{ + + // Patterns that should be excluded by default. + private final static String[] DEFAULTEXCLUDES = + { + "IBM*/**", + "Java class libraries/**", + "Sun class libraries*/**", + "JSP Page Compile Generated Code/**", + "VisualAge*/**", + }; + + // The packages that where found and matched at least + // one includes, and matched no excludes. + private Vector packagesIncluded = new Vector(); + + /** + * Matches a string against a pattern. The pattern contains two special + * characters: '*' which means zero or more characters, '?' which means one + * and only one character. + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string that must be matched against the pattern + * @return true when the string matches against the pattern, + * false otherwise. + */ + protected static boolean match( String pattern, String str ) + { + return DirectoryScanner.match( pattern, str ); + } + + /** + * Get the names of the packages that matched at least one of the include + * patterns, and didn't match one of the exclude patterns. + * + * @return the matching packages + */ + public Package[] getIncludedPackages() + { + int count = packagesIncluded.size(); + Package[] packages = new Package[count]; + for( int i = 0; i < count; i++ ) + { + packages[i] = ( Package )packagesIncluded.elementAt( i ); + } + return packages; + } + + /** + * Adds the array with default exclusions to the current exclusions set. + */ + public void addDefaultExcludes() + { + int excludesLength = excludes == null ? 0 : excludes.length; + String[] newExcludes; + newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length]; + if( excludesLength > 0 ) + { + System.arraycopy( excludes, 0, newExcludes, 0, excludesLength ); + } + for( int i = 0; i < DEFAULTEXCLUDES.length; i++ ) + { + newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i]. + replace( '/', File.separatorChar ). + replace( '\\', File.separatorChar ); + } + excludes = newExcludes; + } + + /** + * Finds all Projects specified in include patterns. + * + * @return the projects + */ + public Vector findMatchingProjects() + { + Project[] projects = VAJLocalUtil.getWorkspace().getProjects(); + + Vector matchingProjects = new Vector(); + + boolean allProjectsMatch = false; + for( int i = 0; i < projects.length; i++ ) + { + Project project = projects[i]; + for( int j = 0; j < includes.length && !allProjectsMatch; j++ ) + { + StringTokenizer tok = + new StringTokenizer( includes[j], File.separator ); + String projectNamePattern = tok.nextToken(); + if( projectNamePattern.equals( "**" ) ) + { + // if an include pattern starts with '**', + // all projects match + allProjectsMatch = true; + } + else + if( match( projectNamePattern, project.getName() ) ) + { + matchingProjects.addElement( project ); + break; + } + } + } + + if( allProjectsMatch ) + { + matchingProjects = new Vector(); + for( int i = 0; i < projects.length; i++ ) + { + matchingProjects.addElement( projects[i] ); + } + } + + return matchingProjects; + } + + /** + * Scans the workspace for packages that match at least one include pattern, + * and don't match any exclude patterns. + */ + public void scan() + { + if( includes == null ) + { + // No includes supplied, so set it to 'matches all' + includes = new String[1]; + includes[0] = "**"; + } + if( excludes == null ) + { + excludes = new String[0]; + } + + // only scan projects which are included in at least one include pattern + Vector matchingProjects = findMatchingProjects(); + for( Enumeration e = matchingProjects.elements(); e.hasMoreElements(); ) + { + Project project = ( Project )e.nextElement(); + scanProject( project ); + } + } + + /** + * Scans a project for packages that match at least one include pattern, and + * don't match any exclude patterns. + * + * @param project Description of Parameter + */ + public void scanProject( Project project ) + { + try + { + Package[] packages = project.getPackages(); + if( packages != null ) + { + for( int i = 0; i < packages.length; i++ ) + { + Package item = packages[i]; + // replace '.' by file seperator because the patterns are + // using file seperator syntax (and we can use the match + // methods this way). + String name = + project.getName() + + File.separator + + item.getName().replace( '.', File.separatorChar ); + if( isIncluded( name ) && !isExcluded( name ) ) + { + packagesIncluded.addElement( item ); + } + } + } + } + catch( IvjException e ) + { + throw VAJLocalUtil.createBuildException( "VA Exception occured: ", e ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/default.ini b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/default.ini new file mode 100644 index 000000000..1ccb8944f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/ide/default.ini @@ -0,0 +1,4 @@ +Name=Ant +Version=0.1 +Help-Item=Ant Help,doc/VAJAntTool.html +Menu-Items=Ant Build,org.apache.tools.ant.taskdefs.optional.ide.VAJAntTool,-P; diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java new file mode 100644 index 000000000..912a18bb9 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JJTree.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.javacc; +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Path; + +/** + * Taskdef for the JJTree compiler compiler. + * + * @author thomas.haas@softwired-inc.com + * @author Michael Saunders michael@amtec.com + * + */ +public class JJTree extends Task +{ + + // keys to optional attributes + private final static String BUILD_NODE_FILES = "BUILD_NODE_FILES"; + private final static String MULTI = "MULTI"; + private final static String NODE_DEFAULT_VOID = "NODE_DEFAULT_VOID"; + private final static String NODE_FACTORY = "NODE_FACTORY"; + private final static String NODE_SCOPE_HOOK = "NODE_SCOPE_HOOK"; + private final static String NODE_USES_PARSER = "NODE_USES_PARSER"; + private final static String STATIC = "STATIC"; + private final static String VISITOR = "VISITOR"; + + private final static String NODE_PACKAGE = "NODE_PACKAGE"; + private final static String VISITOR_EXCEPTION = "VISITOR_EXCEPTION"; + private final static String NODE_PREFIX = "NODE_PREFIX"; + + private final Hashtable optionalAttrs = new Hashtable(); + + // required attributes + private File outputDirectory = null; + private File target = null; + private File javaccHome = null; + + private CommandlineJava cmdl = new CommandlineJava(); + + public JJTree() + { + cmdl.setVm( "java" ); + cmdl.setClassname( "COM.sun.labs.jjtree.Main" ); + } + + + public void setBuildnodefiles( boolean buildNodeFiles ) + { + optionalAttrs.put( BUILD_NODE_FILES, new Boolean( buildNodeFiles ) ); + } + + public void setJavacchome( File javaccHome ) + { + this.javaccHome = javaccHome; + } + + public void setMulti( boolean multi ) + { + optionalAttrs.put( MULTI, new Boolean( multi ) ); + } + + public void setNodedefaultvoid( boolean nodeDefaultVoid ) + { + optionalAttrs.put( NODE_DEFAULT_VOID, new Boolean( nodeDefaultVoid ) ); + } + + public void setNodefactory( boolean nodeFactory ) + { + optionalAttrs.put( NODE_FACTORY, new Boolean( nodeFactory ) ); + } + + public void setNodepackage( String nodePackage ) + { + optionalAttrs.put( NODE_PACKAGE, new String( nodePackage ) ); + } + + public void setNodeprefix( String nodePrefix ) + { + optionalAttrs.put( NODE_PREFIX, new String( nodePrefix ) ); + } + + public void setNodescopehook( boolean nodeScopeHook ) + { + optionalAttrs.put( NODE_SCOPE_HOOK, new Boolean( nodeScopeHook ) ); + } + + public void setNodeusesparser( boolean nodeUsesParser ) + { + optionalAttrs.put( NODE_USES_PARSER, new Boolean( nodeUsesParser ) ); + } + + public void setOutputdirectory( File outputDirectory ) + { + this.outputDirectory = outputDirectory; + } + + public void setStatic( boolean staticParser ) + { + optionalAttrs.put( STATIC, new Boolean( staticParser ) ); + } + + public void setTarget( File target ) + { + this.target = target; + } + + public void setVisitor( boolean visitor ) + { + optionalAttrs.put( VISITOR, new Boolean( visitor ) ); + } + + public void setVisitorException( String visitorException ) + { + optionalAttrs.put( VISITOR_EXCEPTION, new String( visitorException ) ); + } + + public void execute() + throws BuildException + { + + // load command line with optional attributes + Enumeration iter = optionalAttrs.keys(); + while( iter.hasMoreElements() ) + { + String name = ( String )iter.nextElement(); + Object value = optionalAttrs.get( name ); + cmdl.createArgument().setValue( "-" + name + ":" + value.toString() ); + } + + if( target == null || !target.isFile() ) + { + throw new BuildException( "Invalid target: " + target ); + } + + // use the directory containing the target as the output directory + if( outputDirectory == null ) + { + outputDirectory = new File( target.getParent() ); + } + if( !outputDirectory.isDirectory() ) + { + throw new BuildException( "'outputdirectory' " + outputDirectory + " is not a directory." ); + } + // convert backslashes to slashes, otherwise jjtree will put this as + // comments and this seems to confuse javacc + cmdl.createArgument().setValue( + "-OUTPUT_DIRECTORY:" + outputDirectory.getAbsolutePath().replace( '\\', '/' ) ); + + String targetName = target.getName(); + final File javaFile = new File( outputDirectory, + targetName.substring( 0, targetName.indexOf( ".jjt" ) ) + ".jj" ); + if( javaFile.exists() && target.lastModified() < javaFile.lastModified() ) + { + project.log( "Target is already built - skipping (" + target + ")" ); + return; + } + cmdl.createArgument().setValue( target.getAbsolutePath() ); + + if( javaccHome == null || !javaccHome.isDirectory() ) + { + throw new BuildException( "Javacchome not set." ); + } + final Path classpath = cmdl.createClasspath( project ); + classpath.createPathElement().setPath( javaccHome.getAbsolutePath() + + "/JavaCC.zip" ); + classpath.addJavaRuntime(); + + final Commandline.Argument arg = cmdl.createVmArgument(); + arg.setValue( "-mx140M" ); + arg.setValue( "-Dinstall.root=" + javaccHome.getAbsolutePath() ); + + final Execute process = + new Execute( new LogStreamHandler( this, + Project.MSG_INFO, + Project.MSG_INFO ), + null ); + log( cmdl.toString(), Project.MSG_VERBOSE ); + process.setCommandline( cmdl.getCommandline() ); + + try + { + if( process.execute() != 0 ) + { + throw new BuildException( "JJTree failed." ); + } + } + catch( IOException e ) + { + throw new BuildException( "Failed to launch JJTree: " + e ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java new file mode 100644 index 000000000..f85e9089c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/javacc/JavaCC.java @@ -0,0 +1,278 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.javacc; +import java.io.File; +import java.util.Enumeration; +import java.util.Hashtable; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.Path; + +/** + * Taskdef for the JavaCC compiler compiler. + * + * @author thomas.haas@softwired-inc.com + * @author Michael Saunders michael@amtec.com + * + */ +public class JavaCC extends Task +{ + + // keys to optional attributes + private final static String LOOKAHEAD = "LOOKAHEAD"; + private final static String CHOICE_AMBIGUITY_CHECK = "CHOICE_AMBIGUITY_CHECK"; + private final static String OTHER_AMBIGUITY_CHECK = "OTHER_AMBIGUITY_CHECK"; + + private final static String STATIC = "STATIC"; + private final static String DEBUG_PARSER = "DEBUG_PARSER"; + private final static String DEBUG_LOOKAHEAD = "DEBUG_LOOKAHEAD"; + private final static String DEBUG_TOKEN_MANAGER = "DEBUG_TOKEN_MANAGER"; + private final static String OPTIMIZE_TOKEN_MANAGER = "OPTIMIZE_TOKEN_MANAGER"; + private final static String ERROR_REPORTING = "ERROR_REPORTING"; + private final static String JAVA_UNICODE_ESCAPE = "JAVA_UNICODE_ESCAPE"; + private final static String UNICODE_INPUT = "UNICODE_INPUT"; + private final static String IGNORE_CASE = "IGNORE_CASE"; + private final static String COMMON_TOKEN_ACTION = "COMMON_TOKEN_ACTION"; + private final static String USER_TOKEN_MANAGER = "USER_TOKEN_MANAGER"; + private final static String USER_CHAR_STREAM = "USER_CHAR_STREAM"; + private final static String BUILD_PARSER = "BUILD_PARSER"; + private final static String BUILD_TOKEN_MANAGER = "BUILD_TOKEN_MANAGER"; + private final static String SANITY_CHECK = "SANITY_CHECK"; + private final static String FORCE_LA_CHECK = "FORCE_LA_CHECK"; + private final static String CACHE_TOKENS = "CACHE_TOKENS"; + + private final Hashtable optionalAttrs = new Hashtable(); + + // required attributes + private File outputDirectory = null; + private File target = null; + private File javaccHome = null; + + private CommandlineJava cmdl = new CommandlineJava(); + + public JavaCC() + { + cmdl.setVm( "java" ); + cmdl.setClassname( "COM.sun.labs.javacc.Main" ); + } + + public void setBuildparser( boolean buildParser ) + { + optionalAttrs.put( BUILD_PARSER, new Boolean( buildParser ) ); + } + + public void setBuildtokenmanager( boolean buildTokenManager ) + { + optionalAttrs.put( BUILD_TOKEN_MANAGER, new Boolean( buildTokenManager ) ); + } + + public void setCachetokens( boolean cacheTokens ) + { + optionalAttrs.put( CACHE_TOKENS, new Boolean( cacheTokens ) ); + } + + public void setChoiceambiguitycheck( int choiceAmbiguityCheck ) + { + optionalAttrs.put( CHOICE_AMBIGUITY_CHECK, new Integer( choiceAmbiguityCheck ) ); + } + + public void setCommontokenaction( boolean commonTokenAction ) + { + optionalAttrs.put( COMMON_TOKEN_ACTION, new Boolean( commonTokenAction ) ); + } + + public void setDebuglookahead( boolean debugLookahead ) + { + optionalAttrs.put( DEBUG_LOOKAHEAD, new Boolean( debugLookahead ) ); + } + + public void setDebugparser( boolean debugParser ) + { + optionalAttrs.put( DEBUG_PARSER, new Boolean( debugParser ) ); + } + + public void setDebugtokenmanager( boolean debugTokenManager ) + { + optionalAttrs.put( DEBUG_TOKEN_MANAGER, new Boolean( debugTokenManager ) ); + } + + public void setErrorreporting( boolean errorReporting ) + { + optionalAttrs.put( ERROR_REPORTING, new Boolean( errorReporting ) ); + } + + public void setForcelacheck( boolean forceLACheck ) + { + optionalAttrs.put( FORCE_LA_CHECK, new Boolean( forceLACheck ) ); + } + + public void setIgnorecase( boolean ignoreCase ) + { + optionalAttrs.put( IGNORE_CASE, new Boolean( ignoreCase ) ); + } + + public void setJavacchome( File javaccHome ) + { + this.javaccHome = javaccHome; + } + + public void setJavaunicodeescape( boolean javaUnicodeEscape ) + { + optionalAttrs.put( JAVA_UNICODE_ESCAPE, new Boolean( javaUnicodeEscape ) ); + } + + + public void setLookahead( int lookahead ) + { + optionalAttrs.put( LOOKAHEAD, new Integer( lookahead ) ); + } + + public void setOptimizetokenmanager( boolean optimizeTokenManager ) + { + optionalAttrs.put( OPTIMIZE_TOKEN_MANAGER, new Boolean( optimizeTokenManager ) ); + } + + public void setOtherambiguityCheck( int otherAmbiguityCheck ) + { + optionalAttrs.put( OTHER_AMBIGUITY_CHECK, new Integer( otherAmbiguityCheck ) ); + } + + public void setOutputdirectory( File outputDirectory ) + { + this.outputDirectory = outputDirectory; + } + + public void setSanitycheck( boolean sanityCheck ) + { + optionalAttrs.put( SANITY_CHECK, new Boolean( sanityCheck ) ); + } + + public void setStatic( boolean staticParser ) + { + optionalAttrs.put( STATIC, new Boolean( staticParser ) ); + } + + public void setTarget( File target ) + { + this.target = target; + } + + public void setUnicodeinput( boolean unicodeInput ) + { + optionalAttrs.put( UNICODE_INPUT, new Boolean( unicodeInput ) ); + } + + public void setUsercharstream( boolean userCharStream ) + { + optionalAttrs.put( USER_CHAR_STREAM, new Boolean( userCharStream ) ); + } + + public void setUsertokenmanager( boolean userTokenManager ) + { + optionalAttrs.put( USER_TOKEN_MANAGER, new Boolean( userTokenManager ) ); + } + + public void execute() + throws BuildException + { + + // load command line with optional attributes + Enumeration iter = optionalAttrs.keys(); + while( iter.hasMoreElements() ) + { + String name = ( String )iter.nextElement(); + Object value = optionalAttrs.get( name ); + cmdl.createArgument().setValue( "-" + name + ":" + value.toString() ); + } + + // check the target is a file + if( target == null || !target.isFile() ) + { + throw new BuildException( "Invalid target: " + target ); + } + + // use the directory containing the target as the output directory + if( outputDirectory == null ) + { + outputDirectory = new File( target.getParent() ); + } + else if( !outputDirectory.isDirectory() ) + { + throw new BuildException( "Outputdir not a directory." ); + } + cmdl.createArgument().setValue( + "-OUTPUT_DIRECTORY:" + outputDirectory.getAbsolutePath() ); + + // determine if the generated java file is up-to-date + final File javaFile = getOutputJavaFile( outputDirectory, target ); + if( javaFile.exists() && target.lastModified() < javaFile.lastModified() ) + { + log( "Target is already built - skipping (" + target + ")", Project.MSG_VERBOSE ); + return; + } + cmdl.createArgument().setValue( target.getAbsolutePath() ); + + if( javaccHome == null || !javaccHome.isDirectory() ) + { + throw new BuildException( "Javacchome not set." ); + } + final Path classpath = cmdl.createClasspath( project ); + classpath.createPathElement().setPath( javaccHome.getAbsolutePath() + + "/JavaCC.zip" ); + classpath.addJavaRuntime(); + + final Commandline.Argument arg = cmdl.createVmArgument(); + arg.setValue( "-mx140M" ); + arg.setValue( "-Dinstall.root=" + javaccHome.getAbsolutePath() ); + + Execute.runCommand( this, cmdl.getCommandline() ); + } + + /** + * Determines the output Java file to be generated by the given grammar + * file. + * + * @param outputdir Description of Parameter + * @param srcfile Description of Parameter + * @return The OutputJavaFile value + */ + private File getOutputJavaFile( File outputdir, File srcfile ) + { + String path = srcfile.getPath(); + + // Extract file's base-name + int startBasename = path.lastIndexOf( File.separator ); + if( startBasename != -1 ) + { + path = path.substring( startBasename + 1 ); + } + + // Replace the file's extension with '.java' + int startExtn = path.lastIndexOf( '.' ); + if( startExtn != -1 ) + { + path = path.substring( 0, startExtn ) + ".java"; + } + else + { + path += ".java"; + } + + // Change the directory + if( outputdir != null ) + { + path = outputdir + File.separator + path; + } + + return new File( path ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java new file mode 100644 index 000000000..c8e6da765 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jdepend/JDependTask.java @@ -0,0 +1,453 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jdepend; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.PathTokenizer; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteWatchdog; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Ant task to run JDepend tests.

              + * + * JDepend is a tool to generate design quality metrics for each Java package. + * It has been initially created by Mike Clark. JDepend can be found at + * http://www.clarkware.com/software/JDepend.html . The current + * implementation spawn a new Java VM. + * + * @author Jerome Lacoste + * @author Rob Oxspring + */ +public class JDependTask extends Task +{ + + /** + * No problems with this test. + */ + private final static int SUCCESS = 0; + /** + * An error occured. + */ + private final static int ERRORS = 1; + private boolean _haltonerror = false; + private boolean _fork = false; + //private Integer _timeout = null; + + private String _jvm = null; + private String format = "text"; + private Path _compileClasspath; + private File _dir; + + // optional attributes + private File _outputFile; + //private CommandlineJava commandline = new CommandlineJava(); + + // required attributes + private Path _sourcesPath; + + public JDependTask() { } + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( _compileClasspath == null ) + { + _compileClasspath = classpath; + } + else + { + _compileClasspath.append( classpath ); + } + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere. + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /** + * The directory to invoke the VM in. Ignored if no JVM is forked. + * + * @param dir the directory to invoke the JVM from. + * @see #setFork(boolean) + */ + public void setDir( File dir ) + { + _dir = dir; + } + + /** + * Tells whether a JVM should be forked for the task. Default: false. + * + * @param value true if a JVM should be forked, otherwise false + * + */ + public void setFork( boolean value ) + { + _fork = value; + } + + + public void setFormat( FormatAttribute ea ) + { + format = ea.getValue(); + } + + /** + * Halt on Failure? default: false. + * + * @param value The new Haltonerror value + */ + public void setHaltonerror( boolean value ) + { + _haltonerror = value; + } + + /** + * Set a new VM to execute the task. Default is java . Ignored if + * no JVM is forked. + * + * @param value the new VM to use instead of java + * @see #setFork(boolean) + */ + public void setJvm( String value ) + { + _jvm = value; + + } + + /* + * public void setTimeout(Integer value) { + * _timeout = value; + * } + * public Integer getTimeout() { + * return _timeout; + * } + */ + public void setOutputFile( File outputFile ) + { + _outputFile = outputFile; + } + + /** + * Gets the classpath to be used for this compilation. + * + * @return The Classpath value + */ + public Path getClasspath() + { + return _compileClasspath; + } + + public File getDir() + { + return _dir; + } + + public boolean getFork() + { + return _fork; + } + + public boolean getHaltonerror() + { + return _haltonerror; + } + + public File getOutputFile() + { + return _outputFile; + } + + /** + * Gets the sourcepath. + * + * @return The Sourcespath value + */ + public Path getSourcespath() + { + return _sourcesPath; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( _compileClasspath == null ) + { + _compileClasspath = new Path( project ); + } + return _compileClasspath.createPath(); + } + + /** + * Create a new JVM argument. Ignored if no JVM is forked. + * + * @param commandline Description of Parameter + * @return create a new JVM argument so that any argument can be passed to + * the JVM. + * @see #setFork(boolean) + */ + public Commandline.Argument createJvmarg( CommandlineJava commandline ) + { + return commandline.createVmArgument(); + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createSourcespath() + { + if( _sourcesPath == null ) + { + _sourcesPath = new Path( project ); + } + return _sourcesPath.createPath(); + } + + public void execute() + throws BuildException + { + + CommandlineJava commandline = new CommandlineJava(); + + if( "text".equals( format ) ) + commandline.setClassname( "jdepend.textui.JDepend" ); + else + if( "xml".equals( format ) ) + commandline.setClassname( "jdepend.xmlui.JDepend" ); + + if( _jvm != null ) + commandline.setVm( _jvm ); + + if( getSourcespath() == null ) + throw new BuildException( "Missing Sourcepath required argument" ); + + // execute the test and get the return code + int exitValue = JDependTask.ERRORS; + boolean wasKilled = false; + if( !getFork() ) + { + exitValue = executeInVM( commandline ); + } + else + { + ExecuteWatchdog watchdog = createWatchdog(); + exitValue = executeAsForked( commandline, watchdog ); + // null watchdog means no timeout, you'd better not check with null + if( watchdog != null ) + { + //info will be used in later version do nothing for now + //wasKilled = watchdog.killedProcess(); + } + } + + // if there is an error/failure and that it should halt, stop everything otherwise + // just log a statement + boolean errorOccurred = exitValue == JDependTask.ERRORS; + + if( errorOccurred ) + { + if( getHaltonerror() ) + throw new BuildException( "JDepend failed", + location ); + else + log( "JDepend FAILED", Project.MSG_ERR ); + } + } + + + /** + * Execute the task by forking a new JVM. The command will block until it + * finishes. To know if the process was destroyed or not, use the + * killedProcess() method of the watchdog class. + * + * @param watchdog the watchdog in charge of cancelling the test if it + * exceeds a certain amount of time. Can be null , in this + * case the test could probably hang forever. + * @param commandline Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + // JL: comment extracted from JUnitTask (and slightly modified) + public int executeAsForked( CommandlineJava commandline, ExecuteWatchdog watchdog ) + throws BuildException + { + // if not set, auto-create the ClassPath from the project + createClasspath(); + + // not sure whether this test is needed but cost nothing to put. + // hope it will be reviewed by anybody competent + if( getClasspath().toString().length() > 0 ) + { + createJvmarg( commandline ).setValue( "-classpath" ); + createJvmarg( commandline ).setValue( getClasspath().toString() ); + } + + if( getOutputFile() != null ) + { + // having a space between the file and its path causes commandline to add quotes " + // around the argument thus making JDepend not taking it into account. Thus we split it in two + commandline.createArgument().setValue( "-file" ); + commandline.createArgument().setValue( _outputFile.getPath() ); + // we have to find a cleaner way to put this output + } + + PathTokenizer sourcesPath = new PathTokenizer( getSourcespath().toString() ); + while( sourcesPath.hasMoreTokens() ) + { + File f = new File( sourcesPath.nextToken() ); + + // not necessary as JDepend would fail, but why loose some time? + if( !f.exists() || !f.isDirectory() ) + throw new BuildException( "\"" + f.getPath() + "\" does not represent a valid directory. JDepend would fail." ); + commandline.createArgument().setValue( f.getPath() ); + } + + Execute execute = new Execute( new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ), watchdog ); + execute.setCommandline( commandline.getCommandline() ); + if( getDir() != null ) + { + execute.setWorkingDirectory( getDir() ); + execute.setAntRun( project ); + } + + if( getOutputFile() != null ) + log( "Output to be stored in " + getOutputFile().getPath() ); + log( "Executing: " + commandline.toString(), Project.MSG_VERBOSE ); + try + { + return execute.execute(); + } + catch( IOException e ) + { + throw new BuildException( "Process fork failed.", e, location ); + } + } + + + // this comment extract from JUnit Task may also apply here + // "in VM is not very nice since it could probably hang the + // whole build. IMHO this method should be avoided and it would be best + // to remove it in future versions. TBD. (SBa)" + + /** + * Execute inside VM. + * + * @param commandline Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public int executeInVM( CommandlineJava commandline ) + throws BuildException + { + jdepend.textui.JDepend jdepend; + + if( "xml".equals( format ) ) + jdepend = new jdepend.xmlui.JDepend(); + else + jdepend = new jdepend.textui.JDepend(); + + if( getOutputFile() != null ) + { + FileWriter fw; + try + { + fw = new FileWriter( getOutputFile().getPath() ); + } + catch( IOException e ) + { + String msg = "JDepend Failed when creating the output file: " + e.getMessage(); + log( msg ); + throw new BuildException( msg ); + } + jdepend.setWriter( new PrintWriter( fw ) ); + log( "Output to be stored in " + getOutputFile().getPath() ); + } + + PathTokenizer sourcesPath = new PathTokenizer( getSourcespath().toString() ); + while( sourcesPath.hasMoreTokens() ) + { + File f = new File( sourcesPath.nextToken() ); + + // not necessary as JDepend would fail, but why loose some time? + if( !f.exists() || !f.isDirectory() ) + { + String msg = "\"" + f.getPath() + "\" does not represent a valid directory. JDepend would fail."; + log( msg ); + throw new BuildException( msg ); + } + try + { + jdepend.addDirectory( f.getPath() ); + } + catch( IOException e ) + { + String msg = "JDepend Failed when adding a source directory: " + e.getMessage(); + log( msg ); + throw new BuildException( msg ); + } + } + jdepend.analyze(); + return SUCCESS; + } + + /** + * @return null if there is a timeout value, otherwise the watchdog + * instance. + * @exception BuildException Description of Exception + */ + protected ExecuteWatchdog createWatchdog() + throws BuildException + { + + return null; + /* + * if (getTimeout() == null){ + * return null; + * } + * return new ExecuteWatchdog(getTimeout().intValue()); + */ + } + + public static class FormatAttribute extends EnumeratedAttribute + { + private String[] formats = new String[]{"xml", "text"}; + + public String[] getValues() + { + return formats; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java new file mode 100644 index 000000000..b1a089810 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/ClassNameReader.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jlink; +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Provides a quick and dirty way to determine the true name of a class given + * just an InputStream. Reads in just enough to perform this minimal task only. + * + * @author RT + */ +public class ClassNameReader extends Object +{ + + public static String getClassName( InputStream input ) + throws IOException + { + DataInputStream data = new DataInputStream( input ); + // verify this is a valid class file. + int cookie = data.readInt(); + if( cookie != 0xCAFEBABE ) + { + return null; + } + int version = data.readInt(); + // read the constant pool. + ConstantPool constants = new ConstantPool( data ); + Object[] values = constants.values; + // read access flags and class index. + int accessFlags = data.readUnsignedShort(); + int classIndex = data.readUnsignedShort(); + Integer stringIndex = ( Integer )values[classIndex]; + String className = ( String )values[stringIndex.intValue()]; + return className; + } + +} + +/** + * Reads just enough of a class file to determine the class' full name.

              + * + * Extremely minimal constant pool implementation, mainly to support extracting + * strings from a class file. + * + * @author Patrick C. Beard . + */ +class ConstantPool extends Object +{ + + + final static byte UTF8 = 1, UNUSED = 2, INTEGER = 3, FLOAT = 4, LONG = 5, DOUBLE = 6, + CLASS = 7, STRING = 8, FIELDREF = 9, METHODREF = 10, + INTERFACEMETHODREF = 11, NAMEANDTYPE = 12; + + byte[] types; + + Object[] values; + + ConstantPool( DataInput data ) + throws IOException + { + super(); + + int count = data.readUnsignedShort(); + types = new byte[count]; + values = new Object[count]; + // read in all constant pool entries. + for( int i = 1; i < count; i++ ) + { + byte type = data.readByte(); + types[i] = type; + switch ( type ) + { + case UTF8: + values[i] = data.readUTF(); + break; + case UNUSED: + break; + case INTEGER: + values[i] = new Integer( data.readInt() ); + break; + case FLOAT: + values[i] = new Float( data.readFloat() ); + break; + case LONG: + values[i] = new Long( data.readLong() ); + ++i; + break; + case DOUBLE: + values[i] = new Double( data.readDouble() ); + ++i; + break; + case CLASS: + case STRING: + values[i] = new Integer( data.readUnsignedShort() ); + break; + case FIELDREF: + case METHODREF: + case INTERFACEMETHODREF: + case NAMEANDTYPE: + values[i] = new Integer( data.readInt() ); + break; + } + } + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java new file mode 100644 index 000000000..94d642f0c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/JlinkTask.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jlink; +import java.io.File; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Path; + +/** + * This class defines objects that can link together various jar and zip files. + *

              + * + * It is basically a wrapper for the jlink code written originally by Patrick Beard . The classes + * org.apache.tools.ant.taskdefs.optional.jlink.Jlink and + * org.apache.tools.ant.taskdefs.optional.jlink.ClassNameReader support this + * class.

              + * + * For example: + *

              + * <jlink compress="false" outfile="out.jar"/>
              + *   <mergefiles>
              + *     <pathelement path="${build.dir}/mergefoo.jar"/>
              + *     <pathelement path="${build.dir}/mergebar.jar"/>
              + *   </mergefiles>
              + *   <addfiles>
              + *     <pathelement path="${build.dir}/mac.jar"/>
              + *     <pathelement path="${build.dir}/pc.zip"/>
              + *   </addfiles>
              + * </jlink>
              + * 
              + * + * @author Matthew Kuperus Heun + * + */ +public class JlinkTask extends MatchingTask +{ + + private File outfile = null; + + private Path mergefiles = null; + + private Path addfiles = null; + + private boolean compress = false; + + private String ps = System.getProperty( "path.separator" ); + + /** + * Sets the files to be added into the output. + * + * @param addfiles The new Addfiles value + */ + public void setAddfiles( Path addfiles ) + { + if( this.addfiles == null ) + { + this.addfiles = addfiles; + } + else + { + this.addfiles.append( addfiles ); + } + } + + /** + * Defines whether or not the output should be compacted. + * + * @param compress The new Compress value + */ + public void setCompress( boolean compress ) + { + this.compress = compress; + } + + /** + * Sets the files to be merged into the output. + * + * @param mergefiles The new Mergefiles value + */ + public void setMergefiles( Path mergefiles ) + { + if( this.mergefiles == null ) + { + this.mergefiles = mergefiles; + } + else + { + this.mergefiles.append( mergefiles ); + } + } + + /** + * The output file for this run of jlink. Usually a jar or zip file. + * + * @param outfile The new Outfile value + */ + public void setOutfile( File outfile ) + { + this.outfile = outfile; + } + + /** + * Establishes the object that contains the files to be added to the output. + * + * @return Description of the Returned Value + */ + public Path createAddfiles() + { + if( this.addfiles == null ) + { + this.addfiles = new Path( getProject() ); + } + return this.addfiles.createPath(); + } + + /** + * Establishes the object that contains the files to be merged into the + * output. + * + * @return Description of the Returned Value + */ + public Path createMergefiles() + { + if( this.mergefiles == null ) + { + this.mergefiles = new Path( getProject() ); + } + return this.mergefiles.createPath(); + } + + /** + * Does the adding and merging. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + //Be sure everything has been set. + if( outfile == null ) + { + throw new BuildException( "outfile attribute is required! Please set." ); + } + if( !haveAddFiles() && !haveMergeFiles() ) + { + throw new BuildException( "addfiles or mergefiles required! Please set." ); + } + log( "linking: " + outfile.getPath() ); + log( "compression: " + compress, Project.MSG_VERBOSE ); + jlink linker = new jlink(); + linker.setOutfile( outfile.getPath() ); + linker.setCompression( compress ); + if( haveMergeFiles() ) + { + log( "merge files: " + mergefiles.toString(), Project.MSG_VERBOSE ); + linker.addMergeFiles( mergefiles.list() ); + } + if( haveAddFiles() ) + { + log( "add files: " + addfiles.toString(), Project.MSG_VERBOSE ); + linker.addAddFiles( addfiles.list() ); + } + try + { + linker.link(); + } + catch( Exception ex ) + { + throw new BuildException( ex); + } + } + + private boolean haveAddFiles() + { + return haveEntries( addfiles ); + } + + private boolean haveEntries( Path p ) + { + if( p == null ) + { + return false; + } + if( p.size() > 0 ) + { + return true; + } + return false; + } + + private boolean haveMergeFiles() + { + return haveEntries( mergefiles ); + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java new file mode 100644 index 000000000..cb9a3fbe8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jlink/jlink.java @@ -0,0 +1,464 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jlink; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Vector; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +public class jlink extends Object +{ + + private String outfile = null; + + private Vector mergefiles = new Vector( 10 ); + + private Vector addfiles = new Vector( 10 ); + + private boolean compression = false; + + byte[] buffer = new byte[8192]; + + public static void main( String[] args ) + { + // jlink output input1 ... inputN + if( args.length < 2 ) + { + System.out.println( "usage: jlink output input1 ... inputN" ); + System.exit( 1 ); + } + jlink linker = new jlink(); + linker.setOutfile( args[0] ); + //To maintain compatibility with the command-line version, we will only add files to be merged. + for( int i = 1; i < args.length; i++ ) + { + linker.addMergeFile( args[i] ); + } + try + { + linker.link(); + } + catch( Exception ex ) + { + System.err.print( ex.getMessage() ); + } + } + + /** + * Determines whether output will be compressed. + * + * @param compress The new Compression value + */ + public void setCompression( boolean compress ) + { + this.compression = compress; + } + + /** + * The file that will be created by this instance of jlink. + * + * @param outfile The new Outfile value + */ + public void setOutfile( String outfile ) + { + if( outfile == null ) + { + return; + } + this.outfile = outfile; + } + + /** + * Adds a file to be added into the output. + * + * @param addfile The feature to be added to the AddFile attribute + */ + public void addAddFile( String addfile ) + { + if( addfile == null ) + { + return; + } + addfiles.addElement( addfile ); + } + + /** + * Adds several file to be added into the output. + * + * @param addfiles The feature to be added to the AddFiles attribute + */ + public void addAddFiles( String[] addfiles ) + { + if( addfiles == null ) + { + return; + } + for( int i = 0; i < addfiles.length; i++ ) + { + addAddFile( addfiles[i] ); + } + } + + /** + * Adds a file to be merged into the output. + * + * @param mergefile The feature to be added to the MergeFile attribute + */ + public void addMergeFile( String mergefile ) + { + if( mergefile == null ) + { + return; + } + mergefiles.addElement( mergefile ); + } + + /** + * Adds several files to be merged into the output. + * + * @param mergefiles The feature to be added to the MergeFiles attribute + */ + public void addMergeFiles( String[] mergefiles ) + { + if( mergefiles == null ) + { + return; + } + for( int i = 0; i < mergefiles.length; i++ ) + { + addMergeFile( mergefiles[i] ); + } + } + + /** + * Performs the linking of files. Addfiles are added to the output as-is. + * For example, a jar file is added to the output as a jar file. However, + * mergefiles are first examined for their type. If it is a jar or zip file, + * the contents will be extracted from the mergefile and entered into the + * output. If a zip or jar file is encountered in a subdirectory it will be + * added, not merged. If a directory is encountered, it becomes the root + * entry of all the files below it. Thus, you can provide multiple, disjoint + * directories, as addfiles: they will all be added in a rational manner to + * outfile. + * + * @exception Exception Description of Exception + */ + public void link() + throws Exception + { + ZipOutputStream output = new ZipOutputStream( new FileOutputStream( outfile ) ); + if( compression ) + { + output.setMethod( ZipOutputStream.DEFLATED ); + output.setLevel( Deflater.DEFAULT_COMPRESSION ); + } + else + { + output.setMethod( ZipOutputStream.STORED ); + } + Enumeration merges = mergefiles.elements(); + while( merges.hasMoreElements() ) + { + String path = ( String )merges.nextElement(); + File f = new File( path ); + if( f.getName().endsWith( ".jar" ) || f.getName().endsWith( ".zip" ) ) + { + //Do the merge + mergeZipJarContents( output, f ); + } + else + { + //Add this file to the addfiles Vector and add it + //later at the top level of the output file. + addAddFile( path ); + } + } + Enumeration adds = addfiles.elements(); + while( adds.hasMoreElements() ) + { + String name = ( String )adds.nextElement(); + File f = new File( name ); + if( f.isDirectory() ) + { + //System.out.println("in jlink: adding directory contents of " + f.getPath()); + addDirContents( output, f, f.getName() + '/', compression ); + } + else + { + addFile( output, f, "", compression ); + } + } + if( output != null ) + { + try + { + output.close(); + } + catch( IOException ioe ) + {} + } + } + + /* + * Gets the name of an entry in the file. This is the real name + * which for a class is the name of the package with the class + * name appended. + */ + private String getEntryName( File file, String prefix ) + { + String name = file.getName(); + if( !name.endsWith( ".class" ) ) + { + // see if the file is in fact a .class file, and determine its actual name. + try + { + InputStream input = new FileInputStream( file ); + String className = ClassNameReader.getClassName( input ); + input.close(); + if( className != null ) + { + return className.replace( '.', '/' ) + ".class"; + } + } + catch( IOException ioe ) + {} + } + System.out.println( "From " + file.getPath() + " and prefix " + prefix + ", creating entry " + prefix + name ); + return ( prefix + name ); + } + + /* + * Adds contents of a directory to the output. + */ + private void addDirContents( ZipOutputStream output, File dir, String prefix, boolean compress ) + throws IOException + { + String[] contents = dir.list(); + for( int i = 0; i < contents.length; ++i ) + { + String name = contents[i]; + File file = new File( dir, name ); + if( file.isDirectory() ) + { + addDirContents( output, file, prefix + name + '/', compress ); + } + else + { + addFile( output, file, prefix, compress ); + } + } + } + + /* + * Adds a file to the output stream. + */ + private void addFile( ZipOutputStream output, File file, String prefix, boolean compress ) + throws IOException + { + //Make sure file exists + long checksum = 0; + if( !file.exists() ) + { + return; + } + ZipEntry entry = new ZipEntry( getEntryName( file, prefix ) ); + entry.setTime( file.lastModified() ); + entry.setSize( file.length() ); + if( !compress ) + { + entry.setCrc( calcChecksum( file ) ); + } + FileInputStream input = new FileInputStream( file ); + addToOutputStream( output, input, entry ); + } + + /* + * A convenience method that several other methods might call. + */ + private void addToOutputStream( ZipOutputStream output, InputStream input, ZipEntry ze ) + throws IOException + { + try + { + output.putNextEntry( ze ); + } + catch( ZipException zipEx ) + { + //This entry already exists. So, go with the first one. + input.close(); + return; + } + int numBytes = -1; + while( ( numBytes = input.read( buffer ) ) > 0 ) + { + output.write( buffer, 0, numBytes ); + } + output.closeEntry(); + input.close(); + } + + /* + * Necessary in the case where you add a entry that + * is not compressed. + */ + private long calcChecksum( File f ) + throws IOException + { + BufferedInputStream in = new BufferedInputStream( new FileInputStream( f ) ); + return calcChecksum( in, f.length() ); + } + + /* + * Necessary in the case where you add a entry that + * is not compressed. + */ + private long calcChecksum( InputStream in, long size ) + throws IOException + { + CRC32 crc = new CRC32(); + int len = buffer.length; + int count = -1; + int haveRead = 0; + while( ( count = in.read( buffer, 0, len ) ) > 0 ) + { + haveRead += count; + crc.update( buffer, 0, count ); + } + in.close(); + return crc.getValue(); + } + + /* + * Actually performs the merging of f into the output. + * f should be a zip or jar file. + */ + private void mergeZipJarContents( ZipOutputStream output, File f ) + throws IOException + { + //Check to see that the file with name "name" exists. + if( !f.exists() ) + { + return; + } + ZipFile zipf = new ZipFile( f ); + Enumeration entries = zipf.entries(); + while( entries.hasMoreElements() ) + { + ZipEntry inputEntry = ( ZipEntry )entries.nextElement(); + //Ignore manifest entries. They're bound to cause conflicts between + //files that are being merged. User should supply their own + //manifest file when doing the merge. + String inputEntryName = inputEntry.getName(); + int index = inputEntryName.indexOf( "META-INF" ); + if( index < 0 ) + { + //META-INF not found in the name of the entry. Go ahead and process it. + try + { + output.putNextEntry( processEntry( zipf, inputEntry ) ); + } + catch( ZipException ex ) + { + //If we get here, it could be because we are trying to put a + //directory entry that already exists. + //For example, we're trying to write "com", but a previous + //entry from another mergefile was called "com". + //In that case, just ignore the error and go on to the + //next entry. + String mess = ex.getMessage(); + if( mess.indexOf( "duplicate" ) >= 0 ) + { + //It was the duplicate entry. + continue; + } + else + { + //I hate to admit it, but we don't know what happened here. Throw the Exception. + throw ex; + } + } + InputStream in = zipf.getInputStream( inputEntry ); + int len = buffer.length; + int count = -1; + while( ( count = in.read( buffer, 0, len ) ) > 0 ) + { + output.write( buffer, 0, count ); + } + in.close(); + output.closeEntry(); + } + } + zipf.close(); + } + + /* + * A method that does the work on a given entry in a mergefile. + * The big deal is to set the right parameters in the ZipEntry + * on the output stream. + */ + private ZipEntry processEntry( ZipFile zip, ZipEntry inputEntry ) + throws IOException + { + /* + * First, some notes. + * On MRJ 2.2.2, getting the size, compressed size, and CRC32 from the + * ZipInputStream does not work for compressed (deflated) files. Those calls return -1. + * For uncompressed (stored) files, those calls do work. + * However, using ZipFile.getEntries() works for both compressed and + * uncompressed files. + * Now, from some simple testing I did, it seems that the value of CRC-32 is + * independent of the compression setting. So, it should be easy to pass this + * information on to the output entry. + */ + String name = inputEntry.getName(); + if( !( inputEntry.isDirectory() || name.endsWith( ".class" ) ) ) + { + try + { + InputStream input = zip.getInputStream( zip.getEntry( name ) ); + String className = ClassNameReader.getClassName( input ); + input.close(); + if( className != null ) + { + name = className.replace( '.', '/' ) + ".class"; + } + } + catch( IOException ioe ) + {} + } + ZipEntry outputEntry = new ZipEntry( name ); + outputEntry.setTime( inputEntry.getTime() ); + outputEntry.setExtra( inputEntry.getExtra() ); + outputEntry.setComment( inputEntry.getComment() ); + outputEntry.setTime( inputEntry.getTime() ); + if( compression ) + { + outputEntry.setMethod( ZipEntry.DEFLATED ); + //Note, don't need to specify size or crc for compressed files. + } + else + { + outputEntry.setMethod( ZipEntry.STORED ); + outputEntry.setCrc( inputEntry.getCrc() ); + outputEntry.setSize( inputEntry.getSize() ); + } + return outputEntry; + } + +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java new file mode 100644 index 000000000..2d91db4ca --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/JspC.java @@ -0,0 +1,491 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp; +import java.io.File; +import java.util.Date; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.taskdefs.optional.jsp.compilers.CompilerAdapter; +import org.apache.tools.ant.taskdefs.optional.jsp.compilers.CompilerAdapterFactory; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Reference; + +/** + * Ant task to run the jsp compiler.

              + * + * This task takes the given jsp files and compiles them into java files. It is + * then up to the user to compile the java files into classes.

              + * + * The task requires the srcdir and destdir attributes to be set. This Task is a + * MatchingTask, so the files to be compiled can be specified using + * includes/excludes attributes or nested include/exclude elements. Optional + * attributes are verbose (set the verbosity level passed to jasper), package + * (name of the destination package for generated java classes and classpath + * (the classpath to use when running the jsp compiler).

              + * + * This task supports the nested elements classpath (A Path) and classpathref (A + * Reference) which can be used in preference to the attribute classpath, if the + * jsp compiler is not already in the ant classpath.

              + * + *

              Notes

              + * + * At present, this task only supports the jasper compiler. In future, other + * compilers will be supported by setting the jsp.compiler property.

              + * + *

              Usage

              + * <jspc srcdir="${basedir}/src/war"
              + *       destdir="${basedir}/gensrc"
              + *       package="com.i3sp.jsp"
              + *       verbose="9">
              + *   <include name="**\/*.jsp" />
              + * </jspc>
              + * 
              + * + * @author Matthew Watson

              + * + * Large Amount of cutting and pasting from the Javac task... + * @author James Davidson duncan@x180.com + * @author Robin Green greenrd@hotmail.com + * + * @author Stefan Bodewig + * @author J D Glanville + * @version $Revision$ $Date$ + */ +public class JspC extends MatchingTask +{ + + private final static String FAIL_MSG + = "Compile failed, messages should have been provided."; + private int verbose = 0; + protected Vector compileList = new Vector(); + protected boolean failOnError = true; + /* + * ------------------------------------------------------------ + */ + private Path classpath; + private File destDir; + private String iepluginid; + private boolean mapped; + private String packageName; + private Path src; + + /** + * -uribase

              The uri directory compilations should be relative to + * (Default is "/") + */ + + private File uribase; + + /** + * -uriroot The root directory that uri files should be resolved + * against, + */ + private File uriroot; + + + /* + * ------------------------------------------------------------ + */ + /** + * Set the classpath to be used for this compilation + * + * @param cp The new Classpath value + */ + public void setClasspath( Path cp ) + { + if( classpath == null ) + classpath = cp; + else + classpath.append( cp ); + } + + /** + * Adds a reference to a CLASSPATH defined elsewhere + * + * @param r The new ClasspathRef value + */ + public void setClasspathRef( Reference r ) + { + createClasspath().setRefid( r ); + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the destination directory into which the JSP source files should be + * compiled. + * + * @param destDir The new Destdir value + */ + public void setDestdir( File destDir ) + { + this.destDir = destDir; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Throw a BuildException if compilation fails + * + * @param fail The new Failonerror value + */ + public void setFailonerror( boolean fail ) + { + failOnError = fail; + } + + /** + * Set the ieplugin id + * + * @param iepluginid_ The new Ieplugin value + */ + public void setIeplugin( String iepluginid_ ) + { + iepluginid = iepluginid_; + } + + /** + * set the mapped flag + * + * @param mapped_ The new Mapped value + */ + public void setMapped( boolean mapped_ ) + { + mapped = mapped_; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the name of the package the compiled jsp files should be in + * + * @param pkg The new Package value + */ + public void setPackage( String pkg ) + { + this.packageName = pkg; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the source dirs to find the source JSP files. + * + * @param srcDir The new Srcdir value + */ + public void setSrcdir( Path srcDir ) + { + if( src == null ) + { + src = srcDir; + } + else + { + src.append( srcDir ); + } + } + + /** + * -uribase. the uri context of relative URI references in the JSP pages. If + * it does not exist then it is derived from the location of the file + * relative to the declared or derived value of -uriroot. + * + * @param uribase The new Uribase value + */ + public void setUribase( File uribase ) + { + this.uribase = uribase; + } + + /** + * -uriroot The root directory that uri files should be resolved + * against, (Default is the directory jspc is invoked from) + * + * @param uriroot The new Uribase value + */ + public void setUriroot( File uriroot ) + { + this.uriroot = uriroot; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Set the verbose level of the compiler + * + * @param i The new Verbose value + */ + public void setVerbose( int i ) + { + verbose = i; + } + + public Path getClasspath() + { + return classpath; + } + + /* + * ------------------------------------------------------------ + */ + public Vector getCompileList() + { + return compileList; + } + + public File getDestdir() + { + return destDir; + } + + /** + * Gets the failonerror flag. + * + * @return The Failonerror value + */ + public boolean getFailonerror() + { + return failOnError; + } + + /* + * ------------------------------------------------------------ + */ + public String getIeplugin() + { + return iepluginid; + } + + public String getPackage() + { + return packageName; + } + + public Path getSrcDir() + { + return src; + } + + public File getUribase() + { + return uriroot; + } + + public File getUriroot() + { + return uriroot; + } + + public int getVerbose() + { + return verbose; + } + + /* + * ------------------------------------------------------------ + */ + public boolean isMapped() + { + return mapped; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classpath == null ) + classpath = new Path( project ); + return classpath.createPath(); + } + + /* + * ------------------------------------------------------------ + */ + public void execute() + throws BuildException + { + // first off, make sure that we've got a srcdir + if( src == null ) + { + throw new BuildException( "srcdir attribute must be set!", + location ); + } + String[] list = src.list(); + if( list.length == 0 ) + { + throw new BuildException( "srcdir attribute must be set!", + location ); + } + + if( destDir != null && !destDir.isDirectory() ) + { + throw new + BuildException( "destination directory \"" + destDir + + "\" does not exist or is not a directory", + location ); + } + + // calculate where the files will end up: + File dest = null; + if( packageName == null ) + dest = destDir; + else + { + String path = destDir.getPath() + File.separatorChar + + packageName.replace( '.', File.separatorChar ); + dest = new File( path ); + } + + // scan source directories and dest directory to build up both copy + // lists and compile lists + resetFileLists(); + int filecount = 0; + for( int i = 0; i < list.length; i++ ) + { + File srcDir = ( File )project.resolveFile( list[i] ); + if( !srcDir.exists() ) + { + throw new BuildException( "srcdir \"" + srcDir.getPath() + + "\" does not exist!", location ); + } + + DirectoryScanner ds = this.getDirectoryScanner( srcDir ); + + String[] files = ds.getIncludedFiles(); + filecount = files.length; + scanDir( srcDir, dest, files ); + } + + // compile the source files + + String compiler = project.getProperty( "jsp.compiler" ); + if( compiler == null ) + { + compiler = "jasper"; + } + log( "compiling " + compileList.size() + " files", Project.MSG_VERBOSE ); + + if( compileList.size() > 0 ) + { + + CompilerAdapter adapter = + CompilerAdapterFactory.getCompiler( compiler, this ); + log( "Compiling " + compileList.size() + + " source file" + + ( compileList.size() == 1 ? "" : "s" ) + + ( destDir != null ? " to " + destDir : "" ) ); + + // now we need to populate the compiler adapter + adapter.setJspc( this ); + + // finally, lets execute the compiler!! + if( !adapter.execute() ) + { + if( failOnError ) + { + throw new BuildException( FAIL_MSG, location ); + } + else + { + log( FAIL_MSG, Project.MSG_ERR ); + } + } + } + else + { + if( filecount == 0 ) + { + log( "there were no files to compile", Project.MSG_INFO ); + } + else + { + log( "all files are up to date", Project.MSG_VERBOSE ); + } + } + } + + /* + * ------------------------------------------------------------ + */ + /** + * Clear the list of files to be compiled and copied.. + */ + protected void resetFileLists() + { + compileList.removeAllElements(); + } + + /* + * ------------------------------------------------------------ + */ + /** + * Scans the directory looking for source files to be compiled. The results + * are returned in the class variable compileList + * + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param files Description of Parameter + */ + protected void scanDir( File srcDir, File destDir, String files[] ) + { + + long now = ( new Date() ).getTime(); + + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( srcDir, files[i] ); + if( files[i].endsWith( ".jsp" ) ) + { + // drop leading path (if any) + int fileStart = + files[i].lastIndexOf( File.separatorChar ) + 1; + File javaFile = new File( destDir, files[i].substring( fileStart, + files[i].indexOf( ".jsp" ) ) + ".java" ); + + if( srcFile.lastModified() > now ) + { + log( "Warning: file modified in the future: " + + files[i], Project.MSG_WARN ); + } + + if( !javaFile.exists() || + srcFile.lastModified() > javaFile.lastModified() ) + { + if( !javaFile.exists() ) + { + log( "Compiling " + srcFile.getPath() + + " because java file " + + javaFile.getPath() + " does not exist", + Project.MSG_DEBUG ); + } + else + { + log( "Compiling " + srcFile.getPath() + + " because it is out of date with respect to " + + javaFile.getPath(), Project.MSG_DEBUG ); + } + compileList.addElement( srcFile.getAbsolutePath() ); + } + } + } + } + /* + * ------------------------------------------------------------ + */ +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java new file mode 100644 index 000000000..20d7a9176 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/WLJspc.java @@ -0,0 +1,312 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp;//java imports +import java.io.File; +import java.util.Date; +import java.util.StringTokenizer; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java;//apache/ant imports +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.Path; + +/** + * Class to precompile JSP's using weblogic's jsp compiler (weblogic.jspc) + * + * @author Avik Sengupta + * http://www.webteksoftware.com Tested only on Weblogic 4.5.1 - NT4.0 and + * Solaris 5.7 required attributes src : root of source tree for JSP, ie, + * the document root for your weblogic server dest : root of destination + * directory, what you have set as WorkingDir in the weblogic properties + * package : start package name under which your JSP's would be compiled + * other attributes classpath A classpath should be set which contains the + * weblogic classes as well as all application classes referenced by the + * JSP. The system classpath is also appended when the jspc is called, so + * you may choose to put everything in the classpath while calling Ant. + * However, since presumably the JSP's will reference classes being build + * by Ant, it would be better to explicitly add the classpath in the task + * The task checks timestamps on the JSP's and the generated classes, and + * compiles only those files that have changed. It follows the weblogic + * naming convention of putting classes in _dirName/_fileName.class for + * dirname/fileName.jsp Limitation: It compiles the files thru the + * Classic compiler only. Limitation: Since it is my experience that + * weblogic jspc throws out of memory error on being given too many files + * at one go, it is called multiple times with one jsp file each.
              + * example
              + * <target name="jspcompile" depends="compile">
              + *   <wljspc src="c:\\weblogic\\myserver\\public_html" dest="c:\\weblogic\\myserver\\serverclasses" package="myapp.jsp">
              + *   <classpath>
              + *          <pathelement location="${weblogic.classpath}" />
              + *           <pathelement path="${compile.dest}" />
              + *      </classpath>
              + *
              + *   </wljspc>
              + * </target>
              + * 
              + */ + +public class WLJspc extends MatchingTask +{//classpath used to compile the jsp files. + //private String compilerPath; //fully qualified name for the compiler executable + + private String pathToPackage = ""; + private Vector filesToDo = new Vector();//package under which resultant classes will reside + private Path compileClasspath; + //TODO Test on other versions of weblogic + //TODO add more attributes to the task, to take care of all jspc options + //TODO Test on Unix + + private File destinationDirectory;// root of source files tree + private String destinationPackage;//root of compiled files tree + private File sourceDirectory; + + + /** + * Set the classpath to be used for this compilation. + * + * @param classpath The new Classpath value + */ + public void setClasspath( Path classpath ) + { + if( compileClasspath == null ) + { + compileClasspath = classpath; + } + else + { + compileClasspath.append( classpath ); + } + } + + /** + * Set the directory containing the source jsp's + * + * @param dirName the directory containg the source jsp's + */ + public void setDest( File dirName ) + { + + destinationDirectory = dirName; + } + + /** + * Set the package under which the compiled classes go + * + * @param packageName the package name for the clases + */ + public void setPackage( String packageName ) + { + + destinationPackage = packageName; + } + + /** + * Set the directory containing the source jsp's + * + * @param dirName the directory containg the source jsp's + */ + public void setSrc( File dirName ) + { + + sourceDirectory = dirName; + } + + /** + * Maybe creates a nested classpath element. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + return compileClasspath; + } + + public void execute() + throws BuildException + { + if( !destinationDirectory.isDirectory() ) + { + throw new BuildException( "destination directory " + destinationDirectory.getPath() + + " is not valid" ); + } + + if( !sourceDirectory.isDirectory() ) + { + throw new BuildException( "src directory " + sourceDirectory.getPath() + + " is not valid" ); + } + + if( destinationPackage == null ) + { + throw new BuildException( "package attribute must be present.", location ); + } + + String systemClassPath = System.getProperty( "java.class.path" ); + + pathToPackage = this.destinationPackage.replace( '.', File.separatorChar ); + // get all the files in the sourceDirectory + DirectoryScanner ds = super.getDirectoryScanner( sourceDirectory ); + + //use the systemclasspath as well, to include the ant jar + if( compileClasspath == null ) + { + compileClasspath = new Path( project ); + } + + compileClasspath.append( Path.systemClasspath ); + String[] files = ds.getIncludedFiles(); + + //Weblogic.jspc calls System.exit() ... have to fork + // Therefore, takes loads of time + // Can pass directories at a time (*.jsp) but easily runs out of memory on hefty dirs + // (even on a Sun) + Java helperTask = ( Java )project.createTask( "java" ); + helperTask.setFork( true ); + helperTask.setClassname( "weblogic.jspc" ); + helperTask.setTaskName( getTaskName() ); + String[] args = new String[12]; + + File jspFile = null; + String parents = ""; + String arg = ""; + int j = 0; + //XXX this array stuff is a remnant of prev trials.. gotta remove. + args[j++] = "-d"; + args[j++] = destinationDirectory.getAbsolutePath().trim(); + args[j++] = "-docroot"; + args[j++] = sourceDirectory.getAbsolutePath().trim(); + args[j++] = "-keepgenerated";//TODO: Parameterise ?? + //Call compiler as class... dont want to fork again + //Use classic compiler -- can be parameterised? + args[j++] = "-compilerclass"; + args[j++] = "sun.tools.javac.Main"; + //Weblogic jspc does not seem to work unless u explicitly set this... + // Does not take the classpath from the env.... + // Am i missing something about the Java task?? + args[j++] = "-classpath"; + args[j++] = compileClasspath.toString(); + + this.scanDir( files ); + log( "Compiling " + filesToDo.size() + " JSP files" ); + + for( int i = 0; i < filesToDo.size(); i++ ) + { + //XXX + // All this to get package according to weblogic standards + // Can be written better... this is too hacky! + // Careful.. similar code in scanDir , but slightly different!! + jspFile = new File( ( String )filesToDo.elementAt( i ) ); + args[j] = "-package"; + parents = jspFile.getParent(); + if( ( parents != null ) && ( !( "" ).equals( parents ) ) ) + { + parents = this.replaceString( parents, File.separator, "_." ); + args[j + 1] = destinationPackage + "." + "_" + parents; + } + else + { + args[j + 1] = destinationPackage; + } + + args[j + 2] = sourceDirectory + File.separator + ( String )filesToDo.elementAt( i ); + arg = ""; + + for( int x = 0; x < 12; x++ ) + { + arg += " " + args[x]; + } + + System.out.println( "arg = " + arg ); + + helperTask.clearArgs(); + helperTask.setArgs( arg ); + helperTask.setClasspath( compileClasspath ); + if( helperTask.executeJava() != 0 ) + { + log( files[i] + " failed to compile", Project.MSG_WARN ); + } + } + } + + + protected String replaceString( String inpString, String escapeChars, String replaceChars ) + { + String localString = ""; + int numTokens = 0; + StringTokenizer st = new StringTokenizer( inpString, escapeChars, true ); + numTokens = st.countTokens(); + for( int i = 0; i < numTokens; i++ ) + { + String test = st.nextToken(); + test = ( test.equals( escapeChars ) ? replaceChars : test ); + localString += test; + } + return localString; + } + + + protected void scanDir( String files[] ) + { + + long now = ( new Date() ).getTime(); + File jspFile = null; + String parents = null; + String pack = ""; + for( int i = 0; i < files.length; i++ ) + { + File srcFile = new File( this.sourceDirectory, files[i] ); + //XXX + // All this to convert source to destination directory according to weblogic standards + // Can be written better... this is too hacky! + jspFile = new File( files[i] ); + parents = jspFile.getParent(); + int loc = 0; + + if( ( parents != null ) && ( !( "" ).equals( parents ) ) ) + { + parents = this.replaceString( parents, File.separator, "_/" ); + pack = pathToPackage + File.separator + "_" + parents; + } + else + { + pack = pathToPackage; + } + + String filePath = pack + File.separator + "_"; + int startingIndex + = files[i].lastIndexOf( File.separator ) != -1 ? files[i].lastIndexOf( File.separator ) + 1 : 0; + int endingIndex = files[i].indexOf( ".jsp" ); + if( endingIndex == -1 ) + { + break; + } + + filePath += files[i].substring( startingIndex, endingIndex ); + filePath += ".class"; + File classFile = new File( this.destinationDirectory, filePath ); + + if( srcFile.lastModified() > now ) + { + log( "Warning: file modified in the future: " + + files[i], Project.MSG_WARN ); + } + if( srcFile.lastModified() > classFile.lastModified() ) + { + //log("Files are" + srcFile.getAbsolutePath()+" " +classFile.getAbsolutePath()); + filesToDo.addElement( files[i] ); + log( "Recompiling File " + files[i], Project.MSG_VERBOSE ); + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java new file mode 100644 index 000000000..62465e8d8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapter.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.taskdefs.optional.jsp.JspC; + +/** + * The interface that all jsp compiler adapters must adher to.

              + * + * A compiler adapter is an adapter that interprets the jspc's parameters in + * preperation to be passed off to the compier this adapter represents. As all + * the necessary values are stored in the Jspc task itself, the only thing all + * adapters need is the jsp task, the execute command and a parameterless + * constructor (for reflection).

              + * + * @author Jay Dickon Glanville + * jayglanville@home.com + * @author Matthew Watson mattw@i3sp.com + */ + +public interface CompilerAdapter +{ + + /** + * Sets the compiler attributes, which are stored in the Jspc task. + * + * @param attributes The new Jspc value + */ + void setJspc( JspC attributes ); + + /** + * Executes the task. + * + * @return has the compilation been successful + * @exception BuildException Description of Exception + */ + boolean execute() + throws BuildException; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java new file mode 100644 index 000000000..2e648a5ac --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/CompilerAdapterFactory.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; + + +/** + * Creates the necessary compiler adapter, given basic criteria. + * + * @author J D Glanville + * @author Matthew Watson mattw@i3sp.com + */ +public class CompilerAdapterFactory +{ + + /** + * This is a singlton -- can't create instances!! + */ + private CompilerAdapterFactory() { } + + /** + * Based on the parameter passed in, this method creates the necessary + * factory desired. The current mapping for compiler names are as follows: + * + *
                + *
              • jasper = jasper compiler (the default) + *
              • a fully quallified classname = the name of a jsp compiler + * adapter + *
              + * + * + * @param compilerType either the name of the desired compiler, or the full + * classname of the compiler's adapter. + * @param task a task to log through. + * @return The Compiler value + * @throws BuildException if the compiler type could not be resolved into a + * compiler adapter. + */ + public static CompilerAdapter getCompiler( String compilerType, Task task ) + throws BuildException + { + /* + * If I've done things right, this should be the extent of the + * conditional statements required. + */ + if( compilerType.equalsIgnoreCase( "jasper" ) ) + { + return new JasperC(); + } + return resolveClassName( compilerType ); + } + + /** + * Tries to resolve the given classname into a compiler adapter. Throws a + * fit if it can't. + * + * @param className The fully qualified classname to be created. + * @return Description of the Returned Value + * @throws BuildException This is the fit that is thrown if className isn't + * an instance of CompilerAdapter. + */ + private static CompilerAdapter resolveClassName( String className ) + throws BuildException + { + try + { + Class c = Class.forName( className ); + Object o = c.newInstance(); + return ( CompilerAdapter )o; + } + catch( ClassNotFoundException cnfe ) + { + throw new BuildException( className + " can\'t be found.", cnfe ); + } + catch( ClassCastException cce ) + { + throw new BuildException( className + " isn\'t the classname of " + + "a compiler adapter.", cce ); + } + catch( Throwable t ) + { + // for all other possibilities + throw new BuildException( className + " caused an interesting " + + "exception.", t ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java new file mode 100644 index 000000000..6c54419d4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/DefaultCompilerAdapter.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.optional.jsp.JspC; +import org.apache.tools.ant.types.Commandline; + +/** + * This is the default implementation for the CompilerAdapter interface. This is + * currently very light on the ground since only one compiler type is supported. + * + * @author Matthew Watson mattw@i3sp.com + */ +public abstract class DefaultCompilerAdapter + implements CompilerAdapter +{ + /* + * ------------------------------------------------------------ + */ + private static String lSep = System.getProperty( "line.separator" ); + /* + * ------------------------------------------------------------ + */ + protected JspC attributes; + + public void setJspc( JspC attributes ) + { + this.attributes = attributes; + } + + public JspC getJspc() + { + return attributes; + } + + /* + * ------------------------------------------------------------ + */ + /** + * Logs the compilation parameters, adds the files to compile and logs the + * &qout;niceSourceList" + * + * @param jspc Description of Parameter + * @param compileList Description of Parameter + * @param cmd Description of Parameter + */ + protected void logAndAddFilesToCompile( JspC jspc, + Vector compileList, + Commandline cmd ) + { + jspc.log( "Compilation args: " + cmd.toString(), Project.MSG_VERBOSE ); + + StringBuffer niceSourceList = new StringBuffer( "File" ); + if( compileList.size() != 1 ) + { + niceSourceList.append( "s" ); + } + niceSourceList.append( " to be compiled:" ); + + niceSourceList.append( lSep ); + + Enumeration enum = compileList.elements(); + while( enum.hasMoreElements() ) + { + String arg = ( String )enum.nextElement(); + cmd.createArgument().setValue( arg ); + niceSourceList.append( " " + arg + lSep ); + } + + jspc.log( niceSourceList.toString(), Project.MSG_VERBOSE ); + } + /* + * ------------------------------------------------------------ + */ +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java new file mode 100644 index 000000000..4f0985bbf --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/jsp/compilers/JasperC.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.jsp.compilers; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.Java; +import org.apache.tools.ant.taskdefs.optional.jsp.JspC; +import org.apache.tools.ant.types.Commandline; + +/** + * The implementation of the jasper compiler. This is a cut-and-paste of the + * original Jspc task. + * + * @author Matthew Watson mattw@i3sp.com + */ +public class JasperC extends DefaultCompilerAdapter +{ + /* + * ------------------------------------------------------------ + */ + public boolean execute() + throws BuildException + { + getJspc().log( "Using jasper compiler", Project.MSG_VERBOSE ); + Commandline cmd = setupJasperCommand(); + + try + { + // Create an instance of the compiler, redirecting output to + // the project log + Java java = ( Java )( getJspc().getProject() ).createTask( "java" ); + if( getJspc().getClasspath() != null ) + java.setClasspath( getJspc().getClasspath() ); + java.setClassname( "org.apache.jasper.JspC" ); + String args[] = cmd.getArguments(); + for( int i = 0; i < args.length; i++ ) + java.createArg().setValue( args[i] ); + java.setFailonerror( true ); + java.execute(); + return true; + } + catch( Exception ex ) + { + if( ex instanceof BuildException ) + { + throw ( BuildException )ex; + } + else + { + throw new BuildException( "Error running jsp compiler: ", + ex, getJspc().getLocation() ); + } + } + } + + /* + * ------------------------------------------------------------ + */ + private Commandline setupJasperCommand() + { + Commandline cmd = new Commandline(); + JspC jspc = getJspc(); + if( jspc.getDestdir() != null ) + { + cmd.createArgument().setValue( "-d" ); + cmd.createArgument().setFile( jspc.getDestdir() ); + } + if( jspc.getPackage() != null ) + { + cmd.createArgument().setValue( "-p" ); + cmd.createArgument().setValue( jspc.getPackage() ); + } + if( jspc.getVerbose() != 0 ) + { + cmd.createArgument().setValue( "-v" + jspc.getVerbose() ); + } + if( jspc.isMapped() ) + { + cmd.createArgument().setValue( "-mapped" ); + } + if( jspc.getIeplugin() != null ) + { + cmd.createArgument().setValue( "-ieplugin" ); + cmd.createArgument().setValue( jspc.getIeplugin() ); + } + if( jspc.getUriroot() != null ) + { + cmd.createArgument().setValue( "-uriroot" ); + cmd.createArgument().setValue( jspc.getUriroot().toString() ); + } + if( jspc.getUribase() != null ) + { + cmd.createArgument().setValue( "-uribase" ); + cmd.createArgument().setValue( jspc.getUribase().toString() ); + } + logAndAddFilesToCompile( getJspc(), getJspc().getCompileList(), cmd ); + return cmd; + } + /* + * ------------------------------------------------------------ + */ +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java new file mode 100644 index 000000000..0d1e7e9d7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/AggregateTransformer.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + + + +/** + * Transform a JUnit xml report. The default transformation generates an html + * report in either framed or non-framed style. The non-framed style is + * convenient to have a concise report via mail, the framed report is much more + * convenient if you want to browse into different packages or testcases since + * it is a Javadoc like report. + * + * @author Stephane Bailliez + */ +public class AggregateTransformer +{ + + public final static String FRAMES = "frames"; + + public final static String NOFRAMES = "noframes"; + + /** + * XML Parser factory + */ + protected final static DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance(); + + /** + * the xml document to process + */ + protected Document document; + + /** + * the format to use for the report. Must be FRAMES or NOFRAMES + * + */ + protected String format; + + /** + * the style directory. XSLs should be read from here if necessary + */ + protected File styleDir; + + /** + * Task + */ + protected Task task; + + /** + * the destination directory, this is the root from where html should be + * generated + */ + protected File toDir; + + public AggregateTransformer( Task task ) + { + this.task = task; + } + + /** + * set the extension of the output files + * + * @param ext The new Extension value + */ + public void setExtension( String ext ) + { + task.log( "extension is not used anymore", Project.MSG_WARN ); + } + + public void setFormat( Format format ) + { + this.format = format.getValue(); + } + + /** + * set the style directory. It is optional and will override the default xsl + * used. + * + * @param styledir the directory containing the xsl files if the user would + * like to override with its own style. + */ + public void setStyledir( File styledir ) + { + this.styleDir = styledir; + } + + /** + * set the destination directory + * + * @param todir The new Todir value + */ + public void setTodir( File todir ) + { + this.toDir = todir; + } + + public void setXmlDocument( Document doc ) + { + this.document = doc; + } + + public void transform() + throws BuildException + { + checkOptions(); + final long t0 = System.currentTimeMillis(); + try + { + Element root = document.getDocumentElement(); + XalanExecutor executor = XalanExecutor.newInstance( this ); + executor.execute(); + } + catch( Exception e ) + { + throw new BuildException( "Errors while applying transformations", e ); + } + final long dt = System.currentTimeMillis() - t0; + task.log( "Transform time: " + dt + "ms" ); + } + + /** + * Set the xml file to be processed. This is a helper if you want to set the + * file directly. Much more for testing purposes. + * + * @param xmlfile xml file to be processed + * @exception BuildException Description of Exception + */ + protected void setXmlfile( File xmlfile ) + throws BuildException + { + try + { + DocumentBuilder builder = dbfactory.newDocumentBuilder(); + InputStream in = new FileInputStream( xmlfile ); + try + { + Document doc = builder.parse( in ); + setXmlDocument( doc ); + } + finally + { + in.close(); + } + } + catch( Exception e ) + { + throw new BuildException( "Error while parsing document: " + xmlfile, e ); + } + } + + /** + * Get the systemid of the appropriate stylesheet based on its name and + * styledir. If no styledir is defined it will load it as a java resource in + * the xsl child package, otherwise it will get it from the given directory. + * + * @return The StylesheetSystemId value + * @throws IOException thrown if the requested stylesheet does not exist. + */ + protected String getStylesheetSystemId() + throws IOException + { + String xslname = "junit-frames.xsl"; + if( NOFRAMES.equals( format ) ) + { + xslname = "junit-noframes.xsl"; + } + URL url = null; + if( styleDir == null ) + { + url = getClass().getResource( "xsl/" + xslname ); + if( url == null ) + { + throw new FileNotFoundException( "Could not find jar resource " + xslname ); + } + } + else + { + File file = new File( styleDir, xslname ); + if( !file.exists() ) + { + throw new FileNotFoundException( "Could not find file '" + file + "'" ); + } + url = new URL( "file", "", file.getAbsolutePath() ); + } + return url.toExternalForm(); + } + + /** + * check for invalid options + * + * @exception BuildException Description of Exception + */ + protected void checkOptions() + throws BuildException + { + // set the destination directory relative from the project if needed. + if( toDir == null ) + { + toDir = task.getProject().resolveFile( "." ); + } + else if( !toDir.isAbsolute() ) + { + toDir = task.getProject().resolveFile( toDir.getPath() ); + } + } + + public static class Format extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{FRAMES, NOFRAMES}; + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java new file mode 100644 index 000000000..b2173d3e8 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BaseTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.util.Vector; + +/** + * Baseclass for BatchTest and JUnitTest. + * + * @author Stefan Bodewig + * @author Stephane Bailliez + */ +public abstract class BaseTest +{ + protected boolean haltOnError = false; + protected boolean haltOnFail = false; + protected boolean filtertrace = true; + protected boolean fork = false; + protected String ifProperty = null; + protected String unlessProperty = null; + protected Vector formatters = new Vector(); + /** + * destination directory + */ + protected File destDir = null; + protected String errorProperty; + + protected String failureProperty; + + public void setErrorProperty( String errorProperty ) + { + this.errorProperty = errorProperty; + } + + public void setFailureProperty( String failureProperty ) + { + this.failureProperty = failureProperty; + } + + public void setFiltertrace( boolean value ) + { + filtertrace = value; + } + + public void setFork( boolean value ) + { + fork = value; + } + + public void setHaltonerror( boolean value ) + { + haltOnError = value; + } + + public void setHaltonfailure( boolean value ) + { + haltOnFail = value; + } + + public void setIf( String propertyName ) + { + ifProperty = propertyName; + } + + /** + * Sets the destination directory. + * + * @param destDir The new Todir value + */ + public void setTodir( File destDir ) + { + this.destDir = destDir; + } + + public void setUnless( String propertyName ) + { + unlessProperty = propertyName; + } + + public java.lang.String getErrorProperty() + { + return errorProperty; + } + + public java.lang.String getFailureProperty() + { + return failureProperty; + } + + public boolean getFiltertrace() + { + return filtertrace; + } + + public boolean getFork() + { + return fork; + } + + public boolean getHaltonerror() + { + return haltOnError; + } + + public boolean getHaltonfailure() + { + return haltOnFail; + } + + /** + * @return the destination directory as an absolute path if it exists + * otherwise return null + */ + public String getTodir() + { + if( destDir != null ) + { + return destDir.getAbsolutePath(); + } + return null; + } + + public void addFormatter( FormatterElement elem ) + { + formatters.addElement( elem ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java new file mode 100644 index 000000000..115e2c1a3 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BatchTest.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.util.Enumeration; +import java.util.Vector; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FileSet; + +/** + *

              + * + * Create then run JUnitTest's based on the list of files given by + * the fileset attribute.

              + * + * Every .java or .class file in the fileset is + * assumed to be a testcase. A JUnitTest is created for each of + * these named classes with basic setup inherited from the parent BatchTest + * . + * + * @author Jeff Martin + * @author Stefan Bodewig + * @author Stephane Bailliez + * @see JUnitTest + */ +public final class BatchTest extends BaseTest +{ + + /** + * the list of filesets containing the testcase filename rules + */ + private Vector filesets = new Vector(); + + /** + * the reference to the project + */ + private Project project; + + /** + * create a new batchtest instance + * + * @param project the project it depends on. + */ + public BatchTest( Project project ) + { + this.project = project; + } + + /** + * Convenient method to convert a pathname without extension to a fully + * qualified classname. For example org/apache/Whatever will be + * converted to org.apache.Whatever + * + * @param filename the filename to "convert" to a classname. + * @return the classname matching the filename. + */ + public final static String javaToClass( String filename ) + { + return filename.replace( File.separatorChar, '.' ); + } + + /** + * Return all JUnitTest instances obtain by applying the fileset + * rules. + * + * @return an enumeration of all elements of this batchtest that are a + * JUnitTest instance. + */ + public final Enumeration elements() + { + JUnitTest[] tests = createAllJUnitTest(); + return Enumerations.fromArray( tests ); + } + + /** + * Add a new fileset instance to this batchtest. Whatever the fileset is, + * only filename that are .java or .class will be + * considered as 'candidates'. + * + * @param fs the new fileset containing the rules to get the testcases. + */ + public void addFileSet( FileSet fs ) + { + filesets.addElement( fs ); + } + + /** + * Convenient method to merge the JUnitTest s of this batchtest to + * a Vector . + * + * @param v the vector to which should be added all individual tests of this + * batch test. + */ + final void addTestsTo( Vector v ) + { + JUnitTest[] tests = createAllJUnitTest(); + v.ensureCapacity( v.size() + tests.length ); + for( int i = 0; i < tests.length; i++ ) + { + v.addElement( tests[i] ); + } + } + + /** + * Iterate over all filesets and return the filename of all files that end + * with .java or .class . This is to avoid wrapping a + * JUnitTest over an xml file for example. A Testcase is obviously a + * java file (compiled or not). + * + * @return an array of filenames without their extension. As they should + * normally be taken from their root, filenames should match their + * fully qualified class name (If it is not the case it will fail when + * running the test). For the class org/apache/Whatever.class + * it will return org/apache/Whatever . + */ + private String[] getFilenames() + { + Vector v = new Vector(); + final int size = this.filesets.size(); + for( int j = 0; j < size; j++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( j ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + ds.scan(); + String[] f = ds.getIncludedFiles(); + for( int k = 0; k < f.length; k++ ) + { + String pathname = f[k]; + if( pathname.endsWith( ".java" ) ) + { + v.addElement( pathname.substring( 0, pathname.length() - ".java".length() ) ); + } + else if( pathname.endsWith( ".class" ) ) + { + v.addElement( pathname.substring( 0, pathname.length() - ".class".length() ) ); + } + } + } + + String[] files = new String[v.size()]; + v.copyInto( files ); + return files; + } + + /** + * Create all JUnitTest s based on the filesets. Each instance is + * configured to match this instance properties. + * + * @return the array of all JUnitTest s that belongs to this batch. + */ + private JUnitTest[] createAllJUnitTest() + { + String[] filenames = getFilenames(); + JUnitTest[] tests = new JUnitTest[filenames.length]; + for( int i = 0; i < tests.length; i++ ) + { + String classname = javaToClass( filenames[i] ); + tests[i] = createJUnitTest( classname ); + } + return tests; + } + + /** + * Create a JUnitTest that has the same property as this + * BatchTest instance. + * + * @param classname the name of the class that should be run as a + * JUnitTest . It must be a fully qualified name. + * @return the JUnitTest over the given classname. + */ + private JUnitTest createJUnitTest( String classname ) + { + JUnitTest test = new JUnitTest(); + test.setName( classname ); + test.setHaltonerror( this.haltOnError ); + test.setHaltonfailure( this.haltOnFail ); + test.setFiltertrace( this.filtertrace ); + test.setFork( this.fork ); + test.setIf( this.ifProperty ); + test.setUnless( this.unlessProperty ); + test.setTodir( this.destDir ); + test.setFailureProperty( failureProperty ); + test.setErrorProperty( errorProperty ); + Enumeration list = this.formatters.elements(); + while( list.hasMoreElements() ) + { + test.addFormatter( ( FormatterElement )list.nextElement() ); + } + return test; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java new file mode 100644 index 000000000..81ee9ac8f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/BriefJUnitResultFormatter.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import org.apache.tools.ant.BuildException; + +/** + * Prints plain text output of the test to a specified Writer. Inspired by the + * PlainJUnitResultFormatter. + * + * @author Robert Watkins + * @see FormatterElement + * @see PlainJUnitResultFormatter + */ +public class BriefJUnitResultFormatter implements JUnitResultFormatter +{ + + /** + * Formatter for timings. + */ + private java.text.NumberFormat m_numberFormat = java.text.NumberFormat.getInstance(); + + /** + * Output suite has written to System.out + */ + private String systemOutput = null; + + /** + * Output suite has written to System.err + */ + private String systemError = null; + + /** + * Where to write the log to. + */ + private java.io.OutputStream m_out; + + /** + * Used for writing the results. + */ + private java.io.PrintWriter m_output; + + /** + * Used for writing formatted results to. + */ + private java.io.PrintWriter m_resultWriter; + + /** + * Used as part of formatting the results. + */ + private java.io.StringWriter m_results; + + public BriefJUnitResultFormatter() + { + m_results = new java.io.StringWriter(); + m_resultWriter = new java.io.PrintWriter( m_results ); + } + + /** + * Sets the stream the formatter is supposed to write its results to. + * + * @param out The new Output value + */ + public void setOutput( java.io.OutputStream out ) + { + m_out = out; + m_output = new java.io.PrintWriter( out ); + } + + public void setSystemError( String err ) + { + systemError = err; + } + + public void setSystemOutput( String out ) + { + systemOutput = out; + } + + /** + * A test caused an error. + * + * @param test The feature to be added to the Error attribute + * @param error The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable error ) + { + formatError( "\tCaused an ERROR", test, error ); + } + + /** + * Interface TestListener for JUnit <= 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + formatError( "\tFAILED", test, t ); + } + + /** + * Interface TestListener for JUnit > 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * A test ended. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) { } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + String newLine = System.getProperty( "line.separator" ); + StringBuffer sb = new StringBuffer( "Testsuite: " ); + sb.append( suite.getName() ); + sb.append( newLine ); + sb.append( "Tests run: " ); + sb.append( suite.runCount() ); + sb.append( ", Failures: " ); + sb.append( suite.failureCount() ); + sb.append( ", Errors: " ); + sb.append( suite.errorCount() ); + sb.append( ", Time elapsed: " ); + sb.append( m_numberFormat.format( suite.getRunTime() / 1000.0 ) ); + sb.append( " sec" ); + sb.append( newLine ); + sb.append( newLine ); + + // append the err and output streams to the log + if( systemOutput != null && systemOutput.length() > 0 ) + { + sb.append( "------------- Standard Output ---------------" ) + .append( newLine ) + .append( systemOutput ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + if( systemError != null && systemError.length() > 0 ) + { + sb.append( "------------- Standard Error -----------------" ) + .append( newLine ) + .append( systemError ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + if( output() != null ) + { + try + { + output().write( sb.toString() ); + resultWriter().close(); + output().write( m_results.toString() ); + output().flush(); + } + finally + { + if( m_out != ( Object )System.out && + m_out != ( Object )System.err ) + { + try + { + m_out.close(); + } + catch( java.io.IOException e ) + {} + } + } + } + } + + /** + * A test started. + * + * @param test Description of Parameter + */ + public void startTest( Test test ) { } + + /** + * The whole testsuite started. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void startTestSuite( JUnitTest suite ) + throws BuildException { } + + /** + * Format an error and print it. + * + * @param type Description of Parameter + * @param test Description of Parameter + * @param error Description of Parameter + */ + protected synchronized void formatError( String type, Test test, + Throwable error ) + { + if( test != null ) + { + endTest( test ); + } + + resultWriter().println( formatTest( test ) + type ); + resultWriter().println( error.getMessage() ); + String strace = JUnitTestRunner.getFilteredTrace( error ); + resultWriter().println( strace ); + resultWriter().println( "" ); + } + + /** + * Format the test for printing.. + * + * @param test Description of Parameter + * @return Description of the Returned Value + */ + protected String formatTest( Test test ) + { + if( test == null ) + { + return "Null Test: "; + } + else + { + return "Testcase: " + test.toString() + ":"; + } + } + + protected java.io.PrintWriter output() + { + return m_output; + } + + protected java.io.PrintWriter resultWriter() + { + return m_resultWriter; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java new file mode 100644 index 000000000..3fab8d7be --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/DOMUtil.java @@ -0,0 +1,249 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.util.Vector; +import org.w3c.dom.Attr; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Comment; +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.ProcessingInstruction; +import org.w3c.dom.Text; + +/** + * Some utilities that might be useful when manipulating DOM trees. + * + * @author Stephane Bailliez + */ +public final class DOMUtil +{ + + /** + * unused constructor + */ + private DOMUtil() { } + + + /** + * Iterate over the children of a given node and return the first node that + * has a specific name. + * + * @param parent the node to search child from. Can be null . + * @param tagname the child name we are looking for. Cannot be null + * . + * @return the first child that matches the given name or null if + * the parent is null or if a child does not match the given + * name. + */ + public static Element getChildByTagName( Node parent, String tagname ) + { + if( parent == null ) + { + return null; + } + NodeList childList = parent.getChildNodes(); + final int len = childList.getLength(); + for( int i = 0; i < len; i++ ) + { + Node child = childList.item( i ); + if( child != null && child.getNodeType() == Node.ELEMENT_NODE && + child.getNodeName().equals( tagname ) ) + { + return ( Element )child; + } + } + return null; + } + + /** + * return the attribute value of an element. + * + * @param node the node to get the attribute from. + * @param name the name of the attribute we are looking for the value. + * @return the value of the requested attribute or null if the + * attribute was not found or if node is not an Element + * . + */ + public static String getNodeAttribute( Node node, String name ) + { + if( node instanceof Element ) + { + Element element = ( Element )node; + return element.getAttribute( name ); + } + return null; + } + + /** + * Simple tree walker that will clone recursively a node. This is to avoid + * using parser-specific API such as Sun's changeNodeOwner when we + * are dealing with DOM L1 implementations since cloneNode(boolean) + * will not change the owner document. changeNodeOwner is much + * faster and avoid the costly cloning process. importNode is in + * the DOM L2 interface. + * + * @param parent the node parent to which we should do the import to. + * @param child the node to clone recursively. Its clone will be appended to + * parent . + * @return the cloned node that is appended to parent + */ + public final static Node importNode( Node parent, Node child ) + { + Node copy = null; + final Document doc = parent.getOwnerDocument(); + + switch ( child.getNodeType() ) + { + case Node.CDATA_SECTION_NODE: + copy = doc.createCDATASection( ( ( CDATASection )child ).getData() ); + break; + case Node.COMMENT_NODE: + copy = doc.createComment( ( ( Comment )child ).getData() ); + break; + case Node.DOCUMENT_FRAGMENT_NODE: + copy = doc.createDocumentFragment(); + break; + case Node.ELEMENT_NODE: + final Element elem = doc.createElement( ( ( Element )child ).getTagName() ); + copy = elem; + final NamedNodeMap attributes = child.getAttributes(); + if( attributes != null ) + { + final int size = attributes.getLength(); + for( int i = 0; i < size; i++ ) + { + final Attr attr = ( Attr )attributes.item( i ); + elem.setAttribute( attr.getName(), attr.getValue() ); + } + } + break; + case Node.ENTITY_REFERENCE_NODE: + copy = doc.createEntityReference( child.getNodeName() ); + break; + case Node.PROCESSING_INSTRUCTION_NODE: + final ProcessingInstruction pi = ( ProcessingInstruction )child; + copy = doc.createProcessingInstruction( pi.getTarget(), pi.getData() ); + break; + case Node.TEXT_NODE: + copy = doc.createTextNode( ( ( Text )child ).getData() ); + break; + default: + // this should never happen + throw new IllegalStateException( "Invalid node type: " + child.getNodeType() ); + } + + // okay we have a copy of the child, now the child becomes the parent + // and we are iterating recursively over its children. + try + { + final NodeList children = child.getChildNodes(); + if( children != null ) + { + final int size = children.getLength(); + for( int i = 0; i < size; i++ ) + { + final Node newChild = children.item( i ); + if( newChild != null ) + { + importNode( copy, newChild ); + } + } + } + } + catch( DOMException ignored ) + { + } + + // bingo append it. (this should normally not be done here) + parent.appendChild( copy ); + return copy; + } + + /** + * list a set of node that match a specific filter. The list can be made + * recursively or not. + * + * @param parent the parent node to search from + * @param filter the filter that children should match. + * @param recurse true if you want the list to be made recursively + * otherwise false . + * @return Description of the Returned Value + */ + public static NodeList listChildNodes( Node parent, NodeFilter filter, boolean recurse ) + { + NodeListImpl matches = new NodeListImpl(); + NodeList children = parent.getChildNodes(); + if( children != null ) + { + final int len = children.getLength(); + for( int i = 0; i < len; i++ ) + { + Node child = children.item( i ); + if( filter.accept( child ) ) + { + matches.addElement( child ); + } + if( recurse ) + { + NodeList recmatches = listChildNodes( child, filter, recurse ); + final int reclength = matches.getLength(); + for( int j = 0; j < reclength; j++ ) + { + matches.addElement( recmatches.item( i ) ); + } + } + } + } + return matches; + } + + /** + * Filter interface to be applied when iterating over a DOM tree. Just think + * of it like a FileFilter clone. + * + * @author RT + */ + public interface NodeFilter + { + /** + * @param node the node to check for acceptance. + * @return true if the node is accepted by this filter, + * otherwise false + */ + boolean accept( Node node ); + } + + /** + * custom implementation of a nodelist + * + * @author RT + */ + public static class NodeListImpl extends Vector implements NodeList + { + public int getLength() + { + return size(); + } + + public Node item( int i ) + { + try + { + return ( Node )elementAt( i ); + } + catch( ArrayIndexOutOfBoundsException e ) + { + return null;// conforming to NodeList interface + } + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java new file mode 100644 index 000000000..bc75a4e5a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Enumerations.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * A couple of methods related to enumerations that might be useful. This class + * should probably disappear once the required JDK is set to 1.2 instead of 1.1. + * + * @author Stephane Bailliez + */ +public final class Enumerations +{ + + private Enumerations() { } + + /** + * creates an enumeration from an array of objects. + * + * @param array the array of object to enumerate. + * @return the enumeration over the array of objects. + */ + public static Enumeration fromArray( Object[] array ) + { + return new ArrayEnumeration( array ); + } + + /** + * creates an enumeration from an array of enumeration. The created + * enumeration will sequentially enumerate over all elements of each + * enumeration and skip null enumeration elements in the array. + * + * @param enums the array of enumerations. + * @return the enumeration over the array of enumerations. + */ + public static Enumeration fromCompound( Enumeration[] enums ) + { + return new CompoundEnumeration( enums ); + } + +} + + +/** + * Convenient enumeration over an array of objects. + * + * @author Stephane Bailliez + */ +class ArrayEnumeration implements Enumeration +{ + + /** + * object array + */ + private Object[] array; + + /** + * current index + */ + private int pos; + + /** + * Initialize a new enumeration that wraps an array. + * + * @param array the array of object to enumerate. + */ + public ArrayEnumeration( Object[] array ) + { + this.array = array; + this.pos = 0; + } + + /** + * Tests if this enumeration contains more elements. + * + * @return true if and only if this enumeration object contains + * at least one more element to provide; false otherwise. + */ + public boolean hasMoreElements() + { + return ( pos < array.length ); + } + + /** + * Returns the next element of this enumeration if this enumeration object + * has at least one more element to provide. + * + * @return the next element of this enumeration. + * @throws NoSuchElementException if no more elements exist. + */ + public Object nextElement() + throws NoSuchElementException + { + if( hasMoreElements() ) + { + Object o = array[pos]; + pos++; + return o; + } + throw new NoSuchElementException(); + } +} + +/** + * Convenient enumeration over an array of enumeration. For example:

              + * Enumeration e1 = v1.elements();
              + * while (e1.hasMoreElements()){
              + *    // do something
              + * }
              + * Enumeration e2 = v2.elements();
              + * while (e2.hasMoreElements()){
              + *    // do the same thing
              + * }
              + * 
              can be written as:
              + * Enumeration[] enums = { v1.elements(), v2.elements() };
              + * Enumeration e = Enumerations.fromCompound(enums);
              + * while (e.hasMoreElements()){
              + *    // do something
              + * }
              + * 
              Note that the enumeration will skip null elements in the array. The + * following is thus possible:
              + * Enumeration[] enums = { v1.elements(), null, v2.elements() }; // a null enumeration in the array
              + * Enumeration e = Enumerations.fromCompound(enums);
              + * while (e.hasMoreElements()){
              + *    // do something
              + * }
              + * 
              + * + * @author Stephane Bailliez + */ +class CompoundEnumeration implements Enumeration +{ + + /** + * index in the enums array + */ + private int index = 0; + + /** + * enumeration array + */ + private Enumeration[] enumArray; + + public CompoundEnumeration( Enumeration[] enumarray ) + { + this.enumArray = enumarray; + } + + /** + * Tests if this enumeration contains more elements. + * + * @return true if and only if this enumeration object contains + * at least one more element to provide; false otherwise. + */ + public boolean hasMoreElements() + { + while( index < enumArray.length ) + { + if( enumArray[index] != null && enumArray[index].hasMoreElements() ) + { + return true; + } + index++; + } + return false; + } + + /** + * Returns the next element of this enumeration if this enumeration object + * has at least one more element to provide. + * + * @return the next element of this enumeration. + * @throws NoSuchElementException if no more elements exist. + */ + public Object nextElement() + throws NoSuchElementException + { + if( hasMoreElements() ) + { + return enumArray[index].nextElement(); + } + throw new NoSuchElementException(); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java new file mode 100644 index 000000000..b7db0aa61 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/FormatterElement.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.types.EnumeratedAttribute; + +/** + *

              + * + * A wrapper for the implementations of JUnitResultFormatter. In + * particular, used as a nested <formatter> element in a + * <junit> task.

              + * + * For example,

              + *       <junit printsummary="no" haltonfailure="yes" fork="false">
              + *           <formatter type="plain" usefile="false" />
              + *           <test name="org.apache.ecs.InternationalCharTest" />
              + *       </junit>
              adds a plain type + * implementation (PlainJUnitResultFormatter) to display the + * results of the test.

              + * + * Either the type or the classname attribute must be + * set. + * + * @author Stefan Bodewig + * @see JUnitTask + * @see XMLJUnitResultFormatter + * @see BriefJUnitResultFormatter + * @see PlainJUnitResultFormatter + * @see JUnitResultFormatter + */ +public class FormatterElement +{ + private OutputStream out = System.out; + private boolean useFile = true; + + private String classname; + private String extension; + private File outFile; + + /** + *

              + * + * Set name of class to be used as the formatter.

              + * + * This class must implement JUnitResultFormatter + * + * @param classname The new Classname value + */ + public void setClassname( String classname ) + { + this.classname = classname; + } + + public void setExtension( String ext ) + { + this.extension = ext; + } + + /** + *

              + * + * Set output stream for formatter to use.

              + * + * Defaults to standard out. + * + * @param out The new Output value + */ + public void setOutput( OutputStream out ) + { + this.out = out; + } + + /** + *

              + * + * Quick way to use a standard formatter.

              + * + * At the moment, there are three supported standard formatters. + *

                + *
              • The xml type uses a XMLJUnitResultFormatter + * . + *
              • The brief type uses a BriefJUnitResultFormatter + * . + *
              • The plain type (the default) uses a PlainJUnitResultFormatter + * . + *
              + *

              + * + * Sets classname attribute - so you can't use that attribute + * if you use this one. + * + * @param type The new Type value + */ + public void setType( TypeAttribute type ) + { + if( "xml".equals( type.getValue() ) ) + { + setClassname( "org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter" ); + setExtension( ".xml" ); + } + else + { + if( "brief".equals( type.getValue() ) ) + { + setClassname( "org.apache.tools.ant.taskdefs.optional.junit.BriefJUnitResultFormatter" ); + } + else + {// must be plain, ensured by TypeAttribute + setClassname( "org.apache.tools.ant.taskdefs.optional.junit.PlainJUnitResultFormatter" ); + } + setExtension( ".txt" ); + } + } + + /** + * Set whether the formatter should log to file. + * + * @param useFile The new UseFile value + */ + public void setUseFile( boolean useFile ) + { + this.useFile = useFile; + } + + /** + * Get name of class to be used as the formatter. + * + * @return The Classname value + */ + public String getClassname() + { + return classname; + } + + public String getExtension() + { + return extension; + } + + /** + *

              + * + * Set the file which the formatte should log to.

              + * + * Note that logging to file must be enabled . + * + * @param out The new Outfile value + */ + void setOutfile( File out ) + { + this.outFile = out; + } + + /** + * Get whether the formatter should log to file. + * + * @return The UseFile value + */ + boolean getUseFile() + { + return useFile; + } + + JUnitResultFormatter createFormatter() + throws BuildException + { + if( classname == null ) + { + throw new BuildException( "you must specify type or classname" ); + } + + Class f = null; + try + { + f = Class.forName( classname ); + } + catch( ClassNotFoundException e ) + { + throw new BuildException( e ); + } + + Object o = null; + try + { + o = f.newInstance(); + } + catch( InstantiationException e ) + { + throw new BuildException( e ); + } + catch( IllegalAccessException e ) + { + throw new BuildException( e ); + } + + if( !( o instanceof JUnitResultFormatter ) ) + { + throw new BuildException( classname + " is not a JUnitResultFormatter" ); + } + + JUnitResultFormatter r = ( JUnitResultFormatter )o; + + if( useFile && outFile != null ) + { + try + { + out = new FileOutputStream( outFile ); + } + catch( java.io.IOException e ) + { + throw new BuildException( e ); + } + } + r.setOutput( out ); + return r; + } + + /** + *

              + * + * Enumerated attribute with the values "plain", "xml" and "brief".

              + * + * Use to enumerate options for type attribute. + * + * @author RT + */ + public static class TypeAttribute extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"plain", "xml", "brief"}; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java new file mode 100644 index 000000000..7f5f3a4ed --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitResultFormatter.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.OutputStream; +import junit.framework.TestListener; +import org.apache.tools.ant.BuildException; + +/** + * This Interface describes classes that format the results of a JUnit testrun. + * + * @author Stefan Bodewig + */ +public interface JUnitResultFormatter extends TestListener +{ + /** + * The whole testsuite started. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + void startTestSuite( JUnitTest suite ) + throws BuildException; + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + void endTestSuite( JUnitTest suite ) + throws BuildException; + + /** + * Sets the stream the formatter is supposed to write its results to. + * + * @param out The new Output value + */ + void setOutput( OutputStream out ); + + /** + * This is what the test has written to System.out + * + * @param out The new SystemOutput value + */ + void setSystemOutput( String out ); + + /** + * This is what the test has written to System.err + * + * @param err The new SystemError value + */ + void setSystemError( String err ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java new file mode 100644 index 000000000..b53a45da3 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTask.java @@ -0,0 +1,812 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Random; +import java.util.Vector; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteWatchdog; +import org.apache.tools.ant.taskdefs.LogOutputStream; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.EnumeratedAttribute; +import org.apache.tools.ant.types.Environment; +import org.apache.tools.ant.types.Path; + +/** + * Ant task to run JUnit tests.

              + * + * JUnit is a framework to create unit test. It has been initially created by + * Erich Gamma and Kent Beck. JUnit can be found at http://www.junit.org .

              + * + * JUnitTask can run a single specific JUnitTest using + * the test element. For example, the following target

              + *   <target name="test-int-chars" depends="jar-test">
              + *       <echo message="testing international characters"/>
              + *       <junit printsummary="no" haltonfailure="yes" fork="false">
              + *           <classpath refid="classpath"/>
              + *           <formatter type="plain" usefile="false" />
              + *           <test name="org.apache.ecs.InternationalCharTest" />
              + *       </junit>
              + *   </target>
              + * 
              runs a single junit test (org.apache.ecs.InternationalCharTest + * ) in the current VM using the path with id classpath as + * classpath and presents the results formatted using the standard plain + * formatter on the command line.

              + * + * This task can also run batches of tests. The batchtest element + * creates a BatchTest based on a fileset. This allows, for + * example, all classes found in directory to be run as testcases. For example, + *

              + * <target name="run-tests" depends="dump-info,compile-tests" if="junit.present">
              + *   <junit printsummary="no" haltonfailure="yes" fork="${junit.fork}">
              + *     <jvmarg value="-classic"/>
              + *     <classpath refid="tests-classpath"/>
              + *     <sysproperty key="build.tests" value="${build.tests}"/>
              + *     <formatter type="brief" usefile="false" />
              + *     <batchtest>
              + *       <fileset dir="${tests.dir}">
              + *         <include name="**/*Test*" />
              + *       </fileset>
              + *     </batchtest>
              + *   </junit>
              + * </target>
              + * 
              this target finds any classes with a test + * directory anywhere in their path (under the top ${tests.dir}, of + * course) and creates JUnitTest's for each one.

              + * + * Of course, <junit> and <batch> elements + * can be combined for more complex tests. For an example, see the ant build.xml + * target run-tests (the second example is an edited version).

              + * + * To spawn a new Java VM to prevent interferences between different testcases, + * you need to enable fork. A number of attributes and elements + * allow you to set up how this JVM runs. + *

                + *
              • {@link #setTimeout} property sets the maximum time allowed before a + * test is 'timed out' + *
              • {@link #setMaxmemory} property sets memory assignment for the forked + * jvm + *
              • {@link #setJvm} property allows the jvm to be specified + *
              • The <jvmarg> element sets arguements to be passed + * to the forked jvm + *
              + * + * + * @author Thomas Haas + * @author Stefan Bodewig + * @author Stephane Bailliez + * @author Gerrit Riessen + * @author Erik Hatcher + * @see JUnitTest + * @see BatchTest + */ +public class JUnitTask extends Task +{ + + private CommandlineJava commandline = new CommandlineJava(); + private Vector tests = new Vector(); + private Vector batchTests = new Vector(); + private Vector formatters = new Vector(); + private File dir = null; + + private Integer timeout = null; + private boolean summary = false; + private String summaryValue = ""; + private boolean filtertrace = true; + private JUnitTestRunner runner = null; + + /** + * Creates a new JUnitRunner and enables fork of a new Java VM. + * + * @exception Exception Description of Exception + */ + public JUnitTask() + throws Exception + { + commandline.setClassname( "org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" ); + } + + /** + * The directory to invoke the VM in. Ignored if no JVM is forked. + * + * @param dir the directory to invoke the JVM from. + * @see #setFork(boolean) + */ + public void setDir( File dir ) + { + this.dir = dir; + } + + /** + * Tells this task to set the named property to "true" when there is a error + * in a test. This property is applied on all BatchTest (batchtest) and + * JUnitTest (test), however, it can possibly be overriden by their own + * properties. + * + * @param propertyName The new ErrorProperty value + */ + public void setErrorProperty( String propertyName ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setErrorProperty( propertyName ); + } + } + + /** + * Tells this task to set the named property to "true" when there is a + * failure in a test. This property is applied on all BatchTest (batchtest) + * and JUnitTest (test), however, it can possibly be overriden by their own + * properties. + * + * @param propertyName The new FailureProperty value + */ + public void setFailureProperty( String propertyName ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setFailureProperty( propertyName ); + } + } + + /** + * Tells this task whether to smartly filter the stack frames of JUnit + * testcase errors and failures before reporting them. This property is + * applied on all BatchTest (batchtest) and JUnitTest (test) however it can + * possibly be overridden by their own properties. + * + * @param value false if it should not filter, otherwise true + * + */ + public void setFiltertrace( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setFiltertrace( value ); + } + } + + /** + * Tells whether a JVM should be forked for each testcase. It avoids + * interference between testcases and possibly avoids hanging the build. + * this property is applied on all BatchTest (batchtest) and JUnitTest + * (test) however it can possibly be overridden by their own properties. + * + * @param value true if a JVM should be forked, otherwise false + * + * @see #setTimeout + */ + public void setFork( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setFork( value ); + } + } + + /** + * Tells this task to halt when there is an error in a test. this property + * is applied on all BatchTest (batchtest) and JUnitTest (test) however it + * can possibly be overridden by their own properties. + * + * @param value true if it should halt, otherwise false + */ + public void setHaltonerror( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setHaltonerror( value ); + } + } + + /** + * Tells this task to halt when there is a failure in a test. this property + * is applied on all BatchTest (batchtest) and JUnitTest (test) however it + * can possibly be overridden by their own properties. + * + * @param value true if it should halt, otherwise false + */ + public void setHaltonfailure( boolean value ) + { + Enumeration enum = allTests(); + while( enum.hasMoreElements() ) + { + BaseTest test = ( BaseTest )enum.nextElement(); + test.setHaltonfailure( value ); + } + } + + /** + * Set a new VM to execute the testcase. Default is java . Ignored + * if no JVM is forked. + * + * @param value the new VM to use instead of java + * @see #setFork(boolean) + */ + public void setJvm( String value ) + { + commandline.setVm( value ); + } + + /** + * Set the maximum memory to be used by all forked JVMs. + * + * @param max the value as defined by -mx or -Xmx in the + * java command line options. + */ + public void setMaxmemory( String max ) + { + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + createJvmarg().setValue( "-mx" + max ); + } + else + { + createJvmarg().setValue( "-Xmx" + max ); + } + } + + /** + * Tells whether the task should print a short summary of the task. + * + * @param value true to print a summary, withOutAndErr to + * include the test's output as well, false otherwise. + * @see SummaryJUnitResultFormatter + */ + public void setPrintsummary( SummaryAttribute value ) + { + summaryValue = value.getValue(); + summary = value.asBoolean(); + } + + /** + * Set the timeout value (in milliseconds). If the test is running for more + * than this value, the test will be canceled. (works only when in 'fork' + * mode). + * + * @param value the maximum time (in milliseconds) allowed before declaring + * the test as 'timed-out' + * @see #setFork(boolean) + */ + public void setTimeout( Integer value ) + { + timeout = value; + } + + /** + * Add a new formatter to all tests of this task. + * + * @param fe The feature to be added to the Formatter attribute + */ + public void addFormatter( FormatterElement fe ) + { + formatters.addElement( fe ); + } + + /** + * Add a nested sysproperty element. This might be useful to tranfer Ant + * properties to the testcases when JVM forking is not enabled. + * + * @param sysp The feature to be added to the Sysproperty attribute + */ + public void addSysproperty( Environment.Variable sysp ) + { + commandline.addSysproperty( sysp ); + } + + /** + * Add a new single testcase. + * + * @param test a new single testcase + * @see JUnitTest + */ + public void addTest( JUnitTest test ) + { + tests.addElement( test ); + } + + /** + * Create a new set of testcases (also called ..batchtest) and add it to the + * list. + * + * @return a new instance of a batch test. + * @see BatchTest + */ + public BatchTest createBatchTest() + { + BatchTest test = new BatchTest( project ); + batchTests.addElement( test ); + return test; + } + + /** + * <classpath> allows classpath to be set for tests. + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + return commandline.createClasspath( project ).createPath(); + } + + /** + * Create a new JVM argument. Ignored if no JVM is forked. + * + * @return create a new JVM argument so that any argument can be passed to + * the JVM. + * @see #setFork(boolean) + */ + public Commandline.Argument createJvmarg() + { + return commandline.createVmArgument(); + } + + /** + * Runs the testcase. + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + Enumeration list = getIndividualTests(); + while( list.hasMoreElements() ) + { + JUnitTest test = ( JUnitTest )list.nextElement(); + if( test.shouldRun( project ) ) + { + execute( test ); + } + } + } + + /** + * Adds the jars or directories containing Ant, this task and JUnit to the + * classpath - this should make the forked JVM work without having to + * specify them directly. + */ + public void init() + { + addClasspathEntry( "/junit/framework/TestCase.class" ); + addClasspathEntry( "/org/apache/tools/ant/Task.class" ); + addClasspathEntry( "/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.class" ); + } + + /** + * Get the default output for a formatter. + * + * @return The DefaultOutput value + */ + protected OutputStream getDefaultOutput() + { + return new LogOutputStream( this, Project.MSG_INFO ); + } + + /** + * Merge all individual tests from the batchtest with all individual tests + * and return an enumeration over all JUnitTest . + * + * @return The IndividualTests value + */ + protected Enumeration getIndividualTests() + { + Enumeration[] enums = new Enumeration[batchTests.size() + 1]; + for( int i = 0; i < batchTests.size(); i++ ) + { + BatchTest batchtest = ( BatchTest )batchTests.elementAt( i ); + enums[i] = batchtest.elements(); + } + enums[enums.length - 1] = tests.elements(); + return Enumerations.fromCompound( enums ); + } + + /** + * return the file or null if does not use a file + * + * @param fe Description of Parameter + * @param test Description of Parameter + * @return The Output value + */ + protected File getOutput( FormatterElement fe, JUnitTest test ) + { + if( fe.getUseFile() ) + { + String filename = test.getOutfile() + fe.getExtension(); + File destFile = new File( test.getTodir(), filename ); + String absFilename = destFile.getAbsolutePath(); + return project.resolveFile( absFilename ); + } + return null; + } + + /** + * Search for the given resource and add the directory or archive that + * contains it to the classpath.

              + * + * Doesn't work for archives in JDK 1.1 as the URL returned by getResource + * doesn't contain the name of the archive.

              + * + * @param resource The feature to be added to the ClasspathEntry attribute + */ + protected void addClasspathEntry( String resource ) + { + URL url = getClass().getResource( resource ); + if( url != null ) + { + String u = url.toString(); + if( u.startsWith( "jar:file:" ) ) + { + int pling = u.indexOf( "!" ); + String jarName = u.substring( 9, pling ); + log( "Implicitly adding " + jarName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( jarName ) ).getAbsolutePath() ) ); + } + else if( u.startsWith( "file:" ) ) + { + int tail = u.indexOf( resource ); + String dirName = u.substring( 5, tail ); + log( "Implicitly adding " + dirName + " to classpath", + Project.MSG_DEBUG ); + createClasspath().setLocation( new File( ( new File( dirName ) ).getAbsolutePath() ) ); + } + else + { + log( "Don\'t know how to handle resource URL " + u, + Project.MSG_DEBUG ); + } + } + else + { + log( "Couldn\'t find " + resource, Project.MSG_DEBUG ); + } + } + + protected Enumeration allTests() + { + Enumeration[] enums = {tests.elements(), batchTests.elements()}; + return Enumerations.fromCompound( enums ); + } + + /** + * @return null if there is a timeout value, otherwise the watchdog + * instance. + * @exception BuildException Description of Exception + */ + protected ExecuteWatchdog createWatchdog() + throws BuildException + { + if( timeout == null ) + { + return null; + } + return new ExecuteWatchdog( timeout.intValue() ); + } + + /** + * Run the tests. + * + * @param test Description of Parameter + * @exception BuildException Description of Exception + */ + protected void execute( JUnitTest test ) + throws BuildException + { + // set the default values if not specified + //@todo should be moved to the test class instead. + if( test.getTodir() == null ) + { + test.setTodir( project.resolveFile( "." ) ); + } + + if( test.getOutfile() == null ) + { + test.setOutfile( "TEST-" + test.getName() ); + } + + // execute the test and get the return code + int exitValue = JUnitTestRunner.ERRORS; + boolean wasKilled = false; + if( !test.getFork() ) + { + exitValue = executeInVM( test ); + } + else + { + ExecuteWatchdog watchdog = createWatchdog(); + exitValue = executeAsForked( test, watchdog ); + // null watchdog means no timeout, you'd better not check with null + if( watchdog != null ) + { + wasKilled = watchdog.killedProcess(); + } + } + + // if there is an error/failure and that it should halt, stop everything otherwise + // just log a statement + boolean errorOccurredHere = exitValue == JUnitTestRunner.ERRORS; + boolean failureOccurredHere = exitValue != JUnitTestRunner.SUCCESS; + if( errorOccurredHere || failureOccurredHere ) + { + if( errorOccurredHere && test.getHaltonerror() + || failureOccurredHere && test.getHaltonfailure() ) + { + throw new BuildException( "Test " + test.getName() + " failed" + + ( wasKilled ? " (timeout)" : "" ), + location ); + } + else + { + log( "TEST " + test.getName() + " FAILED" + + ( wasKilled ? " (timeout)" : "" ), Project.MSG_ERR ); + if( errorOccurredHere && test.getErrorProperty() != null ) + { + project.setProperty( test.getErrorProperty(), "true" ); + } + if( failureOccurredHere && test.getFailureProperty() != null ) + { + project.setProperty( test.getFailureProperty(), "true" ); + } + } + } + } + + protected void handleErrorOutput( String line ) + { + if( runner != null ) + { + runner.handleErrorOutput( line ); + } + else + { + super.handleErrorOutput( line ); + } + } + + // in VM is not very nice since it could probably hang the + // whole build. IMHO this method should be avoided and it would be best + // to remove it in future versions. TBD. (SBa) + + + protected void handleOutput( String line ) + { + if( runner != null ) + { + runner.handleOutput( line ); + } + else + { + super.handleOutput( line ); + } + } + + /** + * Execute a testcase by forking a new JVM. The command will block until it + * finishes. To know if the process was destroyed or not, use the + * killedProcess() method of the watchdog class. + * + * @param test the testcase to execute. + * @param watchdog the watchdog in charge of cancelling the test if it + * exceeds a certain amount of time. Can be null , in this + * case the test could probably hang forever. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int executeAsForked( JUnitTest test, ExecuteWatchdog watchdog ) + throws BuildException + { + CommandlineJava cmd = ( CommandlineJava )commandline.clone(); + + cmd.setClassname( "org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner" ); + cmd.createArgument().setValue( test.getName() ); + cmd.createArgument().setValue( "filtertrace=" + test.getFiltertrace() ); + cmd.createArgument().setValue( "haltOnError=" + test.getHaltonerror() ); + cmd.createArgument().setValue( "haltOnFailure=" + test.getHaltonfailure() ); + if( summary ) + { + log( "Running " + test.getName(), Project.MSG_INFO ); + cmd.createArgument().setValue( "formatter=org.apache.tools.ant.taskdefs.optional.junit.SummaryJUnitResultFormatter" ); + } + + StringBuffer formatterArg = new StringBuffer( 128 ); + final FormatterElement[] feArray = mergeFormatters( test ); + for( int i = 0; i < feArray.length; i++ ) + { + FormatterElement fe = feArray[i]; + formatterArg.append( "formatter=" ); + formatterArg.append( fe.getClassname() ); + File outFile = getOutput( fe, test ); + if( outFile != null ) + { + formatterArg.append( "," ); + formatterArg.append( outFile ); + } + cmd.createArgument().setValue( formatterArg.toString() ); + formatterArg.setLength( 0 ); + } + + // Create a temporary file to pass the Ant properties to the forked test + File propsFile = new File( "junit" + ( new Random( System.currentTimeMillis() ) ).nextLong() + ".properties" ); + cmd.createArgument().setValue( "propsfile=" + propsFile.getAbsolutePath() ); + Hashtable p = project.getProperties(); + Properties props = new Properties(); + for( Enumeration enum = p.keys(); enum.hasMoreElements(); ) + { + Object key = enum.nextElement(); + props.put( key, p.get( key ) ); + } + try + { + FileOutputStream outstream = new FileOutputStream( propsFile ); + props.save( outstream, "Ant JUnitTask generated properties file" ); + outstream.close(); + } + catch( java.io.IOException e ) + { + throw new BuildException( "Error creating temporary properties file.", e, location ); + } + + Execute execute = new Execute( new LogStreamHandler( this, Project.MSG_INFO, Project.MSG_WARN ), watchdog ); + execute.setCommandline( cmd.getCommandline() ); + execute.setAntRun( project ); + if( dir != null ) + { + execute.setWorkingDirectory( dir ); + } + + log( "Executing: " + cmd.toString(), Project.MSG_VERBOSE ); + int retVal; + try + { + retVal = execute.execute(); + } + catch( IOException e ) + { + throw new BuildException( "Process fork failed.", e, location ); + } + finally + { + if( !propsFile.delete() ) + throw new BuildException( "Could not delete temporary properties file." ); + } + + return retVal; + } + + /** + * Execute inside VM. + * + * @param test Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + private int executeInVM( JUnitTest test ) + throws BuildException + { + test.setProperties( project.getProperties() ); + if( dir != null ) + { + log( "dir attribute ignored if running in the same VM", Project.MSG_WARN ); + } + + CommandlineJava.SysProperties sysProperties = commandline.getSystemProperties(); + if( sysProperties != null ) + { + sysProperties.setSystem(); + } + try + { + log( "Using System properties " + System.getProperties(), Project.MSG_VERBOSE ); + AntClassLoader cl = null; + Path classpath = commandline.getClasspath(); + if( classpath != null ) + { + log( "Using CLASSPATH " + classpath, Project.MSG_VERBOSE ); + + cl = new AntClassLoader( null, project, classpath, false ); + // make sure the test will be accepted as a TestCase + cl.addSystemPackageRoot( "junit" ); + // will cause trouble in JDK 1.1 if omitted + cl.addSystemPackageRoot( "org.apache.tools.ant" ); + } + runner = new JUnitTestRunner( test, test.getHaltonerror(), test.getFiltertrace(), test.getHaltonfailure(), cl ); + if( summary ) + { + log( "Running " + test.getName(), Project.MSG_INFO ); + + SummaryJUnitResultFormatter f = + new SummaryJUnitResultFormatter(); + f.setWithOutAndErr( "withoutanderr".equalsIgnoreCase( summaryValue ) ); + f.setOutput( getDefaultOutput() ); + runner.addFormatter( f ); + } + + final FormatterElement[] feArray = mergeFormatters( test ); + for( int i = 0; i < feArray.length; i++ ) + { + FormatterElement fe = feArray[i]; + File outFile = getOutput( fe, test ); + if( outFile != null ) + { + fe.setOutfile( outFile ); + } + else + { + fe.setOutput( getDefaultOutput() ); + } + runner.addFormatter( fe.createFormatter() ); + } + + runner.run(); + return runner.getRetCode(); + } + finally + { + if( sysProperties != null ) + { + sysProperties.restoreSystem(); + } + } + } + + private FormatterElement[] mergeFormatters( JUnitTest test ) + { + Vector feVector = ( Vector )formatters.clone(); + test.addFormattersTo( feVector ); + FormatterElement[] feArray = new FormatterElement[feVector.size()]; + feVector.copyInto( feArray ); + return feArray; + } + + /** + * Print summary enumeration values. + * + * @author RT + */ + public static class SummaryAttribute extends EnumeratedAttribute + { + public String[] getValues() + { + return new String[]{"true", "yes", "false", "no", + "on", "off", "withOutAndErr"}; + } + + public boolean asBoolean() + { + return "true".equals( value ) + || "on".equals( value ) + || "yes".equals( value ) + || "withOutAndErr".equals( value ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java new file mode 100644 index 000000000..93c2b2d01 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Vector; +import org.apache.tools.ant.Project; + +/** + *

              + * + * Run a single JUnit test.

              + * + * The JUnit test is actually run by {@link JUnitTestRunner}. So read the doc + * comments for that class :) + * + * @author Thomas Haas + * @author Stefan Bodewig , + * @author Stephane Bailliez + * @see JUnitTask + * @see JUnitTestRunner + */ +public class JUnitTest extends BaseTest +{ + + /** + * the name of the test case + */ + private String name = null; + + /** + * the name of the result file + */ + private String outfile = null; + + // Snapshot of the system properties + private Properties props = null; + private long runTime; + + // @todo this is duplicating TestResult information. Only the time is not + // part of the result. So we'd better derive a new class from TestResult + // and deal with it. (SB) + private long runs, failures, errors; + + public JUnitTest() { } + + public JUnitTest( String name ) + { + this.name = name; + } + + public JUnitTest( String name, boolean haltOnError, boolean haltOnFailure, boolean filtertrace ) + { + this.name = name; + this.haltOnError = haltOnError; + this.haltOnFail = haltOnFailure; + this.filtertrace = filtertrace; + } + + public void setCounts( long runs, long failures, long errors ) + { + this.runs = runs; + this.failures = failures; + this.errors = errors; + } + + /** + * Set the name of the test class. + * + * @param value The new Name value + */ + public void setName( String value ) + { + name = value; + } + + /** + * Set the name of the output file. + * + * @param value The new Outfile value + */ + public void setOutfile( String value ) + { + outfile = value; + } + + public void setProperties( Hashtable p ) + { + props = new Properties(); + for( Enumeration enum = p.keys(); enum.hasMoreElements(); ) + { + Object key = enum.nextElement(); + props.put( key, p.get( key ) ); + } + } + + public void setRunTime( long runTime ) + { + this.runTime = runTime; + } + + public FormatterElement[] getFormatters() + { + FormatterElement[] fes = new FormatterElement[formatters.size()]; + formatters.copyInto( fes ); + return fes; + } + + /** + * Get the name of the test class. + * + * @return The Name value + */ + public String getName() + { + return name; + } + + /** + * Get the name of the output file + * + * @return the name of the output file. + */ + public String getOutfile() + { + return outfile; + } + + public Properties getProperties() + { + return props; + } + + public long getRunTime() + { + return runTime; + } + + public long errorCount() + { + return errors; + } + + public long failureCount() + { + return failures; + } + + public long runCount() + { + return runs; + } + + public boolean shouldRun( Project p ) + { + if( ifProperty != null && p.getProperty( ifProperty ) == null ) + { + return false; + } + else if( unlessProperty != null && + p.getProperty( unlessProperty ) != null ) + { + return false; + } + + return true; + } + + /** + * Convenient method to add formatters to a vector + * + * @param v The feature to be added to the FormattersTo attribute + */ + void addFormattersTo( Vector v ) + { + final int count = formatters.size(); + for( int i = 0; i < count; i++ ) + { + v.addElement( formatters.elementAt( i ) ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java new file mode 100644 index 000000000..29170e402 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitTestRunner.java @@ -0,0 +1,637 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import java.util.Vector; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestListener; +import junit.framework.TestResult; +import junit.framework.TestSuite; +import org.apache.tools.ant.AntClassLoader; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.util.StringUtils; + +/** + * Simple Testrunner for JUnit that runs all tests of a testsuite.

              + * + * This TestRunner expects a name of a TestCase class as its argument. If this + * class provides a static suite() method it will be called and the resulting + * Test will be run. So, the signature should be

              
              + *     public static junit.framework.Test suite()
              + * 

              + * + * If no such method exists, all public methods starting with "test" and taking + * no argument will be run.

              + * + * Summary output is generated at the end. + * + * @author Stefan Bodewig + * @author Erik Hatcher + */ + +public class JUnitTestRunner implements TestListener +{ + + /** + * No problems with this test. + */ + public final static int SUCCESS = 0; + + /** + * Some tests failed. + */ + public final static int FAILURES = 1; + + /** + * An error occured. + */ + public final static int ERRORS = 2; + + /** + * Do we filter junit.*.* stack frames out of failure and error exceptions. + */ + private static boolean filtertrace = true; + + private final static String[] DEFAULT_TRACE_FILTERS = new String[]{ + "junit.framework.TestCase", + "junit.framework.TestResult", + "junit.framework.TestSuite", + "junit.framework.Assert.", // don't filter AssertionFailure + "junit.swingui.TestRunner", + "junit.awtui.TestRunner", + "junit.textui.TestRunner", + "java.lang.reflect.Method.invoke(", + "org.apache.tools.ant." + }; + + private static Vector fromCmdLine = new Vector(); + + /** + * Holds the registered formatters. + */ + private Vector formatters = new Vector(); + + /** + * Do we stop on errors. + */ + private boolean haltOnError = false; + + /** + * Do we stop on test failures. + */ + private boolean haltOnFailure = false; + + /** + * The corresponding testsuite. + */ + private Test suite = null; + + /** + * Returncode + */ + private int retCode = SUCCESS; + + /** + * Exception caught in constructor. + */ + private Exception exception; + + /** + * The TestSuite we are currently running. + */ + private JUnitTest junitTest; + + /** + * Collects TestResults. + */ + private TestResult res; + + /** + * output written during the test + */ + private PrintStream systemError; + + /** + * Error output during the test + */ + private PrintStream systemOut; + + /** + * Constructor for fork=true or when the user hasn't specified a classpath. + * + * @param test Description of Parameter + * @param haltOnError Description of Parameter + * @param filtertrace Description of Parameter + * @param haltOnFailure Description of Parameter + */ + public JUnitTestRunner( JUnitTest test, boolean haltOnError, boolean filtertrace, + boolean haltOnFailure ) + { + this( test, haltOnError, filtertrace, haltOnFailure, null ); + } + + /** + * Constructor to use when the user has specified a classpath. + * + * @param test Description of Parameter + * @param haltOnError Description of Parameter + * @param filtertrace Description of Parameter + * @param haltOnFailure Description of Parameter + * @param loader Description of Parameter + */ + public JUnitTestRunner( JUnitTest test, boolean haltOnError, boolean filtertrace, + boolean haltOnFailure, ClassLoader loader ) + { + //JUnitTestRunner.filtertrace = filtertrace; + this.filtertrace = filtertrace; + this.junitTest = test; + this.haltOnError = haltOnError; + this.haltOnFailure = haltOnFailure; + + try + { + Class testClass = null; + if( loader == null ) + { + testClass = Class.forName( test.getName() ); + } + else + { + testClass = loader.loadClass( test.getName() ); + AntClassLoader.initializeClass( testClass ); + } + + Method suiteMethod = null; + try + { + // check if there is a suite method + suiteMethod = testClass.getMethod( "suite", new Class[0] ); + } + catch( Exception e ) + { + // no appropriate suite method found. We don't report any + // error here since it might be perfectly normal. We don't + // know exactly what is the cause, but we're doing exactly + // the same as JUnit TestRunner do. We swallow the exceptions. + } + if( suiteMethod != null ) + { + // if there is a suite method available, then try + // to extract the suite from it. If there is an error + // here it will be caught below and reported. + suite = ( Test )suiteMethod.invoke( null, new Class[0] ); + } + else + { + // try to extract a test suite automatically + // this will generate warnings if the class is no suitable Test + suite = new TestSuite( testClass ); + } + + } + catch( Exception e ) + { + retCode = ERRORS; + exception = e; + } + } + + /** + * Returns a filtered stack trace. This is ripped out of + * junit.runner.BaseTestRunner. Scott M. Stirling. + * + * @param t Description of Parameter + * @return The FilteredTrace value + */ + public static String getFilteredTrace( Throwable t ) + { + String trace = StringUtils.getStackTrace( t ); + return JUnitTestRunner.filterStack( trace ); + } + + /** + * Filters stack frames from internal JUnit and Ant classes + * + * @param stack Description of Parameter + * @return Description of the Returned Value + */ + public static String filterStack( String stack ) + { + if( !filtertrace ) + { + return stack; + } + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter( sw ); + StringReader sr = new StringReader( stack ); + BufferedReader br = new BufferedReader( sr ); + + String line; + try + { + while( ( line = br.readLine() ) != null ) + { + if( !filterLine( line ) ) + { + pw.println( line ); + } + } + } + catch( Exception IOException ) + { + return stack;// return the stack unfiltered + } + return sw.toString(); + } + + /** + * Entry point for standalone (forked) mode. Parameters: testcaseclassname + * plus parameters in the format key=value, none of which is required. + * + * + * + * + * + * + * key + * + * + * + * description + * + * + * + * default value + * + * + * + * + * + * + * + * haltOnError + * + * + * + * halt test on errors? + * + * + * + * false + * + * + * + * + * + * + * + * haltOnFailure + * + * + * + * halt test on failures? + * + * + * + * false + * + * + * + * + * + * + * + * formatter + * + * + * + * A JUnitResultFormatter given as classname,filename. If filename is + * ommitted, System.out is assumed. + * + * + * + * none + * + * + * + * + * + * + * + * @param args The command line arguments + * @exception IOException Description of Exception + */ + public static void main( String[] args ) + throws IOException + { + boolean exitAtEnd = true; + boolean haltError = false; + boolean haltFail = false; + boolean stackfilter = true; + Properties props = new Properties(); + + if( args.length == 0 ) + { + System.err.println( "required argument TestClassName missing" ); + System.exit( ERRORS ); + } + + for( int i = 1; i < args.length; i++ ) + { + if( args[i].startsWith( "haltOnError=" ) ) + { + haltError = Project.toBoolean( args[i].substring( 12 ) ); + } + else if( args[i].startsWith( "haltOnFailure=" ) ) + { + haltFail = Project.toBoolean( args[i].substring( 14 ) ); + } + else if( args[i].startsWith( "filtertrace=" ) ) + { + stackfilter = Project.toBoolean( args[i].substring( 12 ) ); + } + else if( args[i].startsWith( "formatter=" ) ) + { + try + { + createAndStoreFormatter( args[i].substring( 10 ) ); + } + catch( BuildException be ) + { + System.err.println( be.getMessage() ); + System.exit( ERRORS ); + } + } + else if( args[i].startsWith( "propsfile=" ) ) + { + FileInputStream in = new FileInputStream( args[i].substring( 10 ) ); + props.load( in ); + in.close(); + } + } + + JUnitTest t = new JUnitTest( args[0] ); + + // Add/overlay system properties on the properties from the Ant project + Hashtable p = System.getProperties(); + for( Enumeration enum = p.keys(); enum.hasMoreElements(); ) + { + Object key = enum.nextElement(); + props.put( key, p.get( key ) ); + } + t.setProperties( props ); + + JUnitTestRunner runner = new JUnitTestRunner( t, haltError, stackfilter, haltFail ); + transferFormatters( runner ); + runner.run(); + System.exit( runner.getRetCode() ); + } + + /** + * Line format is: formatter=(, + * + * )? + * + * @param line Description of Parameter + * @exception BuildException Description of Exception + */ + private static void createAndStoreFormatter( String line ) + throws BuildException + { + FormatterElement fe = new FormatterElement(); + int pos = line.indexOf( ',' ); + if( pos == -1 ) + { + fe.setClassname( line ); + } + else + { + fe.setClassname( line.substring( 0, pos ) ); + fe.setOutfile( new File( line.substring( pos + 1 ) ) ); + } + fromCmdLine.addElement( fe.createFormatter() ); + } + + private static boolean filterLine( String line ) + { + for( int i = 0; i < DEFAULT_TRACE_FILTERS.length; i++ ) + { + if( line.indexOf( DEFAULT_TRACE_FILTERS[i] ) > 0 ) + { + return true; + } + } + return false; + } + + private static void transferFormatters( JUnitTestRunner runner ) + { + for( int i = 0; i < fromCmdLine.size(); i++ ) + { + runner.addFormatter( ( JUnitResultFormatter )fromCmdLine.elementAt( i ) ); + } + } + + /** + * Returns what System.exit() would return in the standalone version. + * + * @return 2 if errors occurred, 1 if tests failed else 0. + */ + public int getRetCode() + { + return retCode; + } + + /** + * Interface TestListener.

              + * + * An error occured while running the test. + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) + { + if( haltOnError ) + { + res.stop(); + } + } + + /** + * Interface TestListener for JUnit <= 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + if( haltOnFailure ) + { + res.stop(); + } + } + + /** + * Interface TestListener for JUnit > 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + public void addFormatter( JUnitResultFormatter f ) + { + formatters.addElement( f ); + } + + /** + * Interface TestListener.

              + * + * A Test is finished. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) { } + + public void run() + { + res = new TestResult(); + res.addListener( this ); + for( int i = 0; i < formatters.size(); i++ ) + { + res.addListener( ( TestListener )formatters.elementAt( i ) ); + } + + long start = System.currentTimeMillis(); + + fireStartTestSuite(); + if( exception != null ) + {// had an exception in the constructor + for( int i = 0; i < formatters.size(); i++ ) + { + ( ( TestListener )formatters.elementAt( i ) ).addError( null, + exception ); + } + junitTest.setCounts( 1, 0, 1 ); + junitTest.setRunTime( 0 ); + } + else + { + + ByteArrayOutputStream errStrm = new ByteArrayOutputStream(); + systemError = new PrintStream( errStrm ); + + ByteArrayOutputStream outStrm = new ByteArrayOutputStream(); + systemOut = new PrintStream( outStrm ); + + try + { + suite.run( res ); + } + finally + { + systemError.close(); + systemError = null; + systemOut.close(); + systemOut = null; + sendOutAndErr( new String( outStrm.toByteArray() ), + new String( errStrm.toByteArray() ) ); + + junitTest.setCounts( res.runCount(), res.failureCount(), + res.errorCount() ); + junitTest.setRunTime( System.currentTimeMillis() - start ); + } + } + fireEndTestSuite(); + + if( retCode != SUCCESS || res.errorCount() != 0 ) + { + retCode = ERRORS; + } + else if( res.failureCount() != 0 ) + { + retCode = FAILURES; + } + } + + /** + * Interface TestListener.

              + * + * A new Test is started. + * + * @param t Description of Parameter + */ + public void startTest( Test t ) { } + + protected void handleErrorOutput( String line ) + { + if( systemError != null ) + { + systemError.println( line ); + } + } + + protected void handleOutput( String line ) + { + if( systemOut != null ) + { + systemOut.println( line ); + } + } + + private void fireEndTestSuite() + { + for( int i = 0; i < formatters.size(); i++ ) + { + ( ( JUnitResultFormatter )formatters.elementAt( i ) ).endTestSuite( junitTest ); + } + } + + private void fireStartTestSuite() + { + for( int i = 0; i < formatters.size(); i++ ) + { + ( ( JUnitResultFormatter )formatters.elementAt( i ) ).startTestSuite( junitTest ); + } + } + + private void sendOutAndErr( String out, String err ) + { + for( int i = 0; i < formatters.size(); i++ ) + { + JUnitResultFormatter formatter = + ( ( JUnitResultFormatter )formatters.elementAt( i ) ); + + formatter.setSystemOutput( out ); + formatter.setSystemError( err ); + } + } + +}// JUnitTestRunner diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java new file mode 100644 index 000000000..082af4960 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/JUnitVersionHelper.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.lang.reflect.Method; +import junit.framework.Test; +import junit.framework.TestCase; + +/** + * Work around for some changes to the public JUnit API between different JUnit + * releases. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class JUnitVersionHelper +{ + + private static Method testCaseName = null; + static + { + try + { + testCaseName = TestCase.class.getMethod( "getName", new Class[0] ); + } + catch( NoSuchMethodException e ) + { + // pre JUnit 3.7 + try + { + testCaseName = TestCase.class.getMethod( "name", new Class[0] ); + } + catch( NoSuchMethodException e2 ) + {} + } + } + + /** + * JUnit 3.7 introduces TestCase.getName() and subsequent versions of JUnit + * remove the old name() method. This method provides access to the name of + * a TestCase via reflection that is supposed to work with version before + * and after JUnit 3.7. + * + * @param t Description of Parameter + * @return The TestCaseName value + */ + public static String getTestCaseName( Test t ) + { + if( t instanceof TestCase && testCaseName != null ) + { + try + { + return ( String )testCaseName.invoke( t, new Object[0] ); + } + catch( Throwable e ) + {} + } + return "unknown"; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java new file mode 100644 index 000000000..239da0895 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/PlainJUnitResultFormatter.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.text.NumberFormat; +import java.util.Hashtable; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import org.apache.tools.ant.BuildException; + +/** + * Prints plain text output of the test to a specified Writer. + * + * @author Stefan Bodewig + */ + +public class PlainJUnitResultFormatter implements JUnitResultFormatter +{ + + /** + * Formatter for timings. + */ + private NumberFormat nf = NumberFormat.getInstance(); + /** + * Timing helper. + */ + private Hashtable testStarts = new Hashtable(); + /** + * Suppress endTest if testcase failed. + */ + private Hashtable failed = new Hashtable(); + + private String systemOutput = null; + private String systemError = null; + /** + * Helper to store intermediate output. + */ + private StringWriter inner; + /** + * Where to write the log to. + */ + private OutputStream out; + /** + * Convenience layer on top of {@link #inner inner}. + */ + private PrintWriter wri; + + public PlainJUnitResultFormatter() + { + inner = new StringWriter(); + wri = new PrintWriter( inner ); + } + + public void setOutput( OutputStream out ) + { + this.out = out; + } + + public void setSystemError( String err ) + { + systemError = err; + } + + public void setSystemOutput( String out ) + { + systemOutput = out; + } + + /** + * Interface TestListener.

              + * + * An error occured while running the test. + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) + { + formatError( "\tCaused an ERROR", test, t ); + } + + /** + * Interface TestListener for JUnit <= 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + formatError( "\tFAILED", test, t ); + } + + /** + * Interface TestListener for JUnit > 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * Interface TestListener.

              + * + * A Test is finished. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) + { + synchronized( wri ) + { + wri.print( "Testcase: " + + JUnitVersionHelper.getTestCaseName( test ) ); + if( Boolean.TRUE.equals( failed.get( test ) ) ) + { + return; + } + Long l = ( Long )testStarts.get( test ); + wri.println( " took " + + nf.format( ( System.currentTimeMillis() - l.longValue() ) + / 1000.0 ) + + " sec" ); + } + } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + String newLine = System.getProperty( "line.separator" ); + StringBuffer sb = new StringBuffer( "Testsuite: " ); + sb.append( suite.getName() ); + sb.append( newLine ); + sb.append( "Tests run: " ); + sb.append( suite.runCount() ); + sb.append( ", Failures: " ); + sb.append( suite.failureCount() ); + sb.append( ", Errors: " ); + sb.append( suite.errorCount() ); + sb.append( ", Time elapsed: " ); + sb.append( nf.format( suite.getRunTime() / 1000.0 ) ); + sb.append( " sec" ); + sb.append( newLine ); + + // append the err and output streams to the log + if( systemOutput != null && systemOutput.length() > 0 ) + { + sb.append( "------------- Standard Output ---------------" ) + .append( newLine ) + .append( systemOutput ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + if( systemError != null && systemError.length() > 0 ) + { + sb.append( "------------- Standard Error -----------------" ) + .append( newLine ) + .append( systemError ) + .append( "------------- ---------------- ---------------" ) + .append( newLine ); + } + + sb.append( newLine ); + + if( out != null ) + { + try + { + out.write( sb.toString().getBytes() ); + wri.close(); + out.write( inner.toString().getBytes() ); + out.flush(); + } + catch( IOException ioex ) + { + throw new BuildException( "Unable to write output", ioex ); + } + finally + { + if( out != System.out && out != System.err ) + { + try + { + out.close(); + } + catch( IOException e ) + {} + } + } + } + } + + /** + * Interface TestListener.

              + * + * A new Test is started. + * + * @param t Description of Parameter + */ + public void startTest( Test t ) + { + testStarts.put( t, new Long( System.currentTimeMillis() ) ); + failed.put( t, Boolean.FALSE ); + } + + /** + * Empty. + * + * @param suite Description of Parameter + */ + public void startTestSuite( JUnitTest suite ) { } + + private void formatError( String type, Test test, Throwable t ) + { + synchronized( wri ) + { + if( test != null ) + { + endTest( test ); + failed.put( test, Boolean.TRUE ); + } + + wri.println( type ); + wri.println( t.getMessage() ); + String strace = JUnitTestRunner.getFilteredTrace( t ); + wri.print( strace ); + wri.println( "" ); + } + } + +}// PlainJUnitResultFormatter diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java new file mode 100644 index 000000000..2932da2d0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/SummaryJUnitResultFormatter.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.IOException; +import java.io.OutputStream; +import java.text.NumberFormat; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import org.apache.tools.ant.BuildException; + +/** + * Prints short summary output of the test to Ant's logging system. + * + * @author Stefan Bodewig + */ + +public class SummaryJUnitResultFormatter implements JUnitResultFormatter +{ + + /** + * Formatter for timings. + */ + private NumberFormat nf = NumberFormat.getInstance(); + + private boolean withOutAndErr = false; + private String systemOutput = null; + private String systemError = null; + /** + * OutputStream to write to. + */ + private OutputStream out; + + /** + * Empty + */ + public SummaryJUnitResultFormatter() { } + + public void setOutput( OutputStream out ) + { + this.out = out; + } + + public void setSystemError( String err ) + { + systemError = err; + } + + public void setSystemOutput( String out ) + { + systemOutput = out; + } + + /** + * Should the output to System.out and System.err be written to the summary. + * + * @param value The new WithOutAndErr value + */ + public void setWithOutAndErr( boolean value ) + { + withOutAndErr = value; + } + + /** + * Empty + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) { } + + /** + * Empty + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) { } + + /** + * Interface TestListener for JUnit > 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * Empty + * + * @param test Description of Parameter + */ + public void endTest( Test test ) { } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + String newLine = System.getProperty( "line.separator" ); + StringBuffer sb = new StringBuffer( "Tests run: " ); + sb.append( suite.runCount() ); + sb.append( ", Failures: " ); + sb.append( suite.failureCount() ); + sb.append( ", Errors: " ); + sb.append( suite.errorCount() ); + sb.append( ", Time elapsed: " ); + sb.append( nf.format( suite.getRunTime() / 1000.0 ) ); + sb.append( " sec" ); + sb.append( newLine ); + + if( withOutAndErr ) + { + if( systemOutput != null && systemOutput.length() > 0 ) + { + sb.append( "Output:" ).append( newLine ).append( systemOutput ) + .append( newLine ); + } + + if( systemError != null && systemError.length() > 0 ) + { + sb.append( "Error: " ).append( newLine ).append( systemError ) + .append( newLine ); + } + } + + try + { + out.write( sb.toString().getBytes() ); + out.flush(); + } + catch( IOException ioex ) + { + throw new BuildException( "Unable to write summary output", ioex ); + } + finally + { + if( out != System.out && out != System.err ) + { + try + { + out.close(); + } + catch( IOException e ) + {} + } + } + } + + /** + * Empty + * + * @param t Description of Parameter + */ + public void startTest( Test t ) { } + + /** + * Empty + * + * @param suite Description of Parameter + */ + public void startTestSuite( JUnitTest suite ) { } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java new file mode 100644 index 000000000..53a2d1bf5 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLConstants.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; + +/** + *

              + * + * Interface groups XML constants. Interface that groups all constants used + * throughout the XML documents that are generated by the + * XMLJUnitResultFormatter As of now the DTD is:

              + * <-----------------
              + *
              + * @author Stephane Bailliez
              + * @see XMLJUnitResultFormatter
              + * @see XMLResultAggregator
              + * @todo describe DTDs ----------------------> 
              + */ +public interface XMLConstants +{ + /** + * the testsuites element for the aggregate document + */ + String TESTSUITES = "testsuites"; + + /** + * the testsuite element + */ + String TESTSUITE = "testsuite"; + + /** + * the testcase element + */ + String TESTCASE = "testcase"; + + /** + * the error element + */ + String ERROR = "error"; + + /** + * the failure element + */ + String FAILURE = "failure"; + + /** + * the system-err element + */ + String SYSTEM_ERR = "system-err"; + + /** + * the system-out element + */ + String SYSTEM_OUT = "system-out"; + + /** + * package attribute for the aggregate document + */ + String ATTR_PACKAGE = "package"; + + /** + * name attribute for property, testcase and testsuite elements + */ + String ATTR_NAME = "name"; + + /** + * time attribute for testcase and testsuite elements + */ + String ATTR_TIME = "time"; + + /** + * errors attribute for testsuite elements + */ + String ATTR_ERRORS = "errors"; + + /** + * failures attribute for testsuite elements + */ + String ATTR_FAILURES = "failures"; + + /** + * tests attribute for testsuite elements + */ + String ATTR_TESTS = "tests"; + + /** + * type attribute for failure and error elements + */ + String ATTR_TYPE = "type"; + + /** + * message attribute for failure elements + */ + String ATTR_MESSAGE = "message"; + + /** + * the properties element + */ + String PROPERTIES = "properties"; + + /** + * the property element + */ + String PROPERTY = "property"; + + /** + * value attribute for property elements + */ + String ATTR_VALUE = "value"; + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java new file mode 100644 index 000000000..97501dc2c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Properties; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import junit.framework.AssertionFailedError; +import junit.framework.Test; +import junit.framework.TestCase; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.DOMElementWriter; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Text; + +/** + * Prints XML output of the test to a specified Writer. + * + * @author Stefan Bodewig + * @author Erik Hatcher + * @see FormatterElement + */ + +public class XMLJUnitResultFormatter implements JUnitResultFormatter, XMLConstants +{ + /** + * Element for the current test. + */ + private Hashtable testElements = new Hashtable(); + /** + * Timing helper. + */ + private Hashtable testStarts = new Hashtable(); + + /** + * The XML document. + */ + private Document doc; + /** + * Where to write the log to. + */ + private OutputStream out; + /** + * The wrapper for the whole testsuite. + */ + private Element rootElement; + + public XMLJUnitResultFormatter() { } + + private static DocumentBuilder getDocumentBuilder() + { + try + { + return DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } + catch( Exception exc ) + { + throw new ExceptionInInitializerError( exc ); + } + } + + public void setOutput( OutputStream out ) + { + this.out = out; + } + + public void setSystemError( String out ) + { + formatOutput( SYSTEM_ERR, out ); + } + + public void setSystemOutput( String out ) + { + formatOutput( SYSTEM_OUT, out ); + } + + /** + * Interface TestListener.

              + * + * An error occured while running the test. + * + * @param test The feature to be added to the Error attribute + * @param t The feature to be added to the Error attribute + */ + public void addError( Test test, Throwable t ) + { + formatError( ERROR, test, t ); + } + + /** + * Interface TestListener for JUnit <= 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, Throwable t ) + { + formatError( FAILURE, test, t ); + } + + /** + * Interface TestListener for JUnit > 3.4.

              + * + * A Test failed. + * + * @param test The feature to be added to the Failure attribute + * @param t The feature to be added to the Failure attribute + */ + public void addFailure( Test test, AssertionFailedError t ) + { + addFailure( test, ( Throwable )t ); + } + + /** + * Interface TestListener.

              + * + * A Test is finished. + * + * @param test Description of Parameter + */ + public void endTest( Test test ) + { + Element currentTest = ( Element )testElements.get( test ); + Long l = ( Long )testStarts.get( test ); + currentTest.setAttribute( ATTR_TIME, + "" + ( ( System.currentTimeMillis() - l.longValue() ) + / 1000.0 ) ); + } + + /** + * The whole testsuite ended. + * + * @param suite Description of Parameter + * @exception BuildException Description of Exception + */ + public void endTestSuite( JUnitTest suite ) + throws BuildException + { + rootElement.setAttribute( ATTR_TESTS, "" + suite.runCount() ); + rootElement.setAttribute( ATTR_FAILURES, "" + suite.failureCount() ); + rootElement.setAttribute( ATTR_ERRORS, "" + suite.errorCount() ); + rootElement.setAttribute( ATTR_TIME, "" + ( suite.getRunTime() / 1000.0 ) ); + if( out != null ) + { + Writer wri = null; + try + { + wri = new OutputStreamWriter( out, "UTF8" ); + wri.write( "\n" ); + ( new DOMElementWriter() ).write( rootElement, wri, 0, " " ); + wri.flush(); + } + catch( IOException exc ) + { + throw new BuildException( "Unable to write log file", exc ); + } + finally + { + if( out != System.out && out != System.err ) + { + if( wri != null ) + { + try + { + wri.close(); + } + catch( IOException e ) + {} + } + } + } + } + } + + /** + * Interface TestListener.

              + * + * A new Test is started. + * + * @param t Description of Parameter + */ + public void startTest( Test t ) + { + testStarts.put( t, new Long( System.currentTimeMillis() ) ); + + Element currentTest = doc.createElement( TESTCASE ); + currentTest.setAttribute( ATTR_NAME, + JUnitVersionHelper.getTestCaseName( t ) ); + rootElement.appendChild( currentTest ); + testElements.put( t, currentTest ); + } + + /** + * The whole testsuite started. + * + * @param suite Description of Parameter + */ + public void startTestSuite( JUnitTest suite ) + { + doc = getDocumentBuilder().newDocument(); + rootElement = doc.createElement( TESTSUITE ); + rootElement.setAttribute( ATTR_NAME, suite.getName() ); + + // Output properties + Element propsElement = doc.createElement( PROPERTIES ); + rootElement.appendChild( propsElement ); + Properties props = suite.getProperties(); + if( props != null ) + { + Enumeration e = props.propertyNames(); + while( e.hasMoreElements() ) + { + String name = ( String )e.nextElement(); + Element propElement = doc.createElement( PROPERTY ); + propElement.setAttribute( ATTR_NAME, name ); + propElement.setAttribute( ATTR_VALUE, props.getProperty( name ) ); + propsElement.appendChild( propElement ); + } + } + } + + private void formatError( String type, Test test, Throwable t ) + { + if( test != null ) + { + endTest( test ); + } + + Element nested = doc.createElement( type ); + Element currentTest = null; + if( test != null ) + { + currentTest = ( Element )testElements.get( test ); + } + else + { + currentTest = rootElement; + } + + currentTest.appendChild( nested ); + + String message = t.getMessage(); + if( message != null && message.length() > 0 ) + { + nested.setAttribute( ATTR_MESSAGE, t.getMessage() ); + } + nested.setAttribute( ATTR_TYPE, t.getClass().getName() ); + + String strace = JUnitTestRunner.getFilteredTrace( t ); + Text trace = doc.createTextNode( strace ); + nested.appendChild( trace ); + } + + private void formatOutput( String type, String output ) + { + Element nested = doc.createElement( type ); + rootElement.appendChild( nested ); + Text content = doc.createTextNode( output ); + nested.appendChild( content ); + } + +}// XMLJUnitResultFormatter diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java new file mode 100644 index 000000000..cc72e239f --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XMLResultAggregator.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Enumeration; +import java.util.Vector; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.util.DOMElementWriter; +import org.apache.tools.ant.util.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + + +/** + *

              + * + * This is an helper class that will aggregate all testsuites under a specific + * directory and create a new single document. It is not particulary clean but + * should be helpful while I am thinking about another technique.

              + * + * The main problem is due to the fact that a JVM can be forked for a testcase + * thus making it impossible to aggregate all testcases since the listener is + * (obviously) in the forked JVM. A solution could be to write a TestListener + * that will receive events from the TestRunner via sockets. This is IMHO the + * simplest way to do it to avoid this file hacking thing. + * + * @author Stephane Bailliez + */ +public class XMLResultAggregator extends Task implements XMLConstants +{ + + /** + * the default directory: . . It is resolved from the project + * directory + */ + public final static String DEFAULT_DIR = "."; + + /** + * the default file name: TESTS-TestSuites.xml + */ + public final static String DEFAULT_FILENAME = "TESTS-TestSuites.xml"; + + /** + * the list of all filesets, that should contains the xml to aggregate + */ + protected Vector filesets = new Vector(); + + protected Vector transformers = new Vector(); + + /** + * the directory to write the file to + */ + protected File toDir; + + /** + * the name of the result file + */ + protected String toFile; + + /** + * Create a new document builder. Will issue an + * ExceptionInitializerError if something is going wrong. It is fatal + * anyway. + * + * @return a new document builder to create a DOM + * @todo factorize this somewhere else. It is duplicated code. + */ + private static DocumentBuilder getDocumentBuilder() + { + try + { + return DocumentBuilderFactory.newInstance().newDocumentBuilder(); + } + catch( Exception exc ) + { + throw new ExceptionInInitializerError( exc ); + } + } + + /** + * Set the destination directory where the results should be written. If not + * set if will use {@link #DEFAULT_DIR}. When given a relative directory it + * will resolve it from the project directory. + * + * @param value the directory where to write the results, absolute or + * relative. + */ + public void setTodir( File value ) + { + toDir = value; + } + + /** + * Set the name of the file aggregating the results. It must be relative + * from the todir attribute. If not set it will use {@link + * #DEFAULT_FILENAME} + * + * @param value the name of the file. + * @see #setTodir(File) + */ + public void setTofile( String value ) + { + toFile = value; + } + + /** + * Add a new fileset containing the xml results to aggregate + * + * @param fs the new fileset of xml results. + */ + public void addFileSet( FileSet fs ) + { + filesets.addElement( fs ); + } + + + public AggregateTransformer createReport() + { + AggregateTransformer transformer = new AggregateTransformer( this ); + transformers.addElement( transformer ); + return transformer; + } + + /** + * Aggregate all testsuites into a single document and write it to the + * specified directory and file. + * + * @throws BuildException thrown if there is a serious error while writing + * the document. + */ + public void execute() + throws BuildException + { + Element rootElement = createDocument(); + File destFile = getDestinationFile(); + // write the document + try + { + writeDOMTree( rootElement.getOwnerDocument(), destFile ); + } + catch( IOException e ) + { + throw new BuildException( "Unable to write test aggregate to '" + destFile + "'", e ); + } + // apply transformation + Enumeration enum = transformers.elements(); + while( enum.hasMoreElements() ) + { + AggregateTransformer transformer = + ( AggregateTransformer )enum.nextElement(); + transformer.setXmlDocument( rootElement.getOwnerDocument() ); + transformer.transform(); + } + } + + /** + * Get the full destination file where to write the result. It is made of + * the todir and tofile attributes. + * + * @return the destination file where should be written the result file. + */ + protected File getDestinationFile() + { + if( toFile == null ) + { + toFile = DEFAULT_FILENAME; + } + if( toDir == null ) + { + toDir = project.resolveFile( DEFAULT_DIR ); + } + return new File( toDir, toFile ); + } + + /** + * Get all .xml files in the fileset. + * + * @return all files in the fileset that end with a '.xml'. + */ + protected File[] getFiles() + { + Vector v = new Vector(); + final int size = filesets.size(); + for( int i = 0; i < size; i++ ) + { + FileSet fs = ( FileSet )filesets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + ds.scan(); + String[] f = ds.getIncludedFiles(); + for( int j = 0; j < f.length; j++ ) + { + String pathname = f[j]; + if( pathname.endsWith( ".xml" ) ) + { + File file = new File( ds.getBasedir(), pathname ); + file = project.resolveFile( file.getPath() ); + v.addElement( file ); + } + } + } + + File[] files = new File[v.size()]; + v.copyInto( files ); + return files; + } + + /** + *

              + * + * Add a new testsuite node to the document. The main difference is that it + * split the previous fully qualified name into a package and a name.

              + * + * For example: org.apache.Whatever will be split into + * org.apache and Whatever . + * + * @param root the root element to which the testsuite node should + * be appended. + * @param testsuite the element to append to the given root. It will + * slightly modify the original node to change the name attribute and + * add a package one. + */ + protected void addTestSuite( Element root, Element testsuite ) + { + String fullclassname = testsuite.getAttribute( ATTR_NAME ); + int pos = fullclassname.lastIndexOf( '.' ); + + // a missing . might imply no package at all. Don't get fooled. + String pkgName = ( pos == -1 ) ? "" : fullclassname.substring( 0, pos ); + String classname = ( pos == -1 ) ? fullclassname : fullclassname.substring( pos + 1 ); + Element copy = ( Element )DOMUtil.importNode( root, testsuite ); + + // modify the name attribute and set the package + copy.setAttribute( ATTR_NAME, classname ); + copy.setAttribute( ATTR_PACKAGE, pkgName ); + } + + /** + *

              + * + * Create a DOM tree. Has 'testsuites' as firstchild and aggregates all + * testsuite results that exists in the base directory. + * + * @return the root element of DOM tree that aggregates all testsuites. + */ + protected Element createDocument() + { + // create the dom tree + DocumentBuilder builder = getDocumentBuilder(); + Document doc = builder.newDocument(); + Element rootElement = doc.createElement( TESTSUITES ); + doc.appendChild( rootElement ); + + // get all files and add them to the document + File[] files = getFiles(); + for( int i = 0; i < files.length; i++ ) + { + try + { + log( "Parsing file: '" + files[i] + "'", Project.MSG_VERBOSE ); + //XXX there seems to be a bug in xerces 1.3.0 that doesn't like file object + // will investigate later. It does not use the given directory but + // the vm dir instead ? Works fine with crimson. + Document testsuiteDoc = builder.parse( "file:///" + files[i].getAbsolutePath() ); + Element elem = testsuiteDoc.getDocumentElement(); + // make sure that this is REALLY a testsuite. + if( TESTSUITE.equals( elem.getNodeName() ) ) + { + addTestSuite( rootElement, elem ); + } + else + { + // issue a warning. + log( "the file " + files[i] + " is not a valid testsuite XML document", Project.MSG_WARN ); + } + } + catch( SAXException e ) + { + // a testcase might have failed and write a zero-length document, + // It has already failed, but hey.... mm. just put a warning + log( "The file " + files[i] + " is not a valid XML document. It is possibly corrupted.", Project.MSG_WARN ); + log( StringUtils.getStackTrace( e ), Project.MSG_DEBUG ); + } + catch( IOException e ) + { + log( "Error while accessing file " + files[i] + ": " + e.getMessage(), Project.MSG_ERR ); + } + } + return rootElement; + } + + //----- from now, the methods are all related to DOM tree manipulation + + /** + * Write the DOM tree to a file. + * + * @param doc the XML document to dump to disk. + * @param file the filename to write the document to. Should obviouslly be a + * .xml file. + * @throws IOException thrown if there is an error while writing the + * content. + */ + protected void writeDOMTree( Document doc, File file ) + throws IOException + { + OutputStream out = new FileOutputStream( file ); + PrintWriter wri = new PrintWriter( new OutputStreamWriter( out, "UTF8" ) ); + wri.write( "\n" ); + ( new DOMElementWriter() ).write( doc.getDocumentElement(), wri, 0, " " ); + wri.flush(); + wri.close(); + // writers do not throw exceptions, so check for them. + if( wri.checkError() ) + { + throw new IOException( "Error while writing DOM content" ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java new file mode 100644 index 000000000..5f426d55e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan1Executor.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.OutputStream; +import org.apache.xalan.xslt.XSLTInputSource; +import org.apache.xalan.xslt.XSLTProcessor; +import org.apache.xalan.xslt.XSLTProcessorFactory; +import org.apache.xalan.xslt.XSLTResultTarget; + +/** + * Xalan 1 executor. It will need a lot of things in the classpath: xerces for + * the serialization, xalan and bsf for the extension. + * + * @author RT + * @todo do everything via reflection to avoid compile problems ? + */ +public class Xalan1Executor extends XalanExecutor +{ + void execute() + throws Exception + { + XSLTProcessor processor = XSLTProcessorFactory.getProcessor(); + // need to quote otherwise it breaks because of "extra illegal tokens" + processor.setStylesheetParam( "output.dir", "'" + caller.toDir.getAbsolutePath() + "'" ); + XSLTInputSource xml_src = new XSLTInputSource( caller.document ); + String system_id = caller.getStylesheetSystemId(); + XSLTInputSource xsl_src = new XSLTInputSource( system_id ); + OutputStream os = getOutputStream(); + XSLTResultTarget target = new XSLTResultTarget( os ); + processor.process( xml_src, xsl_src, target ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java new file mode 100644 index 000000000..617424290 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/Xalan2Executor.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.OutputStream; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +/** + * Xalan executor via JAXP. Nothing special must exists in the classpath besides + * of course, a parser, jaxp and xalan. + * + * @author RT + */ +public class Xalan2Executor extends XalanExecutor +{ + void execute() + throws Exception + { + TransformerFactory tfactory = TransformerFactory.newInstance(); + String system_id = caller.getStylesheetSystemId(); + Source xsl_src = new StreamSource( system_id ); + Transformer tformer = tfactory.newTransformer( xsl_src ); + Source xml_src = new DOMSource( caller.document ); + OutputStream os = getOutputStream(); + tformer.setParameter( "output.dir", caller.toDir.getAbsolutePath() ); + Result result = new StreamResult( os ); + tformer.transform( xml_src, result ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java new file mode 100644 index 000000000..0b95c3426 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/junit/XalanExecutor.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.junit; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.Field; +import org.apache.tools.ant.BuildException; + +/** + * Command class that encapsulate specific behavior for each Xalan version. The + * right executor will be instantiated at runtime via class lookup. For + * instance, it will check first for Xalan2, then for Xalan1. + * + * @author RT + */ +abstract class XalanExecutor +{ + /** + * the transformer caller + */ + protected AggregateTransformer caller; + + /** + * Create a valid Xalan executor. It checks first if Xalan2 is present, if + * not it checks for xalan1. If none is available, it fails. + * + * @param caller object containing the transformation information. + * @return Description of the Returned Value + * @throws BuildException thrown if it could not find a valid xalan + * executor. + */ + static XalanExecutor newInstance( AggregateTransformer caller ) + throws BuildException + { + Class procVersion = null; + XalanExecutor executor = null; + try + { + procVersion = Class.forName( "org.apache.xalan.processor.XSLProcessorVersion" ); + executor = new Xalan2Executor(); + } + catch( Exception xalan2missing ) + { + try + { + procVersion = Class.forName( "org.apache.xalan.xslt.XSLProcessorVersion" ); + executor = ( XalanExecutor )Class.forName( + "org.apache.tools.ant.taskdefs.optional.junit.Xalan1Executor" ).newInstance(); + } + catch( Exception xalan1missing ) + { + throw new BuildException( "Could not find xalan2 nor xalan1 in the classpath. Check http://xml.apache.org/xalan-j" ); + } + } + String version = getXalanVersion( procVersion ); + caller.task.log( "Using Xalan version: " + version ); + executor.setCaller( caller ); + return executor; + } + + /** + * pretty useful data (Xalan version information) to display. + * + * @param procVersion Description of Parameter + * @return The XalanVersion value + */ + private static String getXalanVersion( Class procVersion ) + { + try + { + Field f = procVersion.getField( "S_VERSION" ); + return f.get( null ).toString(); + } + catch( Exception e ) + { + return "?"; + } + } + + /** + * get the appropriate stream based on the format (frames/noframes) + * + * @return The OutputStream value + * @exception IOException Description of Exception + */ + protected OutputStream getOutputStream() + throws IOException + { + if( caller.FRAMES.equals( caller.format ) ) + { + // dummy output for the framed report + // it's all done by extension... + return new ByteArrayOutputStream(); + } + else + { + return new FileOutputStream( new File( caller.toDir, "junit-noframes.html" ) ); + } + } + + /** + * override to perform transformation + * + * @exception Exception Description of Exception + */ + abstract void execute() + throws Exception; + + /** + * set the caller for this object. + * + * @param caller The new Caller value + */ + private final void setCaller( AggregateTransformer caller ) + { + this.caller = caller; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java new file mode 100644 index 000000000..500cddfd7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/AbstractMetamataTask.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.metamata; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Random; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.taskdefs.Execute; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.types.Commandline; +import org.apache.tools.ant.types.CommandlineJava; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.Path; + +/** + * Somewhat abstract framework to be used for other metama 2.0 tasks. This + * should include, audit, metrics, cover and mparse. For more information, visit + * the website at www.metamata.com + * + * @author Stephane Bailliez + */ +public abstract class AbstractMetamataTask extends Task +{ + + //--------------------------- ATTRIBUTES ----------------------------------- + + /** + * The user classpath to be provided. It matches the -classpath of the + * command line. The classpath must includes both the .class and + * the .java files for accurate audit. + */ + protected Path classPath = null; + + /** + * the path to the source file + */ + protected Path sourcePath = null; + + /** + * Metamata home directory. It will be passed as a metamata.home + * property and should normally matches the environment property + * META_HOME set by the Metamata installer. + */ + protected File metamataHome = null; + + /** + * the command line used to run MAudit + */ + protected CommandlineJava cmdl = new CommandlineJava(); + + /** + * the set of files to be audited + */ + protected Vector fileSets = new Vector(); + + /** + * the options file where are stored the command line options + */ + protected File optionsFile = null; + + // this is used to keep track of which files were included. It will + // be set when calling scanFileSets(); + protected Hashtable includedFiles = null; + + public AbstractMetamataTask() { } + + /** + * initialize the task with the classname of the task to run + * + * @param className Description of Parameter + */ + protected AbstractMetamataTask( String className ) + { + cmdl.setVm( "java" ); + cmdl.setClassname( className ); + } + + /** + * convenient method for JDK 1.1. Will copy all elements from src to dest + * + * @param dest The feature to be added to the AllVector attribute + * @param files The feature to be added to the AllVector attribute + */ + protected final static void addAllVector( Vector dest, Enumeration files ) + { + while( files.hasMoreElements() ) + { + dest.addElement( files.nextElement() ); + } + } + + protected final static File createTmpFile() + { + // must be compatible with JDK 1.1 !!!! + final long rand = ( new Random( System.currentTimeMillis() ) ).nextLong(); + File file = new File( "metamata" + rand + ".tmp" ); + return file; + } + + /** + * -mx or -Xmx depending on VM version + * + * @param max The new Maxmemory value + */ + public void setMaxmemory( String max ) + { + if( Project.getJavaVersion().startsWith( "1.1" ) ) + { + createJvmarg().setValue( "-mx" + max ); + } + else + { + createJvmarg().setValue( "-Xmx" + max ); + } + } + + /** + * the metamata.home property to run all tasks. + * + * @param metamataHome The new Metamatahome value + */ + public void setMetamatahome( final File metamataHome ) + { + this.metamataHome = metamataHome; + } + + + /** + * The java files or directory to be audited + * + * @param fs The feature to be added to the FileSet attribute + */ + public void addFileSet( FileSet fs ) + { + fileSets.addElement( fs ); + } + + /** + * user classpath + * + * @return Description of the Returned Value + */ + public Path createClasspath() + { + if( classPath == null ) + { + classPath = new Path( project ); + } + return classPath; + } + + /** + * Creates a nested jvmarg element. + * + * @return Description of the Returned Value + */ + public Commandline.Argument createJvmarg() + { + return cmdl.createVmArgument(); + } + + /** + * create the source path for this task + * + * @return Description of the Returned Value + */ + public Path createSourcepath() + { + if( sourcePath == null ) + { + sourcePath = new Path( project ); + } + return sourcePath; + } + + /** + * execute the command line + * + * @exception BuildException Description of Exception + */ + public void execute() + throws BuildException + { + try + { + setUp(); + ExecuteStreamHandler handler = createStreamHandler(); + execute0( handler ); + } + finally + { + cleanUp(); + } + } + + //--------------------- PRIVATE/PROTECTED METHODS -------------------------- + + /** + * check the options and build the command line + * + * @exception BuildException Description of Exception + */ + protected void setUp() + throws BuildException + { + checkOptions(); + + // set the classpath as the jar file + File jar = getMetamataJar( metamataHome ); + final Path classPath = cmdl.createClasspath( project ); + classPath.createPathElement().setLocation( jar ); + + // set the metamata.home property + final Commandline.Argument vmArgs = cmdl.createVmArgument(); + vmArgs.setValue( "-Dmetamata.home=" + metamataHome.getAbsolutePath() ); + + // retrieve all the files we want to scan + includedFiles = scanFileSets(); + log( includedFiles.size() + " files added for audit", Project.MSG_VERBOSE ); + + // write all the options to a temp file and use it ro run the process + Vector options = getOptions(); + optionsFile = createTmpFile(); + generateOptionsFile( optionsFile, options ); + Commandline.Argument args = cmdl.createArgument(); + args.setLine( "-arguments " + optionsFile.getAbsolutePath() ); + } + + /** + * return the location of the jar file used to run + * + * @param home Description of Parameter + * @return The MetamataJar value + */ + protected final File getMetamataJar( File home ) + { + return new File( new File( home.getAbsolutePath() ), "lib/metamata.jar" ); + } + + + protected Hashtable getFileMapping() + { + return includedFiles; + } + + /** + * return all options of the command line as string elements + * + * @return The Options value + */ + protected abstract Vector getOptions(); + + /** + * validate options set + * + * @exception BuildException Description of Exception + */ + protected void checkOptions() + throws BuildException + { + // do some validation first + if( metamataHome == null || !metamataHome.exists() ) + { + throw new BuildException( "'metamatahome' must point to Metamata home directory." ); + } + metamataHome = project.resolveFile( metamataHome.getPath() ); + File jar = getMetamataJar( metamataHome ); + if( !jar.exists() ) + { + throw new BuildException( jar + " does not exist. Check your metamata installation." ); + } + } + + /** + * clean up all the mess that we did with temporary objects + */ + protected void cleanUp() + { + if( optionsFile != null ) + { + optionsFile.delete(); + optionsFile = null; + } + } + + /** + * create a stream handler that will be used to get the output since + * metamata tools do not report with convenient files such as XML. + * + * @return Description of the Returned Value + */ + protected abstract ExecuteStreamHandler createStreamHandler(); + + + /** + * execute the process with a specific handler + * + * @param handler Description of Parameter + * @exception BuildException Description of Exception + */ + protected void execute0( ExecuteStreamHandler handler ) + throws BuildException + { + final Execute process = new Execute( handler ); + log( cmdl.toString(), Project.MSG_VERBOSE ); + process.setCommandline( cmdl.getCommandline() ); + try + { + if( process.execute() != 0 ) + { + throw new BuildException( "Metamata task failed." ); + } + } + catch( IOException e ) + { + throw new BuildException( "Failed to launch Metamata task: " + e ); + } + } + + + protected void generateOptionsFile( File tofile, Vector options ) + throws BuildException + { + FileWriter fw = null; + try + { + fw = new FileWriter( tofile ); + PrintWriter pw = new PrintWriter( fw ); + final int size = options.size(); + for( int i = 0; i < size; i++ ) + { + pw.println( options.elementAt( i ) ); + } + pw.flush(); + } + catch( IOException e ) + { + throw new BuildException( "Error while writing options file " + tofile, e ); + } + finally + { + if( fw != null ) + { + try + { + fw.close(); + } + catch( IOException ignored ) + {} + } + } + } + + /** + * @return the list of .java files (as their absolute path) that should be + * audited. + */ + protected Hashtable scanFileSets() + { + Hashtable files = new Hashtable(); + for( int i = 0; i < fileSets.size(); i++ ) + { + FileSet fs = ( FileSet )fileSets.elementAt( i ); + DirectoryScanner ds = fs.getDirectoryScanner( project ); + ds.scan(); + String[] f = ds.getIncludedFiles(); + log( i + ") Adding " + f.length + " files from directory " + ds.getBasedir(), Project.MSG_VERBOSE ); + for( int j = 0; j < f.length; j++ ) + { + String pathname = f[j]; + if( pathname.endsWith( ".java" ) ) + { + File file = new File( ds.getBasedir(), pathname ); +// file = project.resolveFile(file.getAbsolutePath()); + String classname = pathname.substring( 0, pathname.length() - ".java".length() ); + classname = classname.replace( File.separatorChar, '.' ); + files.put( file.getAbsolutePath(), classname );// it's a java file, add it. + } + } + } + return files; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java new file mode 100644 index 000000000..80c5415bb --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/taskdefs/optional/metamata/MAudit.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.taskdefs.optional.metamata; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.ExecuteStreamHandler; +import org.apache.tools.ant.taskdefs.LogStreamHandler; +import org.apache.tools.ant.types.Path; + +/** + * Metamata Audit evaluates Java code for programming errors, weaknesses, and + * style violation.

              + * + * Metamata Audit exists in three versions: + *

                + *
              • The Lite version evaluates about 15 built-in rules.
              • + *
              • The Pro version evaluates about 50 built-in rules.
              • + *
              • The Enterprise version allows you to add your own customized rules via + * the API.
              • + *
                  For more information, visit the website at www.metamata.com + * + * @author Stephane Bailliez + */ +public class MAudit extends AbstractMetamataTask +{ + + /* + * As of Metamata 2.0, the command line of MAudit is as follows: + * Usage + * maudit + */ +public class DOMElementWriter +{ + + private static String lSep = System.getProperty( "line.separator" ); + private StringBuffer sb = new StringBuffer(); + + /** + * Don't try to be too smart but at least recognize the predefined entities. + */ + protected String[] knownEntities = {"gt", "amp", "lt", "apos", "quot"}; + + /** + * Is the given argument a character or entity reference? + * + * @param ent Description of Parameter + * @return The Reference value + */ + public boolean isReference( String ent ) + { + if( !( ent.charAt( 0 ) == '&' ) || !ent.endsWith( ";" ) ) + { + return false; + } + + if( ent.charAt( 1 ) == '#' ) + { + if( ent.charAt( 2 ) == 'x' ) + { + try + { + Integer.parseInt( ent.substring( 3, ent.length() - 1 ), 16 ); + return true; + } + catch( NumberFormatException nfe ) + { + return false; + } + } + else + { + try + { + Integer.parseInt( ent.substring( 2, ent.length() - 1 ) ); + return true; + } + catch( NumberFormatException nfe ) + { + return false; + } + } + } + + String name = ent.substring( 1, ent.length() - 1 ); + for( int i = 0; i < knownEntities.length; i++ ) + { + if( name.equals( knownEntities[i] ) ) + { + return true; + } + } + return false; + } + + /** + * Escape <, > & ' and " as their entities. + * + * @param value Description of Parameter + * @return Description of the Returned Value + */ + public String encode( String value ) + { + sb.setLength( 0 ); + for( int i = 0; i < value.length(); i++ ) + { + char c = value.charAt( i ); + switch ( c ) + { + case '<': + sb.append( "<" ); + break; + case '>': + sb.append( ">" ); + break; + case '\'': + sb.append( "'" ); + break; + case '\"': + sb.append( """ ); + break; + case '&': + int nextSemi = value.indexOf( ";", i ); + if( nextSemi < 0 + || !isReference( value.substring( i, nextSemi + 1 ) ) ) + { + sb.append( "&" ); + } + else + { + sb.append( '&' ); + } + break; + default: + sb.append( c ); + break; + } + } + return sb.toString(); + } + + /** + * Writes a DOM tree to a stream. + * + * @param element the Root DOM element of the tree + * @param out where to send the output + * @param indent number of + * @param indentWith strings, that should be used to indent the + * corresponding tag. + * @exception IOException Description of Exception + */ + public void write( Element element, Writer out, int indent, + String indentWith ) + throws IOException + { + + // Write indent characters + for( int i = 0; i < indent; i++ ) + { + out.write( indentWith ); + } + + // Write element + out.write( "<" ); + out.write( element.getTagName() ); + + // Write attributes + NamedNodeMap attrs = element.getAttributes(); + for( int i = 0; i < attrs.getLength(); i++ ) + { + Attr attr = ( Attr )attrs.item( i ); + out.write( " " ); + out.write( attr.getName() ); + out.write( "=\"" ); + out.write( encode( attr.getValue() ) ); + out.write( "\"" ); + } + out.write( ">" ); + + // Write child elements and text + boolean hasChildren = false; + NodeList children = element.getChildNodes(); + for( int i = 0; i < children.getLength(); i++ ) + { + Node child = children.item( i ); + + switch ( child.getNodeType() ) + { + + case Node.ELEMENT_NODE: + if( !hasChildren ) + { + out.write( lSep ); + hasChildren = true; + } + write( ( Element )child, out, indent + 1, indentWith ); + break; + case Node.TEXT_NODE: + out.write( encode( child.getNodeValue() ) ); + break; + case Node.CDATA_SECTION_NODE: + out.write( "" ); + break; + case Node.ENTITY_REFERENCE_NODE: + out.write( '&' ); + out.write( child.getNodeName() ); + out.write( ';' ); + break; + case Node.PROCESSING_INSTRUCTION_NODE: + out.write( " 0 ) + { + out.write( ' ' ); + out.write( data ); + } + out.write( "?>" ); + break; + } + } + + // If we had child elements, we need to indent before we close + // the element, otherwise we're on the same line and don't need + // to indent + if( hasChildren ) + { + for( int i = 0; i < indent; i++ ) + { + out.write( indentWith ); + } + } + + // Write element close + out.write( "" ); + out.write( lSep ); + out.flush(); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileNameMapper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileNameMapper.java new file mode 100644 index 000000000..33ae65096 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileNameMapper.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Interface to be used by SourceFileScanner.

                  + * + * Used to find the name of the target file(s) corresponding to a source file. + *

                  + * + * The rule by which the file names are transformed is specified via the setFrom + * and setTo methods. The exact meaning of these is implementation dependent. + *

                  + * + * @author
                  Stefan Bodewig + */ +public interface FileNameMapper +{ + + /** + * Sets the from part of the transformation rule. + * + * @param from The new From value + */ + void setFrom( String from ); + + /** + * Sets the to part of the transformation rule. + * + * @param to The new To value + */ + void setTo( String to ); + + /** + * Returns an array containing the target filename(s) for the given source + * file.

                  + * + * if the given rule doesn't apply to the source file, implementation must + * return null. SourceFileScanner will then omit the source file in + * question.

                  + * + * @param sourceFileName the name of the source file relative to some given + * basedirectory. + * @return Description of the Returned Value + */ + String[] mapFileName( String sourceFileName ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileUtils.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileUtils.java new file mode 100644 index 000000000..a27ab6f63 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FileUtils.java @@ -0,0 +1,674 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.text.DecimalFormat; +import java.util.Random; +import java.util.Stack; +import java.util.StringTokenizer; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.types.FilterSetCollection; + +/** + * This class also encapsulates methods which allow Files to be refered to using + * abstract path names which are translated to native system file paths at + * runtime as well as copying files or setting there last modification time. + * + * @author duncan@x180.com + * @author Conor MacNeill + * @author Stefan Bodewig + * @version $Revision$ + */ + +public class FileUtils +{ + private static Random rand = new Random( System.currentTimeMillis() ); + private static Object lockReflection = new Object(); + private static java.lang.reflect.Method setLastModified = null; + + /** + * Empty constructor. + */ + protected FileUtils() { } + + /** + * Factory method. + * + * @return Description of the Returned Value + */ + public static FileUtils newFileUtils() + { + return new FileUtils(); + } + + /** + * Calls File.setLastModified(long time) in a Java 1.1 compatible way. + * + * @param file The new FileLastModified value + * @param time The new FileLastModified value + * @exception BuildException Description of Exception + */ + public void setFileLastModified( File file, long time ) + throws BuildException + { + if( Project.getJavaVersion() == Project.JAVA_1_1 ) + { + return; + } + Long[] times = new Long[1]; + if( time < 0 ) + { + times[0] = new Long( System.currentTimeMillis() ); + } + else + { + times[0] = new Long( time ); + } + + try + { + getSetLastModified().invoke( file, times ); + } + catch( java.lang.reflect.InvocationTargetException ite ) + { + Throwable nested = ite.getTargetException(); + throw new BuildException( "Exception setting the modification time " + + "of " + file, nested ); + } + catch( Throwable other ) + { + throw new BuildException( "Exception setting the modification time " + + "of " + file, other ); + } + } + + /** + * Emulation of File.getParentFile for JDK 1.1 + * + * @param f Description of Parameter + * @return The ParentFile value + * @since 1.10 + */ + public File getParentFile( File f ) + { + if( f != null ) + { + String p = f.getParent(); + if( p != null ) + { + return new File( p ); + } + } + return null; + } + + /** + * Compares the contents of two files. + * + * @param f1 Description of Parameter + * @param f2 Description of Parameter + * @return Description of the Returned Value + * @exception IOException Description of Exception + * @since 1.9 + */ + public boolean contentEquals( File f1, File f2 ) + throws IOException + { + if( f1.exists() != f2.exists() ) + { + return false; + } + + if( !f1.exists() ) + { + // two not existing files are equal + return true; + } + + if( f1.isDirectory() || f2.isDirectory() ) + { + // don't want to compare directory contents for now + return false; + } + + InputStream in1 = null; + InputStream in2 = null; + try + { + in1 = new BufferedInputStream( new FileInputStream( f1 ) ); + in2 = new BufferedInputStream( new FileInputStream( f2 ) ); + + int expectedByte = in1.read(); + while( expectedByte != -1 ) + { + if( expectedByte != in2.read() ) + { + return false; + } + expectedByte = in1.read(); + } + if( in2.read() != -1 ) + { + return false; + } + return true; + } + finally + { + if( in1 != null ) + { + try + { + in1.close(); + } + catch( IOException e ) + {} + } + if( in2 != null ) + { + try + { + in2.close(); + } + catch( IOException e ) + {} + } + } + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), null, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile, FilterSetCollection filters ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), filters, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile, FilterSetCollection filters, + boolean overwrite ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), filters, + overwrite, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + */ + public void copyFile( String sourceFile, String destFile, FilterSetCollection filters, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + copyFile( new File( sourceFile ), new File( destFile ), filters, + overwrite, preserveLastModified ); + } + + /** + * Convienence method to copy a file from a source to a destination. No + * filtering is performed. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile ) + throws IOException + { + copyFile( sourceFile, destFile, null, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile, FilterSetCollection filters ) + throws IOException + { + copyFile( sourceFile, destFile, filters, false, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used and if source files may + * overwrite newer destination files. + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile, FilterSetCollection filters, + boolean overwrite ) + throws IOException + { + copyFile( sourceFile, destFile, filters, overwrite, false ); + } + + /** + * Convienence method to copy a file from a source to a destination + * specifying if token filtering must be used, if source files may overwrite + * newer destination files and the last modified time of destFile + * file should be made equal to the last modified time of sourceFile + * . + * + * @param sourceFile Description of Parameter + * @param destFile Description of Parameter + * @param filters Description of Parameter + * @param overwrite Description of Parameter + * @param preserveLastModified Description of Parameter + * @throws IOException + */ + public void copyFile( File sourceFile, File destFile, FilterSetCollection filters, + boolean overwrite, boolean preserveLastModified ) + throws IOException + { + + if( overwrite || !destFile.exists() || + destFile.lastModified() < sourceFile.lastModified() ) + { + + if( destFile.exists() && destFile.isFile() ) + { + destFile.delete(); + } + + // ensure that parent dir of dest file exists! + // not using getParentFile method to stay 1.1 compat + File parent = getParentFile( destFile ); + if( !parent.exists() ) + { + parent.mkdirs(); + } + + if( filters != null && filters.hasFilters() ) + { + BufferedReader in = new BufferedReader( new FileReader( sourceFile ) ); + BufferedWriter out = new BufferedWriter( new FileWriter( destFile ) ); + + int length; + String newline = null; + String line = in.readLine(); + while( line != null ) + { + if( line.length() == 0 ) + { + out.newLine(); + } + else + { + newline = filters.replaceTokens( line ); + out.write( newline ); + out.newLine(); + } + line = in.readLine(); + } + + out.close(); + in.close(); + } + else + { + FileInputStream in = new FileInputStream( sourceFile ); + FileOutputStream out = new FileOutputStream( destFile ); + + byte[] buffer = new byte[8 * 1024]; + int count = 0; + do + { + out.write( buffer, 0, count ); + count = in.read( buffer, 0, buffer.length ); + }while ( count != -1 ); + + in.close(); + out.close(); + } + + if( preserveLastModified ) + { + setFileLastModified( destFile, sourceFile.lastModified() ); + } + } + } + + /** + * Create a temporary file in a given directory.

                  + * + * The file denoted by the returned abstract pathname did not exist before + * this method was invoked, any subsequent invocation of this method will + * yield a different file name.

                  + * + * This method is different to File.createTempFile of JDK 1.2 as it doesn't + * create the file itself and doesn't use platform specific temporary + * directory when the parentDir attribute is null.

                  + * + * @param parentDir Directory to create the temporary file in - current + * working directory will be assumed if this parameter is null. + * @param prefix Description of Parameter + * @param suffix Description of Parameter + * @return Description of the Returned Value + * @since 1.8 + */ + public File createTempFile( String prefix, String suffix, File parentDir ) + { + + File result = null; + String parent = null; + if( parentDir != null ) + { + parent = parentDir.getPath(); + } + DecimalFormat fmt = new DecimalFormat( "#####" ); + synchronized( rand ) + { + do + { + result = new File( parent, + prefix + fmt.format( rand.nextInt() ) + + suffix ); + }while ( result.exists() ); + } + return result; + } + + /** + * "normalize" the given absolute path.

                  + * + * This includes: + *

                    + *
                  • Uppercase the drive letter if there is one.
                  • + *
                  • Remove redundant slashes after the drive spec.
                  • + *
                  • resolve all ./, .\, ../ and ..\ sequences.
                  • + *
                  • DOS style paths that start with a drive letter will have \ as the + * separator.
                  • + *
                  + * + * + * @param path Description of Parameter + * @return Description of the Returned Value + * @throws java.lang.NullPointerException if the file path is equal to null. + */ + public File normalize( String path ) + { + String orig = path; + + path = path.replace( '/', File.separatorChar ) + .replace( '\\', File.separatorChar ); + + // make sure we are dealing with an absolute path + if( !path.startsWith( File.separator ) && + !( path.length() >= 2 && + Character.isLetter( path.charAt( 0 ) ) && + path.charAt( 1 ) == ':' ) + ) + { + String msg = path + " is not an absolute path"; + throw new BuildException( msg ); + } + + boolean dosWithDrive = false; + String root = null; + // Eliminate consecutive slashes after the drive spec + if( path.length() >= 2 && + Character.isLetter( path.charAt( 0 ) ) && + path.charAt( 1 ) == ':' ) + { + + dosWithDrive = true; + + char[] ca = path.replace( '/', '\\' ).toCharArray(); + StringBuffer sb = new StringBuffer(); + sb.append( Character.toUpperCase( ca[0] ) ).append( ':' ); + + for( int i = 2; i < ca.length; i++ ) + { + if( ( ca[i] != '\\' ) || + ( ca[i] == '\\' && ca[i - 1] != '\\' ) + ) + { + sb.append( ca[i] ); + } + } + + path = sb.toString().replace( '\\', File.separatorChar ); + if( path.length() == 2 ) + { + root = path; + path = ""; + } + else + { + root = path.substring( 0, 3 ); + path = path.substring( 3 ); + } + + } + else + { + if( path.length() == 1 ) + { + root = File.separator; + path = ""; + } + else if( path.charAt( 1 ) == File.separatorChar ) + { + // UNC drive + root = File.separator + File.separator; + path = path.substring( 2 ); + } + else + { + root = File.separator; + path = path.substring( 1 ); + } + } + + Stack s = new Stack(); + s.push( root ); + StringTokenizer tok = new StringTokenizer( path, File.separator ); + while( tok.hasMoreTokens() ) + { + String thisToken = tok.nextToken(); + if( ".".equals( thisToken ) ) + { + continue; + } + else if( "..".equals( thisToken ) ) + { + if( s.size() < 2 ) + { + throw new BuildException( "Cannot resolve path " + orig ); + } + else + { + s.pop(); + } + } + else + {// plain component + s.push( thisToken ); + } + } + + StringBuffer sb = new StringBuffer(); + for( int i = 0; i < s.size(); i++ ) + { + if( i > 1 ) + { + // not before the filesystem root and not after it, since root + // already contains one + sb.append( File.separatorChar ); + } + sb.append( s.elementAt( i ) ); + } + + path = sb.toString(); + if( dosWithDrive ) + { + path = path.replace( '/', '\\' ); + } + return new File( path ); + } + + /** + * Interpret the filename as a file relative to the given file - unless the + * filename already represents an absolute filename. + * + * @param file the "reference" file for relative paths. This instance must + * be an absolute file and must not contain "./" or + * "../" sequences (same for \ instead of /). If it is null, + * this call is equivalent to new java.io.File(filename). + * @param filename a file name + * @return an absolute file that doesn't contain "./" or + * "../" sequences and uses the correct separator for the + * current platform. + */ + public File resolveFile( File file, String filename ) + { + filename = filename.replace( '/', File.separatorChar ) + .replace( '\\', File.separatorChar ); + + // deal with absolute files + if( filename.startsWith( File.separator ) || + ( filename.length() >= 2 && + Character.isLetter( filename.charAt( 0 ) ) && + filename.charAt( 1 ) == ':' ) + ) + { + return normalize( filename ); + } + + if( file == null ) + { + return new File( filename ); + } + + File helpFile = new File( file.getAbsolutePath() ); + StringTokenizer tok = new StringTokenizer( filename, File.separator ); + while( tok.hasMoreTokens() ) + { + String part = tok.nextToken(); + if( part.equals( ".." ) ) + { + helpFile = getParentFile( helpFile ); + if( helpFile == null ) + { + String msg = "The file or path you specified (" + + filename + ") is invalid relative to " + + file.getPath(); + throw new BuildException( msg ); + } + } + else if( part.equals( "." ) ) + { + // Do nothing here + } + else + { + helpFile = new File( helpFile, part ); + } + } + + return new File( helpFile.getAbsolutePath() ); + } + + /** + * see whether we have a setLastModified method in File and return it. + * + * @return The SetLastModified value + */ + protected final Method getSetLastModified() + { + if( Project.getJavaVersion() == Project.JAVA_1_1 ) + { + return null; + } + if( setLastModified == null ) + { + synchronized( lockReflection ) + { + if( setLastModified == null ) + { + try + { + setLastModified = + java.io.File.class.getMethod( "setLastModified", + new Class[]{Long.TYPE} ); + } + catch( NoSuchMethodException nse ) + { + throw new BuildException( "File.setlastModified not in JDK > 1.1?", + nse ); + } + } + } + } + return setLastModified; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FlatFileNameMapper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FlatFileNameMapper.java new file mode 100644 index 000000000..37c6e5bad --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/FlatFileNameMapper.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that always returns the source file name + * without any leading directory information.

                  + * + * This is the default FileNameMapper for the copy and move tasks if the flatten + * attribute has been set.

                  + * + * @author Stefan Bodewig + */ +public class FlatFileNameMapper implements FileNameMapper +{ + + /** + * Ignored. + * + * @param from The new From value + */ + public void setFrom( String from ) { } + + /** + * Ignored. + * + * @param to The new To value + */ + public void setTo( String to ) { } + + /** + * Returns an one-element array containing the source file name without any + * leading directory information. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + return new String[]{new java.io.File( sourceFileName ).getName()}; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/GlobPatternMapper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/GlobPatternMapper.java new file mode 100644 index 000000000..f9cb24277 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/GlobPatternMapper.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that does simple wildcard pattern + * replacements.

                  + * + * This does simple translations like *.foo -> *.bar where the prefix to .foo + * will be left unchanged. It only handles a single * character, use regular + * expressions for more complicated situations.

                  + * + * This is one of the more useful Mappers, it is used by javac for example.

                  + * + * @author Stefan Bodewig + */ +public class GlobPatternMapper implements FileNameMapper +{ + /** + * Part of "from" pattern before the *. + */ + protected String fromPrefix = null; + + /** + * Part of "from" pattern after the *. + */ + protected String fromPostfix = null; + + /** + * Part of "to" pattern before the *. + */ + protected String toPrefix = null; + + /** + * Part of "to" pattern after the *. + */ + protected String toPostfix = null; + + /** + * Length of the postfix ("from" pattern). + */ + protected int postfixLength; + + /** + * Length of the prefix ("from" pattern). + */ + protected int prefixLength; + + /** + * Sets the "from" pattern. Required. + * + * @param from The new From value + */ + public void setFrom( String from ) + { + int index = from.lastIndexOf( "*" ); + if( index == -1 ) + { + fromPrefix = from; + fromPostfix = ""; + } + else + { + fromPrefix = from.substring( 0, index ); + fromPostfix = from.substring( index + 1 ); + } + prefixLength = fromPrefix.length(); + postfixLength = fromPostfix.length(); + } + + /** + * Sets the "to" pattern. Required. + * + * @param to The new To value + */ + public void setTo( String to ) + { + int index = to.lastIndexOf( "*" ); + if( index == -1 ) + { + toPrefix = to; + toPostfix = ""; + } + else + { + toPrefix = to.substring( 0, index ); + toPostfix = to.substring( index + 1 ); + } + } + + /** + * Returns null if the source file name doesn't match the "from" + * pattern, an one-element array containing the translated file otherwise. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + if( fromPrefix == null + || !sourceFileName.startsWith( fromPrefix ) + || !sourceFileName.endsWith( fromPostfix ) ) + { + return null; + } + return new String[]{toPrefix + + extractVariablePart( sourceFileName ) + + toPostfix}; + } + + /** + * Returns the part of the given string that matches the * in the + * "from" pattern. + * + * @param name Description of Parameter + * @return Description of the Returned Value + */ + protected String extractVariablePart( String name ) + { + return name.substring( prefixLength, + name.length() - postfixLength ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/IdentityMapper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/IdentityMapper.java new file mode 100644 index 000000000..a93007e83 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/IdentityMapper.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that always returns the source file name. + *

                  + * + * This is the default FileNameMapper for the copy and move tasks.

                  + * + * @author Stefan Bodewig + */ +public class IdentityMapper implements FileNameMapper +{ + + /** + * Ignored. + * + * @param from The new From value + */ + public void setFrom( String from ) { } + + /** + * Ignored. + * + * @param to The new To value + */ + public void setTo( String to ) { } + + /** + * Returns an one-element array containing the source file name. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + return new String[]{sourceFileName}; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/MergingMapper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/MergingMapper.java new file mode 100644 index 000000000..d9c0b866e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/MergingMapper.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; + +/** + * Implementation of FileNameMapper that always returns the same target file + * name.

                  + * + * This is the default FileNameMapper for the archiving tasks and uptodate.

                  + * + * @author Stefan Bodewig + */ +public class MergingMapper implements FileNameMapper +{ + protected String[] mergedFile = null; + + /** + * Ignored. + * + * @param from The new From value + */ + public void setFrom( String from ) { } + + /** + * Sets the name of the merged file. + * + * @param to The new To value + */ + public void setTo( String to ) + { + mergedFile = new String[]{to}; + } + + /** + * Returns an one-element array containing the file name set via setTo. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + return mergedFile; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/RegexpPatternMapper.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/RegexpPatternMapper.java new file mode 100644 index 000000000..df29877a4 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/RegexpPatternMapper.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.util.regexp.RegexpMatcher; +import org.apache.tools.ant.util.regexp.RegexpMatcherFactory; + +/** + * Implementation of FileNameMapper that does regular expression replacements. + * + * @author Stefan Bodewig + */ +public class RegexpPatternMapper implements FileNameMapper +{ + protected RegexpMatcher reg = null; + protected char[] to = null; + protected StringBuffer result = new StringBuffer(); + + public RegexpPatternMapper() + throws BuildException + { + reg = ( new RegexpMatcherFactory() ).newRegexpMatcher(); + } + + /** + * Sets the "from" pattern. Required. + * + * @param from The new From value + * @exception BuildException Description of Exception + */ + public void setFrom( String from ) + throws BuildException + { + try + { + reg.setPattern( from ); + } + catch( NoClassDefFoundError e ) + { + // depending on the implementation the actual RE won't + // get instantiated in the constructor. + throw new BuildException( "Cannot load regular expression matcher", + e ); + } + } + + /** + * Sets the "to" pattern. Required. + * + * @param to The new To value + */ + public void setTo( String to ) + { + this.to = to.toCharArray(); + } + + /** + * Returns null if the source file name doesn't match the "from" + * pattern, an one-element array containing the translated file otherwise. + * + * @param sourceFileName Description of Parameter + * @return Description of the Returned Value + */ + public String[] mapFileName( String sourceFileName ) + { + if( reg == null || to == null + || !reg.matches( sourceFileName ) ) + { + return null; + } + return new String[]{replaceReferences( sourceFileName )}; + } + + /** + * Replace all backreferences in the to pattern with the matched groups of + * the source. + * + * @param source Description of Parameter + * @return Description of the Returned Value + */ + protected String replaceReferences( String source ) + { + Vector v = reg.getGroups( source ); + + result.setLength( 0 ); + for( int i = 0; i < to.length; i++ ) + { + if( to[i] == '\\' ) + { + if( ++i < to.length ) + { + int value = Character.digit( to[i], 10 ); + if( value > -1 ) + { + result.append( ( String )v.elementAt( value ) ); + } + else + { + result.append( to[i] ); + } + } + else + { + // XXX - should throw an exception instead? + result.append( '\\' ); + } + } + else + { + result.append( to[i] ); + } + } + return result.toString(); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/SourceFileScanner.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/SourceFileScanner.java new file mode 100644 index 000000000..4106357ac --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/SourceFileScanner.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.io.File; +import java.util.Vector; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.Task; +import org.apache.myrmidon.framework.Os; + +/** + * Utility class that collects the functionality of the various scanDir methods + * that have been scattered in several tasks before.

                  + * + * The only method returns an array of source files. The array is a subset of + * the files given as a parameter and holds only those that are newer than their + * corresponding target files.

                  + * + * @author Stefan Bodewig + */ +public class SourceFileScanner +{ + + protected Task task; + + private FileUtils fileUtils; + + /** + * @param task The task we should log messages through + */ + public SourceFileScanner( Task task ) + { + this.task = task; + fileUtils = FileUtils.newFileUtils(); + } + + /** + * Restrict the given set of files to those that are newer than their + * corresponding target files. + * + * @param files the original set of files + * @param srcDir all files are relative to this directory + * @param destDir target files live here. if null file names returned by the + * mapper are assumed to be absolute. + * @param mapper knows how to construct a target file names from source file + * names. + * @return Description of the Returned Value + */ + public String[] restrict( String[] files, File srcDir, File destDir, + FileNameMapper mapper ) + { + + long now = ( new java.util.Date() ).getTime(); + StringBuffer targetList = new StringBuffer(); + + /* + * If we're on Windows, we have to munge the time up to 2 secs to + * be able to check file modification times. + * (Windows has a max resolution of two secs for modification times) + * Actually this is a feature of the FAT file system, NTFS does + * not have it, so if we could reliably passively test for an NTFS + * file systems we could turn this off... + */ + if( Os.isFamily( "windows" ) ) + { + now += 2000; + } + + Vector v = new Vector(); + for( int i = 0; i < files.length; i++ ) + { + + String[] targets = mapper.mapFileName( files[i] ); + if( targets == null || targets.length == 0 ) + { + task.log( files[i] + " skipped - don\'t know how to handle it", + Project.MSG_VERBOSE ); + continue; + } + + File src = fileUtils.resolveFile( srcDir, files[i] ); + + if( src.lastModified() > now ) + { + task.log( "Warning: " + files[i] + " modified in the future.", + Project.MSG_WARN ); + } + + boolean added = false; + targetList.setLength( 0 ); + for( int j = 0; !added && j < targets.length; j++ ) + { + File dest = fileUtils.resolveFile( destDir, targets[j] ); + + if( !dest.exists() ) + { + task.log( files[i] + " added as " + dest.getAbsolutePath() + " doesn\'t exist.", + Project.MSG_VERBOSE ); + v.addElement( files[i] ); + added = true; + } + else if( src.lastModified() > dest.lastModified() ) + { + task.log( files[i] + " added as " + dest.getAbsolutePath() + " is outdated.", + Project.MSG_VERBOSE ); + v.addElement( files[i] ); + added = true; + } + else + { + if( targetList.length() > 0 ) + { + targetList.append( ", " ); + } + targetList.append( dest.getAbsolutePath() ); + } + } + + if( !added ) + { + task.log( files[i] + " omitted as " + targetList.toString() + + ( targets.length == 1 ? " is" : " are " ) + + " up to date.", Project.MSG_VERBOSE ); + } + + } + String[] result = new String[v.size()]; + v.copyInto( result ); + return result; + } + + /** + * Convinience layer on top of restrict that returns the source files as + * File objects (containing absolute paths if srcDir is absolute). + * + * @param files Description of Parameter + * @param srcDir Description of Parameter + * @param destDir Description of Parameter + * @param mapper Description of Parameter + * @return Description of the Returned Value + */ + public File[] restrictAsFiles( String[] files, File srcDir, File destDir, + FileNameMapper mapper ) + { + String[] res = restrict( files, srcDir, destDir, mapper ); + File[] result = new File[res.length]; + for( int i = 0; i < res.length; i++ ) + { + result[i] = new File( srcDir, res[i] ); + } + return result; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/StringUtils.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/StringUtils.java new file mode 100644 index 000000000..6881490c0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/StringUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util; +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * A set of helper methods related to string manipulation. + * + * @author Stephane Bailliez + */ +public final class StringUtils +{ + + /** + * the line separator for this OS + */ + public final static String LINE_SEP = System.getProperty( "line.separator" ); + + /** + * Convenient method to retrieve the full stacktrace from a given exception. + * + * @param t the exception to get the stacktrace from. + * @return the stacktrace from the given exception. + */ + public static String getStackTrace( Throwable t ) + { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter( sw, true ); + t.printStackTrace( pw ); + pw.flush(); + pw.close(); + return sw.toString(); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Dependencies.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Dependencies.java new file mode 100644 index 000000000..b7808ff0c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Dependencies.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.depend; +import java.io.*; +import java.util.*; +import org.apache.bcel.*; +import org.apache.bcel.classfile.*; + + +public class Dependencies implements Visitor +{ + private boolean verbose = false; + private Set dependencies = new HashSet(); + private ConstantPool constantPool; + + private JavaClass javaClass; + + public static void applyFilter( Collection collection, Filter filter ) + { + Iterator i = collection.iterator(); + while( i.hasNext() ) + { + Object next = i.next(); + if( !filter.accept( next ) ) + { + i.remove(); + } + } + } + + public static void main( String[] args ) + { + try + { + Dependencies visitor = new Dependencies(); + + Set set = new TreeSet(); + Set newSet = new HashSet(); + + int o = 0; + String arg = null; + if( "-base".equals( args[0] ) ) + { + arg = args[1]; + if( !arg.endsWith( File.separator ) ) + { + arg = arg + File.separator; + } + o = 2; + } + final String base = arg; + + for( int i = o; i < args.length; i++ ) + { + String fileName = args[i].substring( 0, args[i].length() - ".class".length() ); + if( base != null && fileName.startsWith( base ) ) + fileName = fileName.substring( base.length() ); + newSet.add( fileName ); + } + set.addAll( newSet ); + + do + { + Iterator i = newSet.iterator(); + while( i.hasNext() ) + { + String fileName = i.next() + ".class"; + + if( base != null ) + { + fileName = base + fileName; + } + + JavaClass javaClass = new ClassParser( fileName ).parse(); + javaClass.accept( visitor ); + } + newSet.clear(); + newSet.addAll( visitor.getDependencies() ); + visitor.clearDependencies(); + + applyFilter( newSet, + new Filter() + { + public boolean accept( Object object ) + { + String fileName = object + ".class"; + if( base != null ) + fileName = base + fileName; + return new File( fileName ).exists(); + } + } ); + newSet.removeAll( set ); + set.addAll( newSet ); + }while ( newSet.size() > 0 ); + + Iterator i = set.iterator(); + while( i.hasNext() ) + { + System.out.println( i.next() ); + } + } + catch( Exception e ) + { + System.err.println( e.getMessage() ); + e.printStackTrace( System.err ); + } + } + + public Set getDependencies() + { + return dependencies; + } + + public void clearDependencies() + { + dependencies.clear(); + } + + public void visitCode( Code obj ) { } + + public void visitCodeException( CodeException obj ) { } + + public void visitConstantClass( ConstantClass obj ) + { + if( verbose ) + { + System.out.println( "visit ConstantClass" ); + System.out.println( obj.getConstantValue( constantPool ) ); + } + dependencies.add( "" + obj.getConstantValue( constantPool ) ); + } + + public void visitConstantDouble( ConstantDouble obj ) { } + + public void visitConstantFieldref( ConstantFieldref obj ) { } + + public void visitConstantFloat( ConstantFloat obj ) { } + + public void visitConstantInteger( ConstantInteger obj ) { } + + public void visitConstantInterfaceMethodref( ConstantInterfaceMethodref obj ) { } + + public void visitConstantLong( ConstantLong obj ) { } + + public void visitConstantMethodref( ConstantMethodref obj ) { } + + public void visitConstantNameAndType( ConstantNameAndType obj ) { } + + public void visitConstantPool( ConstantPool obj ) + { + if( verbose ) + System.out.println( "visit ConstantPool" ); + this.constantPool = obj; + + // visit constants + for( int idx = 0; idx < constantPool.getLength(); idx++ ) + { + Constant c = constantPool.getConstant( idx ); + if( c != null ) + { + c.accept( this ); + } + } + } + + public void visitConstantString( ConstantString obj ) { } + + public void visitConstantUtf8( ConstantUtf8 obj ) { } + + public void visitConstantValue( ConstantValue obj ) { } + + public void visitDeprecated( Deprecated obj ) { } + + public void visitExceptionTable( ExceptionTable obj ) { } + + public void visitField( Field obj ) + { + if( verbose ) + { + System.out.println( "visit Field" ); + System.out.println( obj.getSignature() ); + } + addClasses( obj.getSignature() ); + } + + public void visitInnerClass( InnerClass obj ) { } + + public void visitInnerClasses( InnerClasses obj ) { } + + public void visitJavaClass( JavaClass obj ) + { + if( verbose ) + { + System.out.println( "visit JavaClass" ); + } + + this.javaClass = obj; + dependencies.add( javaClass.getClassName().replace( '.', '/' ) ); + + // visit constant pool + javaClass.getConstantPool().accept( this ); + + // visit fields + Field[] fields = obj.getFields(); + for( int i = 0; i < fields.length; i++ ) + { + fields[i].accept( this ); + } + + // visit methods + Method[] methods = obj.getMethods(); + for( int i = 0; i < methods.length; i++ ) + { + methods[i].accept( this ); + } + } + + public void visitLineNumber( LineNumber obj ) { } + + public void visitLineNumberTable( LineNumberTable obj ) { } + + public void visitLocalVariable( LocalVariable obj ) { } + + public void visitLocalVariableTable( LocalVariableTable obj ) { } + + public void visitMethod( Method obj ) + { + if( verbose ) + { + System.out.println( "visit Method" ); + System.out.println( obj.getSignature() ); + } + String signature = obj.getSignature(); + int pos = signature.indexOf( ")" ); + addClasses( signature.substring( 1, pos ) ); + addClasses( signature.substring( pos + 1 ) ); + } + + public void visitSourceFile( SourceFile obj ) { } + + public void visitStackMap( StackMap obj ) { } + + public void visitStackMapEntry( StackMapEntry obj ) { } + + public void visitSynthetic( Synthetic obj ) { } + + public void visitUnknown( Unknown obj ) { } + + void addClass( String string ) + { + int pos = string.indexOf( 'L' ); + if( pos != -1 ) + { + dependencies.add( string.substring( pos + 1 ) ); + } + } + + void addClasses( String string ) + { + StringTokenizer tokens = new StringTokenizer( string, ";" ); + while( tokens.hasMoreTokens() ) + { + addClass( tokens.nextToken() ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Filter.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Filter.java new file mode 100644 index 000000000..2cd26d2aa --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/depend/Filter.java @@ -0,0 +1,14 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.depend; +import java.util.*; + +public interface Filter +{ + boolean accept( Object object ); +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java new file mode 100644 index 000000000..1421fb167 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroMatcher.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.oro.text.regex.MatchResult; +import org.apache.oro.text.regex.Pattern; +import org.apache.oro.text.regex.Perl5Compiler; +import org.apache.oro.text.regex.Perl5Matcher; +import org.apache.tools.ant.BuildException; + +/** + * Implementation of RegexpMatcher for Jakarta-ORO. + * + * @author Stefan Bodewig + * @author Matthew Inger + */ +public class JakartaOroMatcher implements RegexpMatcher +{ + protected final Perl5Compiler compiler = new Perl5Compiler(); + protected final Perl5Matcher matcher = new Perl5Matcher(); + + private String pattern; + + public JakartaOroMatcher() { } + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + */ + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + /** + * Returns a Vector of matched groups found in the argument.

                  + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

                  . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String argument ) + throws BuildException + { + return getGroups( argument, MATCH_DEFAULT ); + } + + /** + * Returns a Vector of matched groups found in the argument.

                  + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

                  . + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String input, int options ) + throws BuildException + { + if( !matches( input, options ) ) + { + return null; + } + Vector v = new Vector(); + MatchResult mr = matcher.getMatch(); + int cnt = mr.groups(); + for( int i = 0; i < cnt; i++ ) + { + v.addElement( mr.group( i ) ); + } + return v; + } + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + */ + public String getPattern() + { + return this.pattern; + } + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String argument ) + throws BuildException + { + return matches( argument, MATCH_DEFAULT ); + } + + /** + * Does the given argument match the pattern? + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String input, int options ) + throws BuildException + { + Pattern p = getCompiledPattern( options ); + return matcher.contains( input, p ); + } + + /** + * Get a compiled representation of the regexp pattern + * + * @param options Description of Parameter + * @return The CompiledPattern value + * @exception BuildException Description of Exception + */ + protected Pattern getCompiledPattern( int options ) + throws BuildException + { + try + { + // compute the compiler options based on the input options first + Pattern p = compiler.compile( pattern, getCompilerOptions( options ) ); + return p; + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + protected int getCompilerOptions( int options ) + { + int cOptions = Perl5Compiler.DEFAULT_MASK; + + if( RegexpUtil.hasFlag( options, MATCH_CASE_INSENSITIVE ) ) + { + cOptions |= Perl5Compiler.CASE_INSENSITIVE_MASK; + } + if( RegexpUtil.hasFlag( options, MATCH_MULTILINE ) ) + { + cOptions |= Perl5Compiler.MULTILINE_MASK; + } + if( RegexpUtil.hasFlag( options, MATCH_SINGLELINE ) ) + { + cOptions |= Perl5Compiler.SINGLELINE_MASK; + } + + return cOptions; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java new file mode 100644 index 000000000..88e8f31f6 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaOroRegexp.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.oro.text.regex.Perl5Substitution; +import org.apache.oro.text.regex.Substitution; +import org.apache.oro.text.regex.Util; +import org.apache.tools.ant.BuildException; + + +/** + * Regular expression implementation using the Jakarta Oro package + * + * @author Matthew Inger + * mattinger@mindless.com + */ +public class JakartaOroRegexp extends JakartaOroMatcher implements Regexp +{ + + public JakartaOroRegexp() + { + super(); + } + + public String substitute( String input, String argument, int options ) + throws BuildException + { + // translate \1 to $1 so that the Perl5Substitution will work + StringBuffer subst = new StringBuffer(); + for( int i = 0; i < argument.length(); i++ ) + { + char c = argument.charAt( i ); + if( c == '\\' ) + { + if( ++i < argument.length() ) + { + c = argument.charAt( i ); + int value = Character.digit( c, 10 ); + if( value > -1 ) + { + subst.append( "$" ).append( value ); + } + else + { + subst.append( c ); + } + } + else + { + // XXX - should throw an exception instead? + subst.append( '\\' ); + } + } + else + { + subst.append( c ); + } + } + + // Do the substitution + Substitution s = + new Perl5Substitution( subst.toString(), + Perl5Substitution.INTERPOLATE_ALL ); + return Util.substitute( matcher, + getCompiledPattern( options ), + s, + input, + getSubsOptions( options ) ); + } + + protected int getSubsOptions( int options ) + { + boolean replaceAll = RegexpUtil.hasFlag( options, REPLACE_ALL ); + int subsOptions = 1; + if( replaceAll ) + { + subsOptions = Util.SUBSTITUTE_ALL; + } + return subsOptions; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java new file mode 100644 index 000000000..a51a8ff3a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpMatcher.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.regexp.RE; +import org.apache.regexp.RESyntaxException; +import org.apache.tools.ant.BuildException; + +/** + * Implementation of RegexpMatcher for Jakarta-Regexp. + * + * @author Stefan Bodewig + * @author Matthew Inger + * mattinger@mindless.com + */ +public class JakartaRegexpMatcher implements RegexpMatcher +{ + + private String pattern; + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + */ + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + /** + * Returns a Vector of matched groups found in the argument.

                  + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

                  . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String argument ) + throws BuildException + { + return getGroups( argument, MATCH_DEFAULT ); + } + + public Vector getGroups( String input, int options ) + throws BuildException + { + RE reg = getCompiledPattern( options ); + if( !matches( input, reg ) ) + { + return null; + } + Vector v = new Vector(); + int cnt = reg.getParenCount(); + for( int i = 0; i < cnt; i++ ) + { + v.addElement( reg.getParen( i ) ); + } + return v; + } + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + */ + public String getPattern() + { + return pattern; + } + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String argument ) + throws BuildException + { + return matches( argument, MATCH_DEFAULT ); + } + + /** + * Does the given argument match the pattern? + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String input, int options ) + throws BuildException + { + return matches( input, getCompiledPattern( options ) ); + } + + protected RE getCompiledPattern( int options ) + throws BuildException + { + int cOptions = getCompilerOptions( options ); + try + { + RE reg = new RE( pattern ); + reg.setMatchFlags( cOptions ); + return reg; + } + catch( RESyntaxException e ) + { + throw new BuildException( e ); + } + } + + protected int getCompilerOptions( int options ) + { + int cOptions = RE.MATCH_NORMAL; + + if( RegexpUtil.hasFlag( options, MATCH_CASE_INSENSITIVE ) ) + cOptions |= RE.MATCH_CASEINDEPENDENT; + if( RegexpUtil.hasFlag( options, MATCH_MULTILINE ) ) + cOptions |= RE.MATCH_MULTILINE; + if( RegexpUtil.hasFlag( options, MATCH_SINGLELINE ) ) + cOptions |= RE.MATCH_SINGLELINE; + + return cOptions; + } + + private boolean matches( String input, RE reg ) + { + return reg.match( input ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java new file mode 100644 index 000000000..3f41e50e5 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/JakartaRegexpRegexp.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.regexp.RE; +import org.apache.tools.ant.BuildException; + +/** + * Regular expression implementation using the Jakarta Regexp package + * + * @author Matthew Inger + * mattinger@mindless.com + */ +public class JakartaRegexpRegexp extends JakartaRegexpMatcher implements Regexp +{ + + public JakartaRegexpRegexp() + { + super(); + } + + public String substitute( String input, String argument, int options ) + throws BuildException + { + Vector v = getGroups( input, options ); + + // replace \1 with the corresponding group + StringBuffer result = new StringBuffer(); + for( int i = 0; i < argument.length(); i++ ) + { + char c = argument.charAt( i ); + if( c == '\\' ) + { + if( ++i < argument.length() ) + { + c = argument.charAt( i ); + int value = Character.digit( c, 10 ); + if( value > -1 ) + { + result.append( ( String )v.elementAt( value ) ); + } + else + { + result.append( c ); + } + } + else + { + // XXX - should throw an exception instead? + result.append( '\\' ); + } + } + else + { + result.append( c ); + } + } + argument = result.toString(); + + RE reg = getCompiledPattern( options ); + int sOptions = getSubsOptions( options ); + return reg.subst( input, argument, sOptions ); + } + + protected int getSubsOptions( int options ) + { + int subsOptions = RE.REPLACE_FIRSTONLY; + if( RegexpUtil.hasFlag( options, REPLACE_ALL ) ) + subsOptions = RE.REPLACE_ALL; + return subsOptions; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java new file mode 100644 index 000000000..7c486420e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpMatcher.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import org.apache.tools.ant.BuildException; + +/** + * Implementation of RegexpMatcher for the built-in regexp matcher of JDK 1.4. + * + * @author Stefan Bodewig + * @author Matthew Inger + * mattinger@mindless.com + */ +public class Jdk14RegexpMatcher implements RegexpMatcher +{ + + private String pattern; + + public Jdk14RegexpMatcher() { } + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + */ + public void setPattern( String pattern ) + { + this.pattern = pattern; + } + + /** + * Returns a Vector of matched groups found in the argument.

                  + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

                  . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String argument ) + throws BuildException + { + return getGroups( argument, MATCH_DEFAULT ); + } + + /** + * Returns a Vector of matched groups found in the argument.

                  + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

                  . + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + public Vector getGroups( String input, int options ) + throws BuildException + { + Pattern p = getCompiledPattern( options ); + Matcher matcher = p.matcher( input ); + if( !matcher.find() ) + { + return null; + } + Vector v = new Vector(); + int cnt = matcher.groupCount(); + for( int i = 0; i <= cnt; i++ ) + { + v.addElement( matcher.group( i ) ); + } + return v; + } + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + */ + public String getPattern() + { + return pattern; + } + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String argument ) + throws BuildException + { + return matches( argument, MATCH_DEFAULT ); + } + + /** + * Does the given argument match the pattern? + * + * @param input Description of Parameter + * @param options Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public boolean matches( String input, int options ) + throws BuildException + { + try + { + Pattern p = getCompiledPattern( options ); + return p.matcher( input ).find(); + } + catch( Exception e ) + { + throw new BuildException( e ); + } + } + + protected Pattern getCompiledPattern( int options ) + throws BuildException + { + int cOptions = getCompilerOptions( options ); + try + { + Pattern p = Pattern.compile( this.pattern, cOptions ); + return p; + } + catch( PatternSyntaxException e ) + { + throw new BuildException( e ); + } + } + + protected int getCompilerOptions( int options ) + { + int cOptions = 0; + + if( RegexpUtil.hasFlag( options, MATCH_CASE_INSENSITIVE ) ) + cOptions |= Pattern.CASE_INSENSITIVE; + if( RegexpUtil.hasFlag( options, MATCH_MULTILINE ) ) + cOptions |= Pattern.MULTILINE; + if( RegexpUtil.hasFlag( options, MATCH_SINGLELINE ) ) + cOptions |= Pattern.DOTALL; + + return cOptions; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java new file mode 100644 index 000000000..23998f0e6 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Jdk14RegexpRegexp.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.tools.ant.BuildException; + + +/** + * Regular expression implementation using the JDK 1.4 regular expression + * package + * + * @author Matthew Inger + * mattinger@mindless.com + */ +public class Jdk14RegexpRegexp extends Jdk14RegexpMatcher implements Regexp +{ + + public Jdk14RegexpRegexp() + { + super(); + } + + public String substitute( String input, String argument, int options ) + throws BuildException + { + // translate \1 to $(1) so that the Matcher will work + StringBuffer subst = new StringBuffer(); + for( int i = 0; i < argument.length(); i++ ) + { + char c = argument.charAt( i ); + if( c == '\\' ) + { + if( ++i < argument.length() ) + { + c = argument.charAt( i ); + int value = Character.digit( c, 10 ); + if( value > -1 ) + { + subst.append( "$" ).append( value ); + } + else + { + subst.append( c ); + } + } + else + { + // XXX - should throw an exception instead? + subst.append( '\\' ); + } + } + else + { + subst.append( c ); + } + } + argument = subst.toString(); + + int sOptions = getSubsOptions( options ); + Pattern p = getCompiledPattern( options ); + StringBuffer sb = new StringBuffer(); + + Matcher m = p.matcher( input ); + if( RegexpUtil.hasFlag( sOptions, REPLACE_ALL ) ) + { + sb.append( m.replaceAll( argument ) ); + } + else + { + boolean res = m.find(); + if( res ) + { + m.appendReplacement( sb, argument ); + m.appendTail( sb ); + } + else + { + sb.append( input ); + } + } + + return sb.toString(); + } + + protected int getSubsOptions( int options ) + { + int subsOptions = REPLACE_FIRST; + if( RegexpUtil.hasFlag( options, REPLACE_ALL ) ) + subsOptions = REPLACE_ALL; + return subsOptions; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Regexp.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Regexp.java new file mode 100644 index 000000000..2acaeda47 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/Regexp.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.tools.ant.BuildException; + +/** + * Interface which represents a regular expression, and the operations that can + * be performed on it. + * + * @author Matthew Inger + */ +public interface Regexp extends RegexpMatcher +{ + + /** + * Replace only the first occurance of the regular expression + */ + int REPLACE_FIRST = 0x00000001; + + /** + * Replace all occurances of the regular expression + */ + int REPLACE_ALL = 0x00000010; + + /** + * Perform a substitution on the regular expression. + * + * @param input The string to substitute on + * @param argument The string which defines the substitution + * @param options The list of options for the match and replace. See the + * MATCH_ and REPLACE_ constants above. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + String substitute( String input, String argument, int options ) + throws BuildException; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpFactory.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpFactory.java new file mode 100644 index 000000000..559bdf958 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpFactory.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Regular expression factory, which will create Regexp objects. The actual + * implementation class depends on the System or Ant Property: ant.regexp.regexpimpl + * . + * + * @author Matthew Inger + * mattinger@mindless.com + * @version $Revision$ + */ +public class RegexpFactory extends RegexpMatcherFactory +{ + public RegexpFactory() { } + + /** + * Create a new regular expression matcher instance. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Regexp newRegexp() + throws BuildException + { + return ( Regexp )newRegexp( null ); + } + + /** + * Create a new regular expression matcher instance. + * + * @param p Project whose ant.regexp.regexpimpl property will be used. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public Regexp newRegexp( Project p ) + throws BuildException + { + String systemDefault = null; + if( p == null ) + { + systemDefault = System.getProperty( "ant.regexp.regexpimpl" ); + } + else + { + systemDefault = ( String )p.getProperties().get( "ant.regexp.regexpimpl" ); + } + + if( systemDefault != null ) + { + return createRegexpInstance( systemDefault ); + // XXX should we silently catch possible exceptions and try to + // load a different implementation? + } + + try + { + return createRegexpInstance( "org.apache.tools.ant.util.regexp.Jdk14RegexpRegexp" ); + } + catch( BuildException be ) + {} + + try + { + return createRegexpInstance( "org.apache.tools.ant.util.regexp.JakartaOroRegexp" ); + } + catch( BuildException be ) + {} + + try + { + return createRegexpInstance( "org.apache.tools.ant.util.regexp.JakartaRegexpRegexp" ); + } + catch( BuildException be ) + {} + + throw new BuildException( "No supported regular expression matcher found" ); + } + + /** + * Wrapper over {@seee RegexpMatcherFactory#createInstance createInstance} + * that ensures that we are dealing with a Regexp implementation. + * + * @param classname Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + * @since 1.3 + */ + protected Regexp createRegexpInstance( String classname ) + throws BuildException + { + + RegexpMatcher m = createInstance( classname ); + if( m instanceof Regexp ) + { + return ( Regexp )m; + } + else + { + throw new BuildException( classname + " doesn't implement the Regexp interface" ); + } + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcher.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcher.java new file mode 100644 index 000000000..26ef487c0 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcher.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import java.util.Vector; +import org.apache.tools.ant.BuildException; + +/** + * Interface describing a regular expression matcher. + * + * @author Stefan Bodewig + * @author Matthew Inger + */ +public interface RegexpMatcher +{ + + /** + * Default Mask (case insensitive, neither multiline nor singleline + * specified). + */ + int MATCH_DEFAULT = 0x00000000; + + /** + * Perform a case insenstive match + */ + int MATCH_CASE_INSENSITIVE = 0x00000100; + + /** + * Treat the input as a multiline input + */ + int MATCH_MULTILINE = 0x00001000; + + /** + * Treat the input as singleline input ('.' matches newline) + */ + int MATCH_SINGLELINE = 0x00010000; + + + /** + * Set the regexp pattern from the String description. + * + * @param pattern The new Pattern value + * @exception BuildException Description of Exception + */ + void setPattern( String pattern ) + throws BuildException; + + /** + * Get a String representation of the regexp pattern + * + * @return The Pattern value + * @exception BuildException Description of Exception + */ + String getPattern() + throws BuildException; + + /** + * Does the given argument match the pattern? + * + * @param argument Description of Parameter + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + boolean matches( String argument ) + throws BuildException; + + /** + * Returns a Vector of matched groups found in the argument.

                  + * + * Group 0 will be the full match, the rest are the parenthesized + * subexpressions

                  . + * + * @param argument Description of Parameter + * @return The Groups value + * @exception BuildException Description of Exception + */ + Vector getGroups( String argument ) + throws BuildException; + + /** + * Does this regular expression match the input, given certain options + * + * @param input The string to check for a match + * @param options The list of options for the match. See the MATCH_ + * constants above. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + boolean matches( String input, int options ) + throws BuildException; + + /** + * Get the match groups from this regular expression. The return type of the + * elements is always String. + * + * @param input The string to check for a match + * @param options The list of options for the match. See the MATCH_ + * constants above. + * @return The Groups value + * @exception BuildException Description of Exception + */ + Vector getGroups( String input, int options ) + throws BuildException; + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java new file mode 100644 index 000000000..ae7b5331a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpMatcherFactory.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Project; + +/** + * Simple Factory Class that produces an implementation of RegexpMatcher based + * on the system property ant.regexp.matcherimpl and the classes + * available.

                  + * + * In a more general framework this class would be abstract and have a static + * newInstance method.

                  + * + * @author Stefan Bodewig + */ +public class RegexpMatcherFactory +{ + + public RegexpMatcherFactory() { } + + /** + * Create a new regular expression instance. + * + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public RegexpMatcher newRegexpMatcher() + throws BuildException + { + return newRegexpMatcher( null ); + } + + /** + * Create a new regular expression instance. + * + * @param p Project whose ant.regexp.regexpimpl property will be used. + * @return Description of the Returned Value + * @exception BuildException Description of Exception + */ + public RegexpMatcher newRegexpMatcher( Project p ) + throws BuildException + { + String systemDefault = null; + if( p == null ) + { + systemDefault = System.getProperty( "ant.regexp.regexpimpl" ); + } + else + { + systemDefault = ( String )p.getProperties().get( "ant.regexp.regexpimpl" ); + } + + if( systemDefault != null ) + { + return createInstance( systemDefault ); + // XXX should we silently catch possible exceptions and try to + // load a different implementation? + } + + try + { + return createInstance( "org.apache.tools.ant.util.regexp.Jdk14RegexpMatcher" ); + } + catch( BuildException be ) + {} + + try + { + return createInstance( "org.apache.tools.ant.util.regexp.JakartaOroMatcher" ); + } + catch( BuildException be ) + {} + + try + { + return createInstance( "org.apache.tools.ant.util.regexp.JakartaRegexpMatcher" ); + } + catch( BuildException be ) + {} + + throw new BuildException( "No supported regular expression matcher found" ); + } + + protected RegexpMatcher createInstance( String className ) + throws BuildException + { + try + { + Class implClass = Class.forName( className ); + return ( RegexpMatcher )implClass.newInstance(); + } + catch( Throwable t ) + { + throw new BuildException( t ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpUtil.java b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpUtil.java new file mode 100644 index 000000000..fb1e3416a --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/ant/util/regexp/RegexpUtil.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.ant.util.regexp; + +/** + * Regular expression utilities class which handles flag operations + * + * @author Matthew Inger + */ +public class RegexpUtil extends Object +{ + public final static boolean hasFlag( int options, int flag ) + { + return ( ( options & flag ) > 0 ); + } + + public final static int removeFlag( int options, int flag ) + { + return ( options & ( 0xFFFFFFFF - flag ) ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/bzip2/BZip2Constants.java b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/BZip2Constants.java new file mode 100644 index 000000000..fa26ce9fc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/BZip2Constants.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; + +/** + * Base class for both the compress and decompress classes. Holds common arrays, + * and static data. + * + * @author Keiron Liddle + */ +public interface BZip2Constants +{ + + int baseBlockSize = 100000; + int MAX_ALPHA_SIZE = 258; + int MAX_CODE_LEN = 23; + int RUNA = 0; + int RUNB = 1; + int N_GROUPS = 6; + int G_SIZE = 50; + int N_ITERS = 4; + int MAX_SELECTORS = ( 2 + ( 900000 / G_SIZE ) ); + int NUM_OVERSHOOT_BYTES = 20; + + int rNums[] = { + 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, + 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, + 733, 859, 335, 708, 621, 574, 73, 654, 730, 472, + 419, 436, 278, 496, 867, 210, 399, 680, 480, 51, + 878, 465, 811, 169, 869, 675, 611, 697, 867, 561, + 862, 687, 507, 283, 482, 129, 807, 591, 733, 623, + 150, 238, 59, 379, 684, 877, 625, 169, 643, 105, + 170, 607, 520, 932, 727, 476, 693, 425, 174, 647, + 73, 122, 335, 530, 442, 853, 695, 249, 445, 515, + 909, 545, 703, 919, 874, 474, 882, 500, 594, 612, + 641, 801, 220, 162, 819, 984, 589, 513, 495, 799, + 161, 604, 958, 533, 221, 400, 386, 867, 600, 782, + 382, 596, 414, 171, 516, 375, 682, 485, 911, 276, + 98, 553, 163, 354, 666, 933, 424, 341, 533, 870, + 227, 730, 475, 186, 263, 647, 537, 686, 600, 224, + 469, 68, 770, 919, 190, 373, 294, 822, 808, 206, + 184, 943, 795, 384, 383, 461, 404, 758, 839, 887, + 715, 67, 618, 276, 204, 918, 873, 777, 604, 560, + 951, 160, 578, 722, 79, 804, 96, 409, 713, 940, + 652, 934, 970, 447, 318, 353, 859, 672, 112, 785, + 645, 863, 803, 350, 139, 93, 354, 99, 820, 908, + 609, 772, 154, 274, 580, 184, 79, 626, 630, 742, + 653, 282, 762, 623, 680, 81, 927, 626, 789, 125, + 411, 521, 938, 300, 821, 78, 343, 175, 128, 250, + 170, 774, 972, 275, 999, 639, 495, 78, 352, 126, + 857, 956, 358, 619, 580, 124, 737, 594, 701, 612, + 669, 112, 134, 694, 363, 992, 809, 743, 168, 974, + 944, 375, 748, 52, 600, 747, 642, 182, 862, 81, + 344, 805, 988, 739, 511, 655, 814, 334, 249, 515, + 897, 955, 664, 981, 649, 113, 974, 459, 893, 228, + 433, 837, 553, 268, 926, 240, 102, 654, 459, 51, + 686, 754, 806, 760, 493, 403, 415, 394, 687, 700, + 946, 670, 656, 610, 738, 392, 760, 799, 887, 653, + 978, 321, 576, 617, 626, 502, 894, 679, 243, 440, + 680, 879, 194, 572, 640, 724, 926, 56, 204, 700, + 707, 151, 457, 449, 797, 195, 791, 558, 945, 679, + 297, 59, 87, 824, 713, 663, 412, 693, 342, 606, + 134, 108, 571, 364, 631, 212, 174, 643, 304, 329, + 343, 97, 430, 751, 497, 314, 983, 374, 822, 928, + 140, 206, 73, 263, 980, 736, 876, 478, 430, 305, + 170, 514, 364, 692, 829, 82, 855, 953, 676, 246, + 369, 970, 294, 750, 807, 827, 150, 790, 288, 923, + 804, 378, 215, 828, 592, 281, 565, 555, 710, 82, + 896, 831, 547, 261, 524, 462, 293, 465, 502, 56, + 661, 821, 976, 991, 658, 869, 905, 758, 745, 193, + 768, 550, 608, 933, 378, 286, 215, 979, 792, 961, + 61, 688, 793, 644, 986, 403, 106, 366, 905, 644, + 372, 567, 466, 434, 645, 210, 389, 550, 919, 135, + 780, 773, 635, 389, 707, 100, 626, 958, 165, 504, + 920, 176, 193, 713, 857, 265, 203, 50, 668, 108, + 645, 990, 626, 197, 510, 357, 358, 850, 858, 364, + 936, 638 + }; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2InputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2InputStream.java new file mode 100644 index 000000000..d614359db --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2InputStream.java @@ -0,0 +1,939 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; +import java.io.*; + +/** + * An input stream that decompresses from the BZip2 format (without the file + * header chars) to be read as any other stream. + * + * @author Keiron Liddle + */ +public class CBZip2InputStream extends InputStream implements BZip2Constants +{ + + private final static int START_BLOCK_STATE = 1; + private final static int RAND_PART_A_STATE = 2; + private final static int RAND_PART_B_STATE = 3; + private final static int RAND_PART_C_STATE = 4; + private final static int NO_RAND_PART_A_STATE = 5; + private final static int NO_RAND_PART_B_STATE = 6; + private final static int NO_RAND_PART_C_STATE = 7; + private CRC mCrc = new CRC(); + + private boolean inUse[] = new boolean[256]; + + private char seqToUnseq[] = new char[256]; + private char unseqToSeq[] = new char[256]; + + private char selector[] = new char[MAX_SELECTORS]; + private char selectorMtf[] = new char[MAX_SELECTORS]; + + /* + * freq table collected to save a pass over the data + * during decompression. + */ + private int unzftab[] = new int[256]; + + private int limit[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int base[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int perm[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + private int minLens[] = new int[N_GROUPS]; + + private boolean streamEnd = false; + + private int currentChar = -1; + + private int currentState = START_BLOCK_STATE; + int rNToGo = 0; + int rTPos = 0; + int i, tPos; + + int i2, count, chPrev, ch2; + int j2; + char z; + + private boolean blockRandomised; + + /* + * always: in the range 0 .. 9. + * The current block size is 100000 * this number. + */ + private int blockSize100k; + private int bsBuff; + private int bsLive; + + private InputStream bsStream; + + private int bytesIn; + private int bytesOut; + private int computedBlockCRC, computedCombinedCRC; + + /* + * index of the last char in the block, so + * the block size == last + 1. + */ + private int last; + private char[] ll8; + private int nInUse; + + /* + * index in zptr[] of original string after sorting. + */ + private int origPtr; + + private int storedBlockCRC, storedCombinedCRC; + + private int[] tt; + + public CBZip2InputStream( InputStream zStream ) + { + ll8 = null; + tt = null; + bsSetStream( zStream ); + initialize(); + initBlock(); + setupBlock(); + } + + private static void badBGLengths() + { + cadvise(); + } + + private static void badBlockHeader() + { + cadvise(); + } + + private static void bitStreamEOF() + { + cadvise(); + } + + private static void blockOverrun() + { + cadvise(); + } + + private static void cadvise() + { + System.out.println( "CRC Error" ); + //throw new CCoruptionError(); + } + + private static void compressedStreamEOF() + { + cadvise(); + } + + private static void crcError() + { + cadvise(); + } + + public int read() + { + if( streamEnd ) + { + return -1; + } + else + { + int retChar = currentChar; + switch ( currentState ) + { + case START_BLOCK_STATE: + break; + case RAND_PART_A_STATE: + break; + case RAND_PART_B_STATE: + setupRandPartB(); + break; + case RAND_PART_C_STATE: + setupRandPartC(); + break; + case NO_RAND_PART_A_STATE: + break; + case NO_RAND_PART_B_STATE: + setupNoRandPartB(); + break; + case NO_RAND_PART_C_STATE: + setupNoRandPartC(); + break; + default: + break; + } + return retChar; + } + } + + private void setDecompressStructureSizes( int newSize100k ) + { + if( !( 0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k + && blockSize100k <= 9 ) ) + { + // throw new IOException("Invalid block size"); + } + + blockSize100k = newSize100k; + + if( newSize100k == 0 ) + return; + + int n = baseBlockSize * newSize100k; + ll8 = new char[n]; + tt = new int[n]; + } + + private void setupBlock() + { + int cftab[] = new int[257]; + char ch; + + cftab[0] = 0; + for( i = 1; i <= 256; i++ ) + cftab[i] = unzftab[i - 1]; + for( i = 1; i <= 256; i++ ) + cftab[i] += cftab[i - 1]; + + for( i = 0; i <= last; i++ ) + { + ch = ( char )ll8[i]; + tt[cftab[ch]] = i; + cftab[ch]++; + } + cftab = null; + + tPos = tt[origPtr]; + + count = 0; + i2 = 0; + ch2 = 256; + /* + * not a char and not EOF + */ + if( blockRandomised ) + { + rNToGo = 0; + rTPos = 0; + setupRandPartA(); + } + else + { + setupNoRandPartA(); + } + } + + private void setupNoRandPartA() + { + if( i2 <= last ) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + i2++; + + currentChar = ch2; + currentState = NO_RAND_PART_B_STATE; + mCrc.updateCRC( ch2 ); + } + else + { + endBlock(); + initBlock(); + setupBlock(); + } + } + + private void setupNoRandPartB() + { + if( ch2 != chPrev ) + { + currentState = NO_RAND_PART_A_STATE; + count = 1; + setupNoRandPartA(); + } + else + { + count++; + if( count >= 4 ) + { + z = ll8[tPos]; + tPos = tt[tPos]; + currentState = NO_RAND_PART_C_STATE; + j2 = 0; + setupNoRandPartC(); + } + else + { + currentState = NO_RAND_PART_A_STATE; + setupNoRandPartA(); + } + } + } + + private void setupNoRandPartC() + { + if( j2 < ( int )z ) + { + currentChar = ch2; + mCrc.updateCRC( ch2 ); + j2++; + } + else + { + currentState = NO_RAND_PART_A_STATE; + i2++; + count = 0; + setupNoRandPartA(); + } + } + + private void setupRandPartA() + { + if( i2 <= last ) + { + chPrev = ch2; + ch2 = ll8[tPos]; + tPos = tt[tPos]; + if( rNToGo == 0 ) + { + rNToGo = rNums[rTPos]; + rTPos++; + if( rTPos == 512 ) + rTPos = 0; + } + rNToGo--; + ch2 ^= ( int )( ( rNToGo == 1 ) ? 1 : 0 ); + i2++; + + currentChar = ch2; + currentState = RAND_PART_B_STATE; + mCrc.updateCRC( ch2 ); + } + else + { + endBlock(); + initBlock(); + setupBlock(); + } + } + + private void setupRandPartB() + { + if( ch2 != chPrev ) + { + currentState = RAND_PART_A_STATE; + count = 1; + setupRandPartA(); + } + else + { + count++; + if( count >= 4 ) + { + z = ll8[tPos]; + tPos = tt[tPos]; + if( rNToGo == 0 ) + { + rNToGo = rNums[rTPos]; + rTPos++; + if( rTPos == 512 ) + rTPos = 0; + } + rNToGo--; + z ^= ( ( rNToGo == 1 ) ? 1 : 0 ); + j2 = 0; + currentState = RAND_PART_C_STATE; + setupRandPartC(); + } + else + { + currentState = RAND_PART_A_STATE; + setupRandPartA(); + } + } + } + + private void setupRandPartC() + { + if( j2 < ( int )z ) + { + currentChar = ch2; + mCrc.updateCRC( ch2 ); + j2++; + } + else + { + currentState = RAND_PART_A_STATE; + i2++; + count = 0; + setupRandPartA(); + } + } + + private void getAndMoveToFrontDecode() + { + char yy[] = new char[256]; + int i; + int j; + int nextSym; + int limitLast; + int EOB; + int groupNo; + int groupPos; + + limitLast = baseBlockSize * blockSize100k; + origPtr = bsGetIntVS( 24 ); + + recvDecodingTables(); + EOB = nInUse + 1; + groupNo = -1; + groupPos = 0; + + /* + * Setting up the unzftab entries here is not strictly + * necessary, but it does save having to do it later + * in a separate pass, and so saves a block's worth of + * cache misses. + */ + for( i = 0; i <= 255; i++ ) + unzftab[i] = 0; + + for( i = 0; i <= 255; i++ ) + yy[i] = ( char )i; + + last = -1; + { + int zt; + int zn; + int zvec; + int zj; + if( groupPos == 0 ) + { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR( zn ); + while( zvec > limit[zt][zn] ) + { + zn++; + { + { + while( bsLive < 1 ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + if( thech == -1 ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + zj = ( bsBuff >> ( bsLive - 1 ) ) & 1; + bsLive--; + } + zvec = ( zvec << 1 ) | zj; + } + nextSym = perm[zt][zvec - base[zt][zn]]; + } + + while( true ) + { + + if( nextSym == EOB ) + break; + + if( nextSym == RUNA || nextSym == RUNB ) + { + char ch; + int s = -1; + int N = 1; + do + { + if( nextSym == RUNA ) + s = s + ( 0 + 1 ) * N; + else if( nextSym == RUNB ) + s = s + ( 1 + 1 ) * N; + N = N * 2; + { + int zt; + int zn; + int zvec; + int zj; + if( groupPos == 0 ) + { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR( zn ); + while( zvec > limit[zt][zn] ) + { + zn++; + { + { + while( bsLive < 1 ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + if( thech == -1 ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + zj = ( bsBuff >> ( bsLive - 1 ) ) & 1; + bsLive--; + } + zvec = ( zvec << 1 ) | zj; + } + ; + nextSym = perm[zt][zvec - base[zt][zn]]; + } + }while ( nextSym == RUNA || nextSym == RUNB ); + + s++; + ch = seqToUnseq[yy[0]]; + unzftab[ch] += s; + + while( s > 0 ) + { + last++; + ll8[last] = ch; + s--; + } + ; + + if( last >= limitLast ) + blockOverrun(); + continue; + } + else + { + char tmp; + last++; + if( last >= limitLast ) + blockOverrun(); + + tmp = yy[nextSym - 1]; + unzftab[seqToUnseq[tmp]]++; + ll8[last] = seqToUnseq[tmp]; + + /* + * This loop is hammered during decompression, + * hence the unrolling. + * for (j = nextSym-1; j > 0; j--) yy[j] = yy[j-1]; + */ + j = nextSym - 1; + for( ; j > 3; j -= 4 ) + { + yy[j] = yy[j - 1]; + yy[j - 1] = yy[j - 2]; + yy[j - 2] = yy[j - 3]; + yy[j - 3] = yy[j - 4]; + } + for( ; j > 0; j-- ) + yy[j] = yy[j - 1]; + + yy[0] = tmp; + { + int zt; + int zn; + int zvec; + int zj; + if( groupPos == 0 ) + { + groupNo++; + groupPos = G_SIZE; + } + groupPos--; + zt = selector[groupNo]; + zn = minLens[zt]; + zvec = bsR( zn ); + while( zvec > limit[zt][zn] ) + { + zn++; + { + { + while( bsLive < 1 ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + zj = ( bsBuff >> ( bsLive - 1 ) ) & 1; + bsLive--; + } + zvec = ( zvec << 1 ) | zj; + } + ; + nextSym = perm[zt][zvec - base[zt][zn]]; + } + continue; + } + } + } + + private void bsFinishedWithStream() + { + bsStream = null; + } + + private int bsGetInt32() + { + return ( int )bsGetint(); + } + + private int bsGetIntVS( int numBits ) + { + return ( int )bsR( numBits ); + } + + private char bsGetUChar() + { + return ( char )bsR( 8 ); + } + + private int bsGetint() + { + int u = 0; + u = ( u << 8 ) | bsR( 8 ); + u = ( u << 8 ) | bsR( 8 ); + u = ( u << 8 ) | bsR( 8 ); + u = ( u << 8 ) | bsR( 8 ); + return u; + } + + private int bsR( int n ) + { + int v; + { + while( bsLive < n ) + { + int zzi; + char thech = 0; + try + { + thech = ( char )bsStream.read(); + } + catch( IOException e ) + { + compressedStreamEOF(); + } + if( thech == -1 ) + { + compressedStreamEOF(); + } + zzi = thech; + bsBuff = ( bsBuff << 8 ) | ( zzi & 0xff ); + bsLive += 8; + } + } + + v = ( bsBuff >> ( bsLive - n ) ) & ( ( 1 << n ) - 1 ); + bsLive -= n; + return v; + } + + private void bsSetStream( InputStream f ) + { + bsStream = f; + bsLive = 0; + bsBuff = 0; + bytesOut = 0; + bytesIn = 0; + } + + private void complete() + { + storedCombinedCRC = bsGetInt32(); + if( storedCombinedCRC != computedCombinedCRC ) + crcError(); + + bsFinishedWithStream(); + streamEnd = true; + } + + private void endBlock() + { + computedBlockCRC = mCrc.getFinalCRC(); + /* + * A bad CRC is considered a fatal error. + */ + if( storedBlockCRC != computedBlockCRC ) + crcError(); + + computedCombinedCRC = ( computedCombinedCRC << 1 ) + | ( computedCombinedCRC >>> 31 ); + computedCombinedCRC ^= computedBlockCRC; + } + + private void hbCreateDecodeTables( int[] limit, int[] base, + int[] perm, char[] length, + int minLen, int maxLen, int alphaSize ) + { + int pp; + int i; + int j; + int vec; + + pp = 0; + for( i = minLen; i <= maxLen; i++ ) + for( j = 0; j < alphaSize; j++ ) + if( length[j] == i ) + { + perm[pp] = j; + pp++; + } + ; + + for( i = 0; i < MAX_CODE_LEN; i++ ) + base[i] = 0; + for( i = 0; i < alphaSize; i++ ) + base[length[i] + 1]++; + + for( i = 1; i < MAX_CODE_LEN; i++ ) + base[i] += base[i - 1]; + + for( i = 0; i < MAX_CODE_LEN; i++ ) + limit[i] = 0; + vec = 0; + + for( i = minLen; i <= maxLen; i++ ) + { + vec += ( base[i + 1] - base[i] ); + limit[i] = vec - 1; + vec <<= 1; + } + for( i = minLen + 1; i <= maxLen; i++ ) + base[i] = ( ( limit[i - 1] + 1 ) << 1 ) - base[i]; + } + + private void initBlock() + { + char magic1; + char magic2; + char magic3; + char magic4; + char magic5; + char magic6; + magic1 = bsGetUChar(); + magic2 = bsGetUChar(); + magic3 = bsGetUChar(); + magic4 = bsGetUChar(); + magic5 = bsGetUChar(); + magic6 = bsGetUChar(); + if( magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45 + && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90 ) + { + complete(); + return; + } + + if( magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59 + || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59 ) + { + badBlockHeader(); + streamEnd = true; + return; + } + + storedBlockCRC = bsGetInt32(); + + if( bsR( 1 ) == 1 ) + blockRandomised = true; + else + blockRandomised = false; + + // currBlockNo++; + getAndMoveToFrontDecode(); + + mCrc.initialiseCRC(); + currentState = START_BLOCK_STATE; + } + + private void initialize() + { + char magic3; + char magic4; + magic3 = bsGetUChar(); + magic4 = bsGetUChar(); + if( magic3 != 'h' || magic4 < '1' || magic4 > '9' ) + { + bsFinishedWithStream(); + streamEnd = true; + return; + } + + setDecompressStructureSizes( magic4 - '0' ); + computedCombinedCRC = 0; + } + + private void makeMaps() + { + int i; + nInUse = 0; + for( i = 0; i < 256; i++ ) + if( inUse[i] ) + { + seqToUnseq[nInUse] = ( char )i; + unseqToSeq[i] = ( char )nInUse; + nInUse++; + } + } + + private void recvDecodingTables() + { + char len[][] = new char[N_GROUPS][MAX_ALPHA_SIZE]; + int i; + int j; + int t; + int nGroups; + int nSelectors; + int alphaSize; + int minLen; + int maxLen; + boolean inUse16[] = new boolean[16]; + + /* + * Receive the mapping table + */ + for( i = 0; i < 16; i++ ) + if( bsR( 1 ) == 1 ) + inUse16[i] = true; + else + inUse16[i] = false; + + for( i = 0; i < 256; i++ ) + inUse[i] = false; + + for( i = 0; i < 16; i++ ) + if( inUse16[i] ) + for( j = 0; j < 16; j++ ) + if( bsR( 1 ) == 1 ) + inUse[i * 16 + j] = true; + + makeMaps(); + alphaSize = nInUse + 2; + + /* + * Now the selectors + */ + nGroups = bsR( 3 ); + nSelectors = bsR( 15 ); + for( i = 0; i < nSelectors; i++ ) + { + j = 0; + while( bsR( 1 ) == 1 ) + j++; + selectorMtf[i] = ( char )j; + } + { + /* + * Undo the MTF values for the selectors. + */ + char pos[] = new char[N_GROUPS]; + char tmp; + char v; + for( v = 0; v < nGroups; v++ ) + pos[v] = v; + + for( i = 0; i < nSelectors; i++ ) + { + v = selectorMtf[i]; + tmp = pos[v]; + while( v > 0 ) + { + pos[v] = pos[v - 1]; + v--; + } + pos[0] = tmp; + selector[i] = tmp; + } + } + + /* + * Now the coding tables + */ + for( t = 0; t < nGroups; t++ ) + { + int curr = bsR( 5 ); + for( i = 0; i < alphaSize; i++ ) + { + while( bsR( 1 ) == 1 ) + { + if( bsR( 1 ) == 0 ) + curr++; + else + curr--; + } + len[t][i] = ( char )curr; + } + } + + /* + * Create the Huffman decoding tables + */ + for( t = 0; t < nGroups; t++ ) + { + minLen = 32; + maxLen = 0; + for( i = 0; i < alphaSize; i++ ) + { + if( len[t][i] > maxLen ) + maxLen = len[t][i]; + if( len[t][i] < minLen ) + minLen = len[t][i]; + } + hbCreateDecodeTables( limit[t], base[t], perm[t], len[t], minLen, + maxLen, alphaSize ); + minLens[t] = minLen; + } + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2OutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2OutputStream.java new file mode 100644 index 000000000..7ee53781c --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CBZip2OutputStream.java @@ -0,0 +1,1807 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; +import java.io.*; + +/** + * An output stream that compresses into the BZip2 format (without the file + * header chars) into another stream. + * + * @author Keiron Liddle TODO: Update to + * BZip2 1.0.1 + */ +public class CBZip2OutputStream extends OutputStream implements BZip2Constants +{ + protected final static int SETMASK = ( 1 << 21 ); + protected final static int CLEARMASK = ( ~SETMASK ); + protected final static int GREATER_ICOST = 15; + protected final static int LESSER_ICOST = 0; + protected final static int SMALL_THRESH = 20; + protected final static int DEPTH_THRESH = 10; + + /* + * If you are ever unlucky/improbable enough + * to get a stack overflow whilst sorting, + * increase the following constant and try + * again. In practice I have never seen the + * stack go above 27 elems, so the following + * limit seems very generous. + */ + protected final static int QSORT_STACK_SIZE = 1000; + CRC mCrc = new CRC(); + + private boolean inUse[] = new boolean[256]; + + private char seqToUnseq[] = new char[256]; + private char unseqToSeq[] = new char[256]; + + private char selector[] = new char[MAX_SELECTORS]; + private char selectorMtf[] = new char[MAX_SELECTORS]; + + private int mtfFreq[] = new int[MAX_ALPHA_SIZE]; + + private int currentChar = -1; + private int runLength = 0; + + boolean closed = false; + + /* + * Knuth's increments seem to work better + * than Incerpi-Sedgewick here. Possibly + * because the number of elems to sort is + * usually small, typically <= 20. + */ + private int incs[] = {1, 4, 13, 40, 121, 364, 1093, 3280, + 9841, 29524, 88573, 265720, + 797161, 2391484}; + + boolean blockRandomised; + + /* + * always: in the range 0 .. 9. + * The current block size is 100000 * this number. + */ + int blockSize100k; + int bsBuff; + int bsLive; + + int bytesIn; + int bytesOut; + + /* + * index of the last char in the block, so + * the block size == last + 1. + */ + int last; + + /* + * index in zptr[] of original string after sorting. + */ + int origPtr; + + private int allowableBlockSize; + + private char block[]; + + private int blockCRC, combinedCRC; + + private OutputStream bsStream; + private boolean firstAttempt; + private int ftab[]; + private int nBlocksRandomised; + private int nInUse; + + private int nMTF; + private int quadrant[]; + private short szptr[]; + private int workDone; + + /* + * Used when sorting. If too many long comparisons + * happen, we stop sorting, randomise the block + * slightly, and try again. + */ + private int workFactor; + private int workLimit; + private int zptr[]; + + public CBZip2OutputStream( OutputStream inStream ) + throws IOException + { + this( inStream, 9 ); + } + + public CBZip2OutputStream( OutputStream inStream, int inBlockSize ) + throws IOException + { + block = null; + quadrant = null; + zptr = null; + ftab = null; + + bsSetStream( inStream ); + + workFactor = 50; + if( inBlockSize > 9 ) + { + inBlockSize = 9; + } + if( inBlockSize < 1 ) + { + inBlockSize = 1; + } + blockSize100k = inBlockSize; + allocateCompressStructures(); + initialize(); + initBlock(); + } + + protected static void hbMakeCodeLengths( char[] len, int[] freq, + int alphaSize, int maxLen ) + { + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int nNodes; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int nHeap; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int n1; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int n2; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int i; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int j; + /* + * Nodes and heap entries run from 1. Entry 0 + * for both the heap and nodes is a sentinel. + */ + int k; + boolean tooLong; + + int heap[] = new int[MAX_ALPHA_SIZE + 2]; + int weight[] = new int[MAX_ALPHA_SIZE * 2]; + int parent[] = new int[MAX_ALPHA_SIZE * 2]; + + for( i = 0; i < alphaSize; i++ ) + weight[i + 1] = ( freq[i] == 0 ? 1 : freq[i] ) << 8; + + while( true ) + { + nNodes = alphaSize; + nHeap = 0; + + heap[0] = 0; + weight[0] = 0; + parent[0] = -2; + + for( i = 1; i <= alphaSize; i++ ) + { + parent[i] = -1; + nHeap++; + heap[nHeap] = i; + { + int zz; + int tmp; + zz = nHeap; + tmp = heap[zz]; + while( weight[tmp] < weight[heap[zz >> 1]] ) + { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + } + if( !( nHeap < ( MAX_ALPHA_SIZE + 2 ) ) ) + panic(); + + while( nHeap > 1 ) + { + n1 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + { + int zz = 0; + int yy = 0; + int tmp = 0; + zz = 1; + tmp = heap[zz]; + while( true ) + { + yy = zz << 1; + if( yy > nHeap ) + break; + if( yy < nHeap && + weight[heap[yy + 1]] < weight[heap[yy]] ) + yy++; + if( weight[tmp] < weight[heap[yy]] ) + break; + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + } + n2 = heap[1]; + heap[1] = heap[nHeap]; + nHeap--; + { + int zz = 0; + int yy = 0; + int tmp = 0; + zz = 1; + tmp = heap[zz]; + while( true ) + { + yy = zz << 1; + if( yy > nHeap ) + break; + if( yy < nHeap && + weight[heap[yy + 1]] < weight[heap[yy]] ) + yy++; + if( weight[tmp] < weight[heap[yy]] ) + break; + heap[zz] = heap[yy]; + zz = yy; + } + heap[zz] = tmp; + } + nNodes++; + parent[n1] = parent[n2] = nNodes; + + weight[nNodes] = ( ( weight[n1] & 0xffffff00 ) + + ( weight[n2] & 0xffffff00 ) ) + | ( 1 + ( ( ( weight[n1] & 0x000000ff ) > + ( weight[n2] & 0x000000ff ) ) ? + ( weight[n1] & 0x000000ff ) : + ( weight[n2] & 0x000000ff ) ) ); + + parent[nNodes] = -1; + nHeap++; + heap[nHeap] = nNodes; + { + int zz = 0; + int tmp = 0; + zz = nHeap; + tmp = heap[zz]; + while( weight[tmp] < weight[heap[zz >> 1]] ) + { + heap[zz] = heap[zz >> 1]; + zz >>= 1; + } + heap[zz] = tmp; + } + } + if( !( nNodes < ( MAX_ALPHA_SIZE * 2 ) ) ) + panic(); + + tooLong = false; + for( i = 1; i <= alphaSize; i++ ) + { + j = 0; + k = i; + while( parent[k] >= 0 ) + { + k = parent[k]; + j++; + } + len[i - 1] = ( char )j; + if( j > maxLen ) + tooLong = true; + } + + if( !tooLong ) + break; + + for( i = 1; i < alphaSize; i++ ) + { + j = weight[i] >> 8; + j = 1 + ( j / 2 ); + weight[i] = j << 8; + } + } + } + + private static void panic() + { + System.out.println( "panic" ); + //throw new CError(); + } + + public void close() + throws IOException + { + if( closed ) + return; + + if( runLength > 0 ) + writeRun(); + currentChar = -1; + endBlock(); + endCompression(); + closed = true; + super.close(); + bsStream.close(); + } + + public void finalize() + throws Throwable + { + close(); + } + + public void flush() + throws IOException + { + super.flush(); + bsStream.flush(); + } + + /** + * modified by Oliver Merkel, 010128 + * + * @param bv Description of Parameter + * @exception IOException Description of Exception + */ + public void write( int bv ) + throws IOException + { + int b = ( 256 + bv ) % 256; + if( currentChar != -1 ) + { + if( currentChar == b ) + { + runLength++; + if( runLength > 254 ) + { + writeRun(); + currentChar = -1; + runLength = 0; + } + } + else + { + writeRun(); + runLength = 1; + currentChar = b; + } + } + else + { + currentChar = b; + runLength++; + } + } + + private void allocateCompressStructures() + { + int n = baseBlockSize * blockSize100k; + block = new char[( n + 1 + NUM_OVERSHOOT_BYTES )]; + quadrant = new int[( n + NUM_OVERSHOOT_BYTES )]; + zptr = new int[n]; + ftab = new int[65537]; + + if( block == null || quadrant == null || zptr == null + || ftab == null ) + { + //int totalDraw = (n + 1 + NUM_OVERSHOOT_BYTES) + (n + NUM_OVERSHOOT_BYTES) + n + 65537; + //compressOutOfMemory ( totalDraw, n ); + } + + /* + * The back end needs a place to store the MTF values + * whilst it calculates the coding tables. We could + * put them in the zptr array. However, these values + * will fit in a short, so we overlay szptr at the + * start of zptr, in the hope of reducing the number + * of cache misses induced by the multiple traversals + * of the MTF values when calculating coding tables. + * Seems to improve compression speed by about 1%. + */ + // szptr = zptr; + + szptr = new short[2 * n]; + } + + private void bsFinishedWithStream() + throws IOException + { + while( bsLive > 0 ) + { + int ch = ( bsBuff >> 24 ); + try + { + bsStream.write( ch );// write 8-bit + } + catch( IOException e ) + { + throw e; + } + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + } + + private void bsPutIntVS( int numBits, int c ) + throws IOException + { + bsW( numBits, c ); + } + + private void bsPutUChar( int c ) + throws IOException + { + bsW( 8, c ); + } + + private void bsPutint( int u ) + throws IOException + { + bsW( 8, ( u >> 24 ) & 0xff ); + bsW( 8, ( u >> 16 ) & 0xff ); + bsW( 8, ( u >> 8 ) & 0xff ); + bsW( 8, u & 0xff ); + } + + private void bsSetStream( OutputStream f ) + { + bsStream = f; + bsLive = 0; + bsBuff = 0; + bytesOut = 0; + bytesIn = 0; + } + + private void bsW( int n, int v ) + throws IOException + { + while( bsLive >= 8 ) + { + int ch = ( bsBuff >> 24 ); + try + { + bsStream.write( ch );// write 8-bit + } + catch( IOException e ) + { + throw e; + } + bsBuff <<= 8; + bsLive -= 8; + bytesOut++; + } + bsBuff |= ( v << ( 32 - bsLive - n ) ); + bsLive += n; + } + + private void doReversibleTransformation() + { + int i; + + workLimit = workFactor * last; + workDone = 0; + blockRandomised = false; + firstAttempt = true; + + mainSort(); + + if( workDone > workLimit && firstAttempt ) + { + randomiseBlock(); + workLimit = workDone = 0; + blockRandomised = true; + firstAttempt = false; + mainSort(); + } + + origPtr = -1; + for( i = 0; i <= last; i++ ) + if( zptr[i] == 0 ) + { + origPtr = i; + break; + } + ; + + if( origPtr == -1 ) + panic(); + } + + private void endBlock() + throws IOException + { + blockCRC = mCrc.getFinalCRC(); + combinedCRC = ( combinedCRC << 1 ) | ( combinedCRC >>> 31 ); + combinedCRC ^= blockCRC; + + /* + * sort the block and establish posn of original string + */ + doReversibleTransformation(); + + /* + * A 6-byte block header, the value chosen arbitrarily + * as 0x314159265359 :-). A 32 bit value does not really + * give a strong enough guarantee that the value will not + * appear by chance in the compressed datastream. Worst-case + * probability of this event, for a 900k block, is about + * 2.0e-3 for 32 bits, 1.0e-5 for 40 bits and 4.0e-8 for 48 bits. + * For a compressed file of size 100Gb -- about 100000 blocks -- + * only a 48-bit marker will do. NB: normal compression/ + * decompression do *not* rely on these statistical properties. + * They are only important when trying to recover blocks from + * damaged files. + */ + bsPutUChar( 0x31 ); + bsPutUChar( 0x41 ); + bsPutUChar( 0x59 ); + bsPutUChar( 0x26 ); + bsPutUChar( 0x53 ); + bsPutUChar( 0x59 ); + + /* + * Now the block's CRC, so it is in a known place. + */ + bsPutint( blockCRC ); + + /* + * Now a single bit indicating randomisation. + */ + if( blockRandomised ) + { + bsW( 1, 1 ); + nBlocksRandomised++; + } + else + { + bsW( 1, 0 ); + } + + /* + * Finally, block's contents proper. + */ + moveToFrontCodeAndSend(); + } + + private void endCompression() + throws IOException + { + /* + * Now another magic 48-bit number, 0x177245385090, to + * indicate the end of the last block. (sqrt(pi), if + * you want to know. I did want to use e, but it contains + * too much repetition -- 27 18 28 18 28 46 -- for me + * to feel statistically comfortable. Call me paranoid.) + */ + bsPutUChar( 0x17 ); + bsPutUChar( 0x72 ); + bsPutUChar( 0x45 ); + bsPutUChar( 0x38 ); + bsPutUChar( 0x50 ); + bsPutUChar( 0x90 ); + + bsPutint( combinedCRC ); + + bsFinishedWithStream(); + } + + private boolean fullGtU( int i1, int i2 ) + { + int k; + char c1; + char c2; + int s1; + int s2; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + i1++; + i2++; + + k = last + 1; + + do + { + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + c1 = block[i1 + 1]; + c2 = block[i2 + 1]; + if( c1 != c2 ) + return ( c1 > c2 ); + s1 = quadrant[i1]; + s2 = quadrant[i2]; + if( s1 != s2 ) + return ( s1 > s2 ); + i1++; + i2++; + + if( i1 > last ) + { + i1 -= last; + i1--; + } + ; + if( i2 > last ) + { + i2 -= last; + i2--; + } + ; + + k -= 4; + workDone++; + }while ( k >= 0 ); + + return false; + } + + private void generateMTFValues() + { + char yy[] = new char[256]; + int i; + int j; + char tmp; + char tmp2; + int zPend; + int wr; + int EOB; + + makeMaps(); + EOB = nInUse + 1; + + for( i = 0; i <= EOB; i++ ) + mtfFreq[i] = 0; + + wr = 0; + zPend = 0; + for( i = 0; i < nInUse; i++ ) + yy[i] = ( char )i; + + for( i = 0; i <= last; i++ ) + { + char ll_i; + + ll_i = unseqToSeq[block[zptr[i]]]; + + j = 0; + tmp = yy[j]; + while( ll_i != tmp ) + { + j++; + tmp2 = tmp; + tmp = yy[j]; + yy[j] = tmp2; + } + ; + yy[0] = tmp; + + if( j == 0 ) + { + zPend++; + } + else + { + if( zPend > 0 ) + { + zPend--; + while( true ) + { + switch ( zPend % 2 ) + { + case 0: + szptr[wr] = ( short )RUNA; + wr++; + mtfFreq[RUNA]++; + break; + case 1: + szptr[wr] = ( short )RUNB; + wr++; + mtfFreq[RUNB]++; + break; + } + ; + if( zPend < 2 ) + break; + zPend = ( zPend - 2 ) / 2; + } + ; + zPend = 0; + } + szptr[wr] = ( short )( j + 1 ); + wr++; + mtfFreq[j + 1]++; + } + } + + if( zPend > 0 ) + { + zPend--; + while( true ) + { + switch ( zPend % 2 ) + { + case 0: + szptr[wr] = ( short )RUNA; + wr++; + mtfFreq[RUNA]++; + break; + case 1: + szptr[wr] = ( short )RUNB; + wr++; + mtfFreq[RUNB]++; + break; + } + if( zPend < 2 ) + break; + zPend = ( zPend - 2 ) / 2; + } + } + + szptr[wr] = ( short )EOB; + wr++; + mtfFreq[EOB]++; + + nMTF = wr; + } + + private void hbAssignCodes( int[] code, char[] length, int minLen, + int maxLen, int alphaSize ) + { + int n; + int vec; + int i; + + vec = 0; + for( n = minLen; n <= maxLen; n++ ) + { + for( i = 0; i < alphaSize; i++ ) + if( length[i] == n ) + { + code[i] = vec; + vec++; + } + ; + vec <<= 1; + } + } + + private void initBlock() + { + // blockNo++; + mCrc.initialiseCRC(); + last = -1; + // ch = 0; + + for( int i = 0; i < 256; i++ ) + inUse[i] = false; + + /* + * 20 is just a paranoia constant + */ + allowableBlockSize = baseBlockSize * blockSize100k - 20; + } + + private void initialize() + throws IOException + { + bytesIn = 0; + bytesOut = 0; + nBlocksRandomised = 0; + + /* + * Write `magic' bytes h indicating file-format == huffmanised, + * followed by a digit indicating blockSize100k. + */ + bsPutUChar( 'h' ); + bsPutUChar( '0' + blockSize100k ); + + combinedCRC = 0; + } + + private void mainSort() + { + int i; + int j; + int ss; + int sb; + int runningOrder[] = new int[256]; + int copy[] = new int[256]; + boolean bigDone[] = new boolean[256]; + int c1; + int c2; + int numQSorted; + + /* + * In the various block-sized structures, live data runs + * from 0 to last+NUM_OVERSHOOT_BYTES inclusive. First, + * set up the overshoot area for block. + */ + // if (verbosity >= 4) fprintf ( stderr, " sort initialise ...\n" ); + for( i = 0; i < NUM_OVERSHOOT_BYTES; i++ ) + block[last + i + 2] = block[( i % ( last + 1 ) ) + 1]; + for( i = 0; i <= last + NUM_OVERSHOOT_BYTES; i++ ) + quadrant[i] = 0; + + block[0] = ( char )( block[last + 1] ); + + if( last < 4000 ) + { + /* + * Use simpleSort(), since the full sorting mechanism + * has quite a large constant overhead. + */ + for( i = 0; i <= last; i++ ) + zptr[i] = i; + firstAttempt = false; + workDone = workLimit = 0; + simpleSort( 0, last, 0 ); + } + else + { + numQSorted = 0; + for( i = 0; i <= 255; i++ ) + bigDone[i] = false; + + for( i = 0; i <= 65536; i++ ) + ftab[i] = 0; + + c1 = block[0]; + for( i = 0; i <= last; i++ ) + { + c2 = block[i + 1]; + ftab[( c1 << 8 ) + c2]++; + c1 = c2; + } + + for( i = 1; i <= 65536; i++ ) + ftab[i] += ftab[i - 1]; + + c1 = block[1]; + for( i = 0; i < last; i++ ) + { + c2 = block[i + 2]; + j = ( c1 << 8 ) + c2; + c1 = c2; + ftab[j]--; + zptr[ftab[j]] = i; + } + + j = ( ( block[last + 1] ) << 8 ) + ( block[1] ); + ftab[j]--; + zptr[ftab[j]] = last; + + /* + * Now ftab contains the first loc of every small bucket. + * Calculate the running order, from smallest to largest + * big bucket. + */ + for( i = 0; i <= 255; i++ ) + runningOrder[i] = i; + { + int vv; + int h = 1; + do + h = 3 * h + 1; +while ( h <= 256 ); + do + { + h = h / 3; + for( i = h; i <= 255; i++ ) + { + vv = runningOrder[i]; + j = i; + while( ( ftab[( ( runningOrder[j - h] ) + 1 ) << 8] + - ftab[( runningOrder[j - h] ) << 8] ) > + ( ftab[( ( vv ) + 1 ) << 8] - ftab[( vv ) << 8] ) ) + { + runningOrder[j] = runningOrder[j - h]; + j = j - h; + if( j <= ( h - 1 ) ) + break; + } + runningOrder[j] = vv; + } + }while ( h != 1 ); + } + + /* + * The main sorting loop. + */ + for( i = 0; i <= 255; i++ ) + { + + /* + * Process big buckets, starting with the least full. + */ + ss = runningOrder[i]; + + /* + * Complete the big bucket [ss] by quicksorting + * any unsorted small buckets [ss, j]. Hopefully + * previous pointer-scanning phases have already + * completed many of the small buckets [ss, j], so + * we don't have to sort them at all. + */ + for( j = 0; j <= 255; j++ ) + { + sb = ( ss << 8 ) + j; + if( !( ( ftab[sb] & SETMASK ) == SETMASK ) ) + { + int lo = ftab[sb] & CLEARMASK; + int hi = ( ftab[sb + 1] & CLEARMASK ) - 1; + if( hi > lo ) + { + qSort3( lo, hi, 2 ); + numQSorted += ( hi - lo + 1 ); + if( workDone > workLimit && firstAttempt ) + return; + } + ftab[sb] |= SETMASK; + } + } + + /* + * The ss big bucket is now done. Record this fact, + * and update the quadrant descriptors. Remember to + * update quadrants in the overshoot area too, if + * necessary. The "if (i < 255)" test merely skips + * this updating for the last bucket processed, since + * updating for the last bucket is pointless. + */ + bigDone[ss] = true; + + if( i < 255 ) + { + int bbStart = ftab[ss << 8] & CLEARMASK; + int bbSize = ( ftab[( ss + 1 ) << 8] & CLEARMASK ) - bbStart; + int shifts = 0; + + while( ( bbSize >> shifts ) > 65534 ) + shifts++; + + for( j = 0; j < bbSize; j++ ) + { + int a2update = zptr[bbStart + j]; + int qVal = ( j >> shifts ); + quadrant[a2update] = qVal; + if( a2update < NUM_OVERSHOOT_BYTES ) + quadrant[a2update + last + 1] = qVal; + } + + if( !( ( ( bbSize - 1 ) >> shifts ) <= 65535 ) ) + panic(); + } + + /* + * Now scan this big bucket so as to synthesise the + * sorted order for small buckets [t, ss] for all t != ss. + */ + for( j = 0; j <= 255; j++ ) + copy[j] = ftab[( j << 8 ) + ss] & CLEARMASK; + + for( j = ftab[ss << 8] & CLEARMASK; + j < ( ftab[( ss + 1 ) << 8] & CLEARMASK ); j++ ) + { + c1 = block[zptr[j]]; + if( !bigDone[c1] ) + { + zptr[copy[c1]] = zptr[j] == 0 ? last : zptr[j] - 1; + copy[c1]++; + } + } + + for( j = 0; j <= 255; j++ ) + ftab[( j << 8 ) + ss] |= SETMASK; + } + } + } + + private void makeMaps() + { + int i; + nInUse = 0; + for( i = 0; i < 256; i++ ) + if( inUse[i] ) + { + seqToUnseq[nInUse] = ( char )i; + unseqToSeq[i] = ( char )nInUse; + nInUse++; + } + } + + private char med3( char a, char b, char c ) + { + char t; + if( a > b ) + { + t = a; + a = b; + b = t; + } + if( b > c ) + { + t = b; + b = c; + c = t; + } + if( a > b ) + b = a; + return b; + } + + private void moveToFrontCodeAndSend() + throws IOException + { + bsPutIntVS( 24, origPtr ); + generateMTFValues(); + sendMTFValues(); + } + + private void qSort3( int loSt, int hiSt, int dSt ) + { + int unLo; + int unHi; + int ltLo; + int gtHi; + int med; + int n; + int m; + int sp; + int lo; + int hi; + int d; + StackElem[] stack = new StackElem[QSORT_STACK_SIZE]; + for( int count = 0; count < QSORT_STACK_SIZE; count++ ) + stack[count] = new StackElem(); + + sp = 0; + + stack[sp].ll = loSt; + stack[sp].hh = hiSt; + stack[sp].dd = dSt; + sp++; + + while( sp > 0 ) + { + if( sp >= QSORT_STACK_SIZE ) + panic(); + + sp--; + lo = stack[sp].ll; + hi = stack[sp].hh; + d = stack[sp].dd; + + if( hi - lo < SMALL_THRESH || d > DEPTH_THRESH ) + { + simpleSort( lo, hi, d ); + if( workDone > workLimit && firstAttempt ) + return; + continue; + } + + med = med3( block[zptr[lo] + d + 1], + block[zptr[hi] + d + 1], + block[zptr[( lo + hi ) >> 1] + d + 1] ); + + unLo = ltLo = lo; + unHi = gtHi = hi; + + while( true ) + { + while( true ) + { + if( unLo > unHi ) + break; + n = ( ( int )block[zptr[unLo] + d + 1] ) - med; + if( n == 0 ) + { + int temp = 0; + temp = zptr[unLo]; + zptr[unLo] = zptr[ltLo]; + zptr[ltLo] = temp; + ltLo++; + unLo++; + continue; + } + ; + if( n > 0 ) + break; + unLo++; + } + while( true ) + { + if( unLo > unHi ) + break; + n = ( ( int )block[zptr[unHi] + d + 1] ) - med; + if( n == 0 ) + { + int temp = 0; + temp = zptr[unHi]; + zptr[unHi] = zptr[gtHi]; + zptr[gtHi] = temp; + gtHi--; + unHi--; + continue; + } + ; + if( n < 0 ) + break; + unHi--; + } + if( unLo > unHi ) + break; + int temp = 0; + temp = zptr[unLo]; + zptr[unLo] = zptr[unHi]; + zptr[unHi] = temp; + unLo++; + unHi--; + } + + if( gtHi < ltLo ) + { + stack[sp].ll = lo; + stack[sp].hh = hi; + stack[sp].dd = d + 1; + sp++; + continue; + } + + n = ( ( ltLo - lo ) < ( unLo - ltLo ) ) ? ( ltLo - lo ) : ( unLo - ltLo ); + vswap( lo, unLo - n, n ); + m = ( ( hi - gtHi ) < ( gtHi - unHi ) ) ? ( hi - gtHi ) : ( gtHi - unHi ); + vswap( unLo, hi - m + 1, m ); + + n = lo + unLo - ltLo - 1; + m = hi - ( gtHi - unHi ) + 1; + + stack[sp].ll = lo; + stack[sp].hh = n; + stack[sp].dd = d; + sp++; + + stack[sp].ll = n + 1; + stack[sp].hh = m - 1; + stack[sp].dd = d + 1; + sp++; + + stack[sp].ll = m; + stack[sp].hh = hi; + stack[sp].dd = d; + sp++; + } + } + + private void randomiseBlock() + { + int i; + int rNToGo = 0; + int rTPos = 0; + for( i = 0; i < 256; i++ ) + inUse[i] = false; + + for( i = 0; i <= last; i++ ) + { + if( rNToGo == 0 ) + { + rNToGo = ( char )rNums[rTPos]; + rTPos++; + if( rTPos == 512 ) + rTPos = 0; + } + rNToGo--; + block[i + 1] ^= ( ( rNToGo == 1 ) ? 1 : 0 ); + // handle 16 bit signed numbers + block[i + 1] &= 0xFF; + + inUse[block[i + 1]] = true; + } + } + + private void sendMTFValues() + throws IOException + { + char len[][] = new char[N_GROUPS][MAX_ALPHA_SIZE]; + + int v; + + int t; + + int i; + + int j; + + int gs; + + int ge; + + int totc; + + int bt; + + int bc; + + int iter; + int nSelectors = 0; + int alphaSize; + int minLen; + int maxLen; + int selCtr; + int nGroups; + int nBytes; + + alphaSize = nInUse + 2; + for( t = 0; t < N_GROUPS; t++ ) + for( v = 0; v < alphaSize; v++ ) + len[t][v] = ( char )GREATER_ICOST; + + /* + * Decide how many coding tables to use + */ + if( nMTF <= 0 ) + panic(); + + if( nMTF < 200 ) + nGroups = 2; + else if( nMTF < 600 ) + nGroups = 3; + else if( nMTF < 1200 ) + nGroups = 4; + else if( nMTF < 2400 ) + nGroups = 5; + else + nGroups = 6; + { + /* + * Generate an initial set of coding tables + */ + int nPart; + int remF; + int tFreq; + int aFreq; + + nPart = nGroups; + remF = nMTF; + gs = 0; + while( nPart > 0 ) + { + tFreq = remF / nPart; + ge = gs - 1; + aFreq = 0; + while( aFreq < tFreq && ge < alphaSize - 1 ) + { + ge++; + aFreq += mtfFreq[ge]; + } + + if( ge > gs && nPart != nGroups && nPart != 1 + && ( ( nGroups - nPart ) % 2 == 1 ) ) + { + aFreq -= mtfFreq[ge]; + ge--; + } + + for( v = 0; v < alphaSize; v++ ) + if( v >= gs && v <= ge ) + len[nPart - 1][v] = ( char )LESSER_ICOST; + else + len[nPart - 1][v] = ( char )GREATER_ICOST; + + nPart--; + gs = ge + 1; + remF -= aFreq; + } + } + + int rfreq[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + int fave[] = new int[N_GROUPS]; + short cost[] = new short[N_GROUPS]; + /* + * Iterate up to N_ITERS times to improve the tables. + */ + for( iter = 0; iter < N_ITERS; iter++ ) + { + for( t = 0; t < nGroups; t++ ) + fave[t] = 0; + + for( t = 0; t < nGroups; t++ ) + for( v = 0; v < alphaSize; v++ ) + rfreq[t][v] = 0; + + nSelectors = 0; + totc = 0; + gs = 0; + while( true ) + { + + /* + * Set group start & end marks. + */ + if( gs >= nMTF ) + break; + ge = gs + G_SIZE - 1; + if( ge >= nMTF ) + ge = nMTF - 1; + + /* + * Calculate the cost of this group as coded + * by each of the coding tables. + */ + for( t = 0; t < nGroups; t++ ) + cost[t] = 0; + + if( nGroups == 6 ) + { + short cost0; + short cost1; + short cost2; + short cost3; + short cost4; + short cost5; + cost0 = cost1 = cost2 = cost3 = cost4 = cost5 = 0; + for( i = gs; i <= ge; i++ ) + { + short icv = szptr[i]; + cost0 += len[0][icv]; + cost1 += len[1][icv]; + cost2 += len[2][icv]; + cost3 += len[3][icv]; + cost4 += len[4][icv]; + cost5 += len[5][icv]; + } + cost[0] = cost0; + cost[1] = cost1; + cost[2] = cost2; + cost[3] = cost3; + cost[4] = cost4; + cost[5] = cost5; + } + else + { + for( i = gs; i <= ge; i++ ) + { + short icv = szptr[i]; + for( t = 0; t < nGroups; t++ ) + cost[t] += len[t][icv]; + } + } + + /* + * Find the coding table which is best for this group, + * and record its identity in the selector table. + */ + bc = 999999999; + bt = -1; + for( t = 0; t < nGroups; t++ ) + if( cost[t] < bc ) + { + bc = cost[t]; + bt = t; + } + ; + totc += bc; + fave[bt]++; + selector[nSelectors] = ( char )bt; + nSelectors++; + + /* + * Increment the symbol frequencies for the selected table. + */ + for( i = gs; i <= ge; i++ ) + rfreq[bt][szptr[i]]++; + + gs = ge + 1; + } + + /* + * Recompute the tables based on the accumulated frequencies. + */ + for( t = 0; t < nGroups; t++ ) + hbMakeCodeLengths( len[t], rfreq[t], alphaSize, 20 ); + } + + rfreq = null; + fave = null; + cost = null; + + if( !( nGroups < 8 ) ) + panic(); + if( !( nSelectors < 32768 && nSelectors <= ( 2 + ( 900000 / G_SIZE ) ) ) ) + panic(); + { + /* + * Compute MTF values for the selectors. + */ + char pos[] = new char[N_GROUPS]; + char ll_i; + char tmp2; + char tmp; + for( i = 0; i < nGroups; i++ ) + pos[i] = ( char )i; + for( i = 0; i < nSelectors; i++ ) + { + ll_i = selector[i]; + j = 0; + tmp = pos[j]; + while( ll_i != tmp ) + { + j++; + tmp2 = tmp; + tmp = pos[j]; + pos[j] = tmp2; + } + pos[0] = tmp; + selectorMtf[i] = ( char )j; + } + } + + int code[][] = new int[N_GROUPS][MAX_ALPHA_SIZE]; + + /* + * Assign actual codes for the tables. + */ + for( t = 0; t < nGroups; t++ ) + { + minLen = 32; + maxLen = 0; + for( i = 0; i < alphaSize; i++ ) + { + if( len[t][i] > maxLen ) + maxLen = len[t][i]; + if( len[t][i] < minLen ) + minLen = len[t][i]; + } + if( maxLen > 20 ) + panic(); + if( minLen < 1 ) + panic(); + hbAssignCodes( code[t], len[t], minLen, maxLen, alphaSize ); + } + { + /* + * Transmit the mapping table. + */ + boolean inUse16[] = new boolean[16]; + for( i = 0; i < 16; i++ ) + { + inUse16[i] = false; + for( j = 0; j < 16; j++ ) + if( inUse[i * 16 + j] ) + inUse16[i] = true; + } + + nBytes = bytesOut; + for( i = 0; i < 16; i++ ) + if( inUse16[i] ) + bsW( 1, 1 ); + else + bsW( 1, 0 ); + + for( i = 0; i < 16; i++ ) + if( inUse16[i] ) + for( j = 0; j < 16; j++ ) + if( inUse[i * 16 + j] ) + bsW( 1, 1 ); + else + bsW( 1, 0 ); + + } + + /* + * Now the selectors. + */ + nBytes = bytesOut; + bsW( 3, nGroups ); + bsW( 15, nSelectors ); + for( i = 0; i < nSelectors; i++ ) + { + for( j = 0; j < selectorMtf[i]; j++ ) + bsW( 1, 1 ); + bsW( 1, 0 ); + } + + /* + * Now the coding tables. + */ + nBytes = bytesOut; + + for( t = 0; t < nGroups; t++ ) + { + int curr = len[t][0]; + bsW( 5, curr ); + for( i = 0; i < alphaSize; i++ ) + { + while( curr < len[t][i] ) + { + bsW( 2, 2 ); + curr++; + /* + * 10 + */ + } + while( curr > len[t][i] ) + { + bsW( 2, 3 ); + curr--; + /* + * 11 + */ + } + bsW( 1, 0 ); + } + } + + /* + * And finally, the block data proper + */ + nBytes = bytesOut; + selCtr = 0; + gs = 0; + while( true ) + { + if( gs >= nMTF ) + break; + ge = gs + G_SIZE - 1; + if( ge >= nMTF ) + ge = nMTF - 1; + for( i = gs; i <= ge; i++ ) + { + bsW( len[selector[selCtr]][szptr[i]], + code[selector[selCtr]][szptr[i]] ); + } + + gs = ge + 1; + selCtr++; + } + if( !( selCtr == nSelectors ) ) + panic(); + } + + private void simpleSort( int lo, int hi, int d ) + { + int i; + int j; + int h; + int bigN; + int hp; + int v; + + bigN = hi - lo + 1; + if( bigN < 2 ) + return; + + hp = 0; + while( incs[hp] < bigN ) + hp++; + hp--; + + for( ; hp >= 0; hp-- ) + { + h = incs[hp]; + + i = lo + h; + while( true ) + { + /* + * copy 1 + */ + if( i > hi ) + break; + v = zptr[i]; + j = i; + while( fullGtU( zptr[j - h] + d, v + d ) ) + { + zptr[j] = zptr[j - h]; + j = j - h; + if( j <= ( lo + h - 1 ) ) + break; + } + zptr[j] = v; + i++; + + /* + * copy 2 + */ + if( i > hi ) + break; + v = zptr[i]; + j = i; + while( fullGtU( zptr[j - h] + d, v + d ) ) + { + zptr[j] = zptr[j - h]; + j = j - h; + if( j <= ( lo + h - 1 ) ) + break; + } + zptr[j] = v; + i++; + + /* + * copy 3 + */ + if( i > hi ) + break; + v = zptr[i]; + j = i; + while( fullGtU( zptr[j - h] + d, v + d ) ) + { + zptr[j] = zptr[j - h]; + j = j - h; + if( j <= ( lo + h - 1 ) ) + break; + } + zptr[j] = v; + i++; + + if( workDone > workLimit && firstAttempt ) + return; + } + } + } + + private void vswap( int p1, int p2, int n ) + { + int temp = 0; + while( n > 0 ) + { + temp = zptr[p1]; + zptr[p1] = zptr[p2]; + zptr[p2] = temp; + p1++; + p2++; + n--; + } + } + + private void writeRun() + throws IOException + { + if( last < allowableBlockSize ) + { + inUse[currentChar] = true; + for( int i = 0; i < runLength; i++ ) + { + mCrc.updateCRC( ( char )currentChar ); + } + switch ( runLength ) + { + case 1: + last++; + block[last + 1] = ( char )currentChar; + break; + case 2: + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + break; + case 3: + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + break; + default: + inUse[runLength - 4] = true; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )currentChar; + last++; + block[last + 1] = ( char )( runLength - 4 ); + break; + } + } + else + { + endBlock(); + initBlock(); + writeRun(); + } + } + + private class StackElem + { + int dd; + int hh; + int ll; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CRC.java b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CRC.java new file mode 100644 index 000000000..f12b482a6 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/bzip2/CRC.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.bzip2; + +/** + * A simple class the hold and calculate the CRC for sanity checking of the + * data. + * + * @author Keiron Liddle + */ +class CRC +{ + public static int crc32Table[] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, + 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005, + 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, + 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, + 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd, + 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, + 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, + 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, + 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, + 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, + 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, + 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, + 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, + 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, + 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692, + 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, + 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, + 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a, + 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, + 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, + 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b, + 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, + 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, + 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, + 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, + 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, + 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, + 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, + 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, + 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, + 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, + 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, + 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, + 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c, + 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 + }; + + int globalCrc; + + public CRC() + { + initialiseCRC(); + } + + void setGlobalCRC( int newCrc ) + { + globalCrc = newCrc; + } + + int getFinalCRC() + { + return ~globalCrc; + } + + int getGlobalCRC() + { + return globalCrc; + } + + void initialiseCRC() + { + globalCrc = 0xffffffff; + } + + void updateCRC( int inCh ) + { + int temp = ( globalCrc >> 24 ) ^ inCh; + if( temp < 0 ) + temp = 256 + temp; + globalCrc = ( globalCrc << 8 ) ^ CRC.crc32Table[temp]; + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/mail/MailMessage.java b/proposal/myrmidon/src/todo/org/apache/tools/mail/MailMessage.java new file mode 100644 index 000000000..d8520d6c6 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/mail/MailMessage.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.mail; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.InetAddress; +import java.net.Socket; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + +/** + * A class to help send SMTP email. This class is an improvement on the + * sun.net.smtp.SmtpClient class found in the JDK. This version has extra + * functionality, and can be used with JVMs that did not extend from the JDK. + * It's not as robust as the JavaMail Standard Extension classes, but it's + * easier to use and easier to install, and has an Open Source license.

                  + * + * It can be used like this:

                  + * String mailhost = "localhost";  // or another mail host
                  + * String from = "Mail Message Servlet <MailMessage@server.com>";
                  + * String to = "to@you.com";
                  + * String cc1 = "cc1@you.com";
                  + * String cc2 = "cc2@you.com";
                  + * String bcc = "bcc@you.com";
                  + *  
                  + * MailMessage msg = new MailMessage(mailhost);
                  + * msg.setPort(25);
                  + * msg.from(from);
                  + * msg.to(to);
                  + * msg.cc(cc1);
                  + * msg.cc(cc2);
                  + * msg.bcc(bcc);
                  + * msg.setSubject("Test subject");
                  + * PrintStream out = msg.getPrintStream();
                  + *  
                  + * Enumeration enum = req.getParameterNames();
                  + * while (enum.hasMoreElements()) {
                  + *   String name = (String)enum.nextElement();
                  + *   String value = req.getParameter(name);
                  + *   out.println(name + " = " + value);
                  + * }
                  + *  
                  + * msg.sendAndClose();
                  + * 

                  + * + * Be sure to set the from address, then set the recepient addresses, then set + * the subject and other headers, then get the PrintStream, then write the + * message, and finally send and close. The class does minimal error checking + * internally; it counts on the mail host to complain if there's any + * malformatted input or out of order execution.

                  + * + * An attachment mechanism based on RFC 1521 could be implemented on top of this + * class. In the meanwhile, JavaMail is the best solution for sending email with + * attachments.

                  + * + * Still to do: + *

                    + *
                  • Figure out how to close the connection in case of error + *
                  + * + * + * @author Jason Hunter + * @version 1.1, 2000/03/19, added angle brackets to address, helps some servers + * version 1.0, 1999/12/29 + */ +public class MailMessage +{ + + /** + * default port for SMTP: 25 + */ + public final static int DEFAULT_PORT = 25; + + /** + * host port for the mail server + */ + private int port = DEFAULT_PORT; + + /** + * list of email addresses to cc to + */ + private Vector cc; + + /** + * sender email address + */ + private String from; + + /** + * headers to send in the mail + */ + private Hashtable headers; + + /** + * host name for the mail server + */ + private String host; + + private SmtpResponseReader in; + + private MailPrintStream out; + + private Socket socket; + + /** + * list of email addresses to send to + */ + private Vector to; + + /** + * Constructs a new MailMessage to send an email. Use localhost as the mail + * server. + * + * @exception IOException if there's any problem contacting the mail server + */ + public MailMessage() + throws IOException + { + this( "localhost" ); + } + + /** + * Constructs a new MailMessage to send an email. Use the given host as the + * mail server. + * + * @param host the mail server to use + * @exception IOException if there's any problem contacting the mail server + */ + public MailMessage( String host ) + throws IOException + { + this.host = host; + to = new Vector(); + cc = new Vector(); + headers = new Hashtable(); + setHeader( "X-Mailer", "org.apache.tools.mail.MailMessage (jakarta.apache.org)" ); + connect(); + sendHelo(); + } + + // Make a limited attempt to extract a sanitized email address + // Prefer text in , ignore anything in (parentheses) + static String sanitizeAddress( String s ) + { + int paramDepth = 0; + int start = 0; + int end = 0; + int len = s.length(); + + for( int i = 0; i < len; i++ ) + { + char c = s.charAt( i ); + if( c == '(' ) + { + paramDepth++; + if( start == 0 ) + { + end = i;// support "address (name)" + } + } + else if( c == ')' ) + { + paramDepth--; + if( end == 0 ) + { + start = i + 1;// support "(name) address" + } + } + else if( paramDepth == 0 && c == '<' ) + { + start = i + 1; + } + else if( paramDepth == 0 && c == '>' ) + { + end = i; + } + } + + if( end == 0 ) + { + end = len; + } + + return s.substring( start, end ); + } + + /** + * Sets the named header to the given value. RFC 822 provides the rules for + * what text may constitute a header name and value. + * + * @param name The new Header value + * @param value The new Header value + */ + public void setHeader( String name, String value ) + { + // Blindly trust the user doesn't set any invalid headers + headers.put( name, value ); + } + + /** + * Set the port to connect to the SMTP host. + * + * @param port the port to use for connection. + * @see #DEFAULT_PORT + */ + public void setPort( int port ) + { + this.port = port; + } + + /** + * Sets the subject of the mail message. Actually sets the "Subject" header. + * + * @param subj The new Subject value + */ + public void setSubject( String subj ) + { + headers.put( "Subject", subj ); + } + + /** + * Returns a PrintStream that can be used to write the body of the message. + * A stream is used since email bodies are byte-oriented. A writer could be + * wrapped on top if necessary for internationalization. + * + * @return The PrintStream value + * @exception IOException if there's any problem reported by the mail server + */ + public PrintStream getPrintStream() + throws IOException + { + setFromHeader(); + setToHeader(); + setCcHeader(); + sendData(); + flushHeaders(); + return out; + } + + /** + * Sets the bcc address. Does NOT set any header since it's a *blind* copy. + * This method may be called multiple times. + * + * @param bcc Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void bcc( String bcc ) + throws IOException + { + sendRcpt( bcc ); + // No need to keep track of Bcc'd addresses + } + + /** + * Sets the cc address. Also sets the "Cc" header. This method may be called + * multiple times. + * + * @param cc Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void cc( String cc ) + throws IOException + { + sendRcpt( cc ); + this.cc.addElement( cc ); + } + + /** + * Sets the from address. Also sets the "From" header. This method should be + * called only once. + * + * @param from Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void from( String from ) + throws IOException + { + sendFrom( from ); + this.from = from; + } + + /** + * Sends the message and closes the connection to the server. The + * MailMessage object cannot be reused. + * + * @exception IOException if there's any problem reported by the mail server + */ + public void sendAndClose() + throws IOException + { + sendDot(); + sendQuit(); + disconnect(); + } + + /** + * Sets the to address. Also sets the "To" header. This method may be called + * multiple times. + * + * @param to Description of Parameter + * @exception IOException if there's any problem reported by the mail server + */ + public void to( String to ) + throws IOException + { + sendRcpt( to ); + this.to.addElement( to ); + } + + void setCcHeader() + { + setHeader( "Cc", vectorToList( cc ) ); + } + + void setFromHeader() + { + setHeader( "From", from ); + } + + void setToHeader() + { + setHeader( "To", vectorToList( to ) ); + } + + void getReady() + throws IOException + { + String response = in.getResponse(); + int[] ok = {220}; + if( !isResponseOK( response, ok ) ) + { + throw new IOException( + "Didn't get introduction from server: " + response ); + } + } + + boolean isResponseOK( String response, int[] ok ) + { + // Check that the response is one of the valid codes + for( int i = 0; i < ok.length; i++ ) + { + if( response.startsWith( "" + ok[i] ) ) + { + return true; + } + } + return false; + } + + // * * * * * Raw protocol methods below here * * * * * + + void connect() + throws IOException + { + socket = new Socket( host, port ); + out = new MailPrintStream( + new BufferedOutputStream( + socket.getOutputStream() ) ); + in = new SmtpResponseReader( socket.getInputStream() ); + getReady(); + } + + void disconnect() + throws IOException + { + if( out != null ) + out.close(); + if( in != null ) + in.close(); + if( socket != null ) + socket.close(); + } + + void flushHeaders() + throws IOException + { + // XXX Should I care about order here? + Enumeration e = headers.keys(); + while( e.hasMoreElements() ) + { + String name = ( String )e.nextElement(); + String value = ( String )headers.get( name ); + out.println( name + ": " + value ); + } + out.println(); + out.flush(); + } + + void send( String msg, int[] ok ) + throws IOException + { + out.rawPrint( msg + "\r\n" );// raw supports . + //System.out.println("S: " + msg); + String response = in.getResponse(); + //System.out.println("R: " + response); + if( !isResponseOK( response, ok ) ) + { + throw new IOException( + "Unexpected reply to command: " + msg + ": " + response ); + } + } + + void sendData() + throws IOException + { + int[] ok = {354}; + send( "DATA", ok ); + } + + void sendDot() + throws IOException + { + int[] ok = {250}; + send( "\r\n.", ok );// make sure dot is on new line + } + + void sendFrom( String from ) + throws IOException + { + int[] ok = {250}; + send( "MAIL FROM: " + "<" + sanitizeAddress( from ) + ">", ok ); + } + + void sendHelo() + throws IOException + { + String local = InetAddress.getLocalHost().getHostName(); + int[] ok = {250}; + send( "HELO " + local, ok ); + } + + void sendQuit() + throws IOException + { + int[] ok = {221}; + send( "QUIT", ok ); + } + + void sendRcpt( String rcpt ) + throws IOException + { + int[] ok = {250, 251}; + send( "RCPT TO: " + "<" + sanitizeAddress( rcpt ) + ">", ok ); + } + + String vectorToList( Vector v ) + { + StringBuffer buf = new StringBuffer(); + Enumeration e = v.elements(); + while( e.hasMoreElements() ) + { + buf.append( e.nextElement() ); + if( e.hasMoreElements() ) + { + buf.append( ", " ); + } + } + return buf.toString(); + } +} + +// This PrintStream subclass makes sure that . becomes .. +// per RFC 821. It also ensures that new lines are always \r\n. +// +class MailPrintStream extends PrintStream +{ + + int lastChar; + + public MailPrintStream( OutputStream out ) + { + super( out, true );// deprecated, but email is byte-oriented + } + + // Mac does \n\r, but that's tough to distinguish from Windows \r\n\r\n. + // Don't tackle that problem right now. + public void write( int b ) + { + if( b == '\n' && lastChar != '\r' ) + { + rawWrite( '\r' );// ensure always \r\n + rawWrite( b ); + } + else if( b == '.' && lastChar == '\n' ) + { + rawWrite( '.' );// add extra dot + rawWrite( b ); + } + else + { + rawWrite( b ); + } + lastChar = b; + } + + public void write( byte buf[], int off, int len ) + { + for( int i = 0; i < len; i++ ) + { + write( buf[off + i] ); + } + } + + void rawPrint( String s ) + { + int len = s.length(); + for( int i = 0; i < len; i++ ) + { + rawWrite( s.charAt( i ) ); + } + } + + void rawWrite( int b ) + { + super.write( b ); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/mail/SmtpResponseReader.java b/proposal/myrmidon/src/todo/org/apache/tools/mail/SmtpResponseReader.java new file mode 100644 index 000000000..73f935bfc --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/mail/SmtpResponseReader.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.mail; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * A wrapper around the raw input from the SMTP server that assembles multi line + * responses into a single String.

                  + * + * The same rules used here would apply to FTP and other Telnet based protocols + * as well.

                  + * + * @author Stefan Bodewig + */ +public class SmtpResponseReader +{ + + protected BufferedReader reader = null; + private StringBuffer result = new StringBuffer(); + + /** + * Wrap this input stream. + * + * @param in Description of Parameter + */ + public SmtpResponseReader( InputStream in ) + { + reader = new BufferedReader( new InputStreamReader( in ) ); + } + + /** + * Read until the server indicates that the response is complete. + * + * @return Responsecode (3 digits) + Blank + Text from all response line + * concatenated (with blanks replacing the \r\n sequences). + * @exception IOException Description of Exception + */ + public String getResponse() + throws IOException + { + result.setLength( 0 ); + String line = reader.readLine(); + if( line != null && line.length() >= 3 ) + { + result.append( line.substring( 0, 3 ) ); + result.append( " " ); + } + + while( line != null ) + { + append( line ); + if( !hasMoreLines( line ) ) + { + break; + } + line = reader.readLine(); + } + return result.toString().trim(); + } + + /** + * Closes the underlying stream. + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + reader.close(); + } + + /** + * Should we expect more input? + * + * @param line Description of Parameter + * @return Description of the Returned Value + */ + protected boolean hasMoreLines( String line ) + { + return line.length() > 3 && line.charAt( 3 ) == '-'; + } + + /** + * Append the text from this line of the resonse. + * + * @param line Description of Parameter + */ + private void append( String line ) + { + if( line.length() > 4 ) + { + result.append( line.substring( 4 ) ); + result.append( " " ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/tar/TarBuffer.java b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarBuffer.java new file mode 100644 index 000000000..a580a3377 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarBuffer.java @@ -0,0 +1,475 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The TarBuffer class implements the tar archive concept of a buffered input + * stream. This concept goes back to the days of blocked tape drives and special + * io devices. In the Java universe, the only real function that this class + * performs is to ensure that files have the correct "block" size, or other tars + * will complain.

                  + * + * You should never have a need to access this class directly. TarBuffers are + * created by Tar IO Streams. + * + * @author Timothy Gerard Endres time@ice.com + */ + +public class TarBuffer +{ + + public final static int DEFAULT_RCDSIZE = ( 512 ); + public final static int DEFAULT_BLKSIZE = ( DEFAULT_RCDSIZE * 20 ); + private byte[] blockBuffer; + private int blockSize; + private int currBlkIdx; + private int currRecIdx; + private boolean debug; + + private InputStream inStream; + private OutputStream outStream; + private int recordSize; + private int recsPerBlock; + + public TarBuffer( InputStream inStream ) + { + this( inStream, TarBuffer.DEFAULT_BLKSIZE ); + } + + public TarBuffer( InputStream inStream, int blockSize ) + { + this( inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarBuffer( InputStream inStream, int blockSize, int recordSize ) + { + this.inStream = inStream; + this.outStream = null; + + this.initialize( blockSize, recordSize ); + } + + public TarBuffer( OutputStream outStream ) + { + this( outStream, TarBuffer.DEFAULT_BLKSIZE ); + } + + public TarBuffer( OutputStream outStream, int blockSize ) + { + this( outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarBuffer( OutputStream outStream, int blockSize, int recordSize ) + { + this.inStream = null; + this.outStream = outStream; + + this.initialize( blockSize, recordSize ); + } + + /** + * Set the debugging flag for the buffer. + * + * @param debug If true, print debugging output. + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + } + + /** + * Get the TAR Buffer's block size. Blocks consist of multiple records. + * + * @return The BlockSize value + */ + public int getBlockSize() + { + return this.blockSize; + } + + /** + * Get the current block number, zero based. + * + * @return The current zero based block number. + */ + public int getCurrentBlockNum() + { + return this.currBlkIdx; + } + + /** + * Get the current record number, within the current block, zero based. + * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum. + * + * @return The current zero based record number. + */ + public int getCurrentRecordNum() + { + return this.currRecIdx - 1; + } + + /** + * Get the TAR Buffer's record size. + * + * @return The RecordSize value + */ + public int getRecordSize() + { + return this.recordSize; + } + + /** + * Determine if an archive record indicate End of Archive. End of archive is + * indicated by a record that consists entirely of null bytes. + * + * @param record The record data to check. + * @return The EOFRecord value + */ + public boolean isEOFRecord( byte[] record ) + { + for( int i = 0, sz = this.getRecordSize(); i < sz; ++i ) + { + if( record[i] != 0 ) + { + return false; + } + } + + return true; + } + + /** + * Close the TarBuffer. If this is an output buffer, also flush the current + * block before closing. + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + if( this.debug ) + { + System.err.println( "TarBuffer.closeBuffer()." ); + } + + if( this.outStream != null ) + { + this.flushBlock(); + + if( this.outStream != System.out + && this.outStream != System.err ) + { + this.outStream.close(); + + this.outStream = null; + } + } + else if( this.inStream != null ) + { + if( this.inStream != System.in ) + { + this.inStream.close(); + + this.inStream = null; + } + } + } + + /** + * Read a record from the input stream and return the data. + * + * @return The record data. + * @exception IOException Description of Exception + */ + public byte[] readRecord() + throws IOException + { + if( this.debug ) + { + System.err.println( "ReadRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.inStream == null ) + { + throw new IOException( "reading from an output buffer" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + if( !this.readBlock() ) + { + return null; + } + } + + byte[] result = new byte[this.recordSize]; + + System.arraycopy( this.blockBuffer, + ( this.currRecIdx * this.recordSize ), result, 0, + this.recordSize ); + + this.currRecIdx++; + + return result; + } + + /** + * Skip over a record on the input stream. + * + * @exception IOException Description of Exception + */ + public void skipRecord() + throws IOException + { + if( this.debug ) + { + System.err.println( "SkipRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.inStream == null ) + { + throw new IOException( "reading (via skip) from an output buffer" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + if( !this.readBlock() ) + { + return;// UNDONE + } + } + + this.currRecIdx++; + } + + /** + * Write an archive record to the archive. + * + * @param record The record data to write to the archive. + * @exception IOException Description of Exception + */ + public void writeRecord( byte[] record ) + throws IOException + { + if( this.debug ) + { + System.err.println( "WriteRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + if( record.length != this.recordSize ) + { + throw new IOException( "record to write has length '" + + record.length + + "' which is not the record size of '" + + this.recordSize + "'" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + this.writeBlock(); + } + + System.arraycopy( record, 0, this.blockBuffer, + ( this.currRecIdx * this.recordSize ), + this.recordSize ); + + this.currRecIdx++; + } + + /** + * Write an archive record to the archive, where the record may be inside of + * a larger array buffer. The buffer must be "offset plus record size" long. + * + * @param buf The buffer containing the record data to write. + * @param offset The offset of the record data within buf. + * @exception IOException Description of Exception + */ + public void writeRecord( byte[] buf, int offset ) + throws IOException + { + if( this.debug ) + { + System.err.println( "WriteRecord: recIdx = " + this.currRecIdx + + " blkIdx = " + this.currBlkIdx ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + if( ( offset + this.recordSize ) > buf.length ) + { + throw new IOException( "record has length '" + buf.length + + "' with offset '" + offset + + "' which is less than the record size of '" + + this.recordSize + "'" ); + } + + if( this.currRecIdx >= this.recsPerBlock ) + { + this.writeBlock(); + } + + System.arraycopy( buf, offset, this.blockBuffer, + ( this.currRecIdx * this.recordSize ), + this.recordSize ); + + this.currRecIdx++; + } + + /** + * Flush the current data block if it has any data in it. + * + * @exception IOException Description of Exception + */ + private void flushBlock() + throws IOException + { + if( this.debug ) + { + System.err.println( "TarBuffer.flushBlock() called." ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + if( this.currRecIdx > 0 ) + { + this.writeBlock(); + } + } + + /** + * Initialization common to all constructors. + * + * @param blockSize Description of Parameter + * @param recordSize Description of Parameter + */ + private void initialize( int blockSize, int recordSize ) + { + this.debug = false; + this.blockSize = blockSize; + this.recordSize = recordSize; + this.recsPerBlock = ( this.blockSize / this.recordSize ); + this.blockBuffer = new byte[this.blockSize]; + + if( this.inStream != null ) + { + this.currBlkIdx = -1; + this.currRecIdx = this.recsPerBlock; + } + else + { + this.currBlkIdx = 0; + this.currRecIdx = 0; + } + } + + /** + * @return false if End-Of-File, else true + * @exception IOException Description of Exception + */ + private boolean readBlock() + throws IOException + { + if( this.debug ) + { + System.err.println( "ReadBlock: blkIdx = " + this.currBlkIdx ); + } + + if( this.inStream == null ) + { + throw new IOException( "reading from an output buffer" ); + } + + this.currRecIdx = 0; + + int offset = 0; + int bytesNeeded = this.blockSize; + + while( bytesNeeded > 0 ) + { + long numBytes = this.inStream.read( this.blockBuffer, offset, + bytesNeeded ); + + // + // NOTE + // We have fit EOF, and the block is not full! + // + // This is a broken archive. It does not follow the standard + // blocking algorithm. However, because we are generous, and + // it requires little effort, we will simply ignore the error + // and continue as if the entire block were read. This does + // not appear to break anything upstream. We used to return + // false in this case. + // + // Thanks to 'Yohann.Roussel@alcatel.fr' for this fix. + // + if( numBytes == -1 ) + { + break; + } + + offset += numBytes; + bytesNeeded -= numBytes; + + if( numBytes != this.blockSize ) + { + if( this.debug ) + { + System.err.println( "ReadBlock: INCOMPLETE READ " + + numBytes + " of " + this.blockSize + + " bytes read." ); + } + } + } + + this.currBlkIdx++; + + return true; + } + + /** + * Write a TarBuffer block to the archive. + * + * @exception IOException Description of Exception + */ + private void writeBlock() + throws IOException + { + if( this.debug ) + { + System.err.println( "WriteBlock: blkIdx = " + this.currBlkIdx ); + } + + if( this.outStream == null ) + { + throw new IOException( "writing to an input buffer" ); + } + + this.outStream.write( this.blockBuffer, 0, this.blockSize ); + this.outStream.flush(); + + this.currRecIdx = 0; + this.currBlkIdx++; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/tar/TarConstants.java b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarConstants.java new file mode 100644 index 000000000..6203d31d1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarConstants.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; + +/** + * This interface contains all the definitions used in the package. + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ + +public interface TarConstants +{ + + /** + * The length of the name field in a header buffer. + */ + int NAMELEN = 100; + + /** + * The length of the mode field in a header buffer. + */ + int MODELEN = 8; + + /** + * The length of the user id field in a header buffer. + */ + int UIDLEN = 8; + + /** + * The length of the group id field in a header buffer. + */ + int GIDLEN = 8; + + /** + * The length of the checksum field in a header buffer. + */ + int CHKSUMLEN = 8; + + /** + * The length of the size field in a header buffer. + */ + int SIZELEN = 12; + + /** + * The length of the magic field in a header buffer. + */ + int MAGICLEN = 8; + + /** + * The length of the modification time field in a header buffer. + */ + int MODTIMELEN = 12; + + /** + * The length of the user name field in a header buffer. + */ + int UNAMELEN = 32; + + /** + * The length of the group name field in a header buffer. + */ + int GNAMELEN = 32; + + /** + * The length of the devices field in a header buffer. + */ + int DEVLEN = 8; + + /** + * LF_ constants represent the "link flag" of an entry, or more commonly, + * the "entry type". This is the "old way" of indicating a normal file. + */ + byte LF_OLDNORM = 0; + + /** + * Normal file type. + */ + byte LF_NORMAL = ( byte )'0'; + + /** + * Link file type. + */ + byte LF_LINK = ( byte )'1'; + + /** + * Symbolic link file type. + */ + byte LF_SYMLINK = ( byte )'2'; + + /** + * Character device file type. + */ + byte LF_CHR = ( byte )'3'; + + /** + * Block device file type. + */ + byte LF_BLK = ( byte )'4'; + + /** + * Directory file type. + */ + byte LF_DIR = ( byte )'5'; + + /** + * FIFO (pipe) file type. + */ + byte LF_FIFO = ( byte )'6'; + + /** + * Contiguous file type. + */ + byte LF_CONTIG = ( byte )'7'; + + /** + * The magic tag representing a POSIX tar archive. + */ + String TMAGIC = "ustar"; + + /** + * The magic tag representing a GNU tar archive. + */ + String GNU_TMAGIC = "ustar "; + + /** + * The namr of the GNU tar entry which contains a long name. + */ + String GNU_LONGLINK = "././@LongLink"; + + /** + * Identifies the *next* file on the tape as having a long name. + */ + byte LF_GNUTYPE_LONGNAME = ( byte )'L'; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/tar/TarEntry.java b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarEntry.java new file mode 100644 index 000000000..7914dd132 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarEntry.java @@ -0,0 +1,654 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.File; +import java.util.Date; + +/** + * This class represents an entry in a Tar archive. It consists of the entry's + * header, as well as the entry's File. Entries can be instantiated in one of + * three ways, depending on how they are to be used.

                  + * + * TarEntries that are created from the header bytes read from an archive are + * instantiated with the TarEntry( byte[] ) constructor. These entries will be + * used when extracting from or listing the contents of an archive. These + * entries have their header filled in using the header bytes. They also set the + * File to null, since they reference an archive entry not a file.

                  + * + * TarEntries that are created from Files that are to be written into an archive + * are instantiated with the TarEntry( File ) constructor. These entries have + * their header filled in using the File's information. They also keep a + * reference to the File for convenience when writing entries.

                  + * + * Finally, TarEntries can be constructed from nothing but a name. This allows + * the programmer to construct the entry by hand, for instance when only an + * InputStream is available for writing to the archive, and the header + * information is constructed from other information. In this case the header + * fields are set to defaults and the File is set to null.

                  + * + * The C structure for a Tar Entry's header is:

                  + * struct header {
                  + * char name[NAMSIZ];
                  + * char mode[8];
                  + * char uid[8];
                  + * char gid[8];
                  + * char size[12];
                  + * char mtime[12];
                  + * char chksum[8];
                  + * char linkflag;
                  + * char linkname[NAMSIZ];
                  + * char magic[8];
                  + * char uname[TUNMLEN];
                  + * char gname[TGNMLEN];
                  + * char devmajor[8];
                  + * char devminor[8];
                  + * } header;
                  + * 
                  + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ + +public class TarEntry implements TarConstants +{ + /** + * The entry's modification time. + */ + private int checkSum; + /** + * The entry's group name. + */ + private int devMajor; + /** + * The entry's major device number. + */ + private int devMinor; + /** + * The entry's minor device number. + */ + private File file; + /** + * The entry's user id. + */ + private int groupId; + /** + * The entry's user name. + */ + private StringBuffer groupName; + /** + * The entry's checksum. + */ + private byte linkFlag; + /** + * The entry's link flag. + */ + private StringBuffer linkName; + /** + * The entry's link name. + */ + private StringBuffer magic; + /** + * The entry's size. + */ + private long modTime; + /** + * The entry's name. + */ + private int mode; + + private StringBuffer name; + /** + * The entry's group id. + */ + private long size; + /** + * The entry's permission mode. + */ + private int userId; + /** + * The entry's magic tag. + */ + private StringBuffer userName; + + /** + * Construct an entry with only a name. This allows the programmer to + * construct the entry's header "by hand". File is set to null. + * + * @param name Description of Parameter + */ + public TarEntry( String name ) + { + this(); + + boolean isDir = name.endsWith( "/" ); + + this.checkSum = 0; + this.devMajor = 0; + this.devMinor = 0; + this.name = new StringBuffer( name ); + this.mode = isDir ? 040755 : 0100644; + this.linkFlag = isDir ? LF_DIR : LF_NORMAL; + this.userId = 0; + this.groupId = 0; + this.size = 0; + this.checkSum = 0; + this.modTime = ( new Date() ).getTime() / 1000; + this.linkName = new StringBuffer( "" ); + this.userName = new StringBuffer( "" ); + this.groupName = new StringBuffer( "" ); + this.devMajor = 0; + this.devMinor = 0; + + } + + /** + * Construct an entry with a name an a link flag. + * + * @param name Description of Parameter + * @param linkFlag Description of Parameter + */ + public TarEntry( String name, byte linkFlag ) + { + this( name ); + this.linkFlag = linkFlag; + } + + /** + * Construct an entry for a file. File is set to file, and the header is + * constructed from information from the file. + * + * @param file The file that the entry represents. + */ + public TarEntry( File file ) + { + this(); + + this.file = file; + + String name = file.getPath(); + String osname = System.getProperty( "os.name" ); + + if( osname != null ) + { + + // Strip off drive letters! + // REVIEW Would a better check be "(File.separator == '\')"? + String win32Prefix = "Windows"; + String prefix = osname.substring( 0, win32Prefix.length() ); + + if( prefix.equalsIgnoreCase( win32Prefix ) ) + { + if( name.length() > 2 ) + { + char ch1 = name.charAt( 0 ); + char ch2 = name.charAt( 1 ); + + if( ch2 == ':' + && ( ( ch1 >= 'a' && ch1 <= 'z' ) + || ( ch1 >= 'A' && ch1 <= 'Z' ) ) ) + { + name = name.substring( 2 ); + } + } + } + else if( osname.toLowerCase().indexOf( "netware" ) > -1 ) + { + int colon = name.indexOf( ':' ); + if( colon != -1 ) + { + name = name.substring( colon + 1 ); + } + } + } + + name = name.replace( File.separatorChar, '/' ); + + // No absolute pathnames + // Windows (and Posix?) paths can start with "\\NetworkDrive\", + // so we loop on starting /'s. + while( name.startsWith( "/" ) ) + { + name = name.substring( 1 ); + } + + this.linkName = new StringBuffer( "" ); + this.name = new StringBuffer( name ); + + if( file.isDirectory() ) + { + this.mode = 040755; + this.linkFlag = LF_DIR; + + if( this.name.charAt( this.name.length() - 1 ) != '/' ) + { + this.name.append( "/" ); + } + } + else + { + this.mode = 0100644; + this.linkFlag = LF_NORMAL; + } + + this.size = file.length(); + this.modTime = file.lastModified() / 1000; + this.checkSum = 0; + this.devMajor = 0; + this.devMinor = 0; + } + + /** + * Construct an entry from an archive's header bytes. File is set to null. + * + * @param headerBuf The header bytes from a tar archive entry. + */ + public TarEntry( byte[] headerBuf ) + { + this(); + this.parseTarHeader( headerBuf ); + } + + /** + * The entry's file reference + */ + + /** + * Construct an empty entry and prepares the header values. + */ + private TarEntry() + { + this.magic = new StringBuffer( TMAGIC ); + this.name = new StringBuffer(); + this.linkName = new StringBuffer(); + + String user = System.getProperty( "user.name", "" ); + + if( user.length() > 31 ) + { + user = user.substring( 0, 31 ); + } + + this.userId = 0; + this.groupId = 0; + this.userName = new StringBuffer( user ); + this.groupName = new StringBuffer( "" ); + this.file = null; + } + + /** + * Set this entry's group id. + * + * @param groupId This entry's new group id. + */ + public void setGroupId( int groupId ) + { + this.groupId = groupId; + } + + /** + * Set this entry's group name. + * + * @param groupName This entry's new group name. + */ + public void setGroupName( String groupName ) + { + this.groupName = new StringBuffer( groupName ); + } + + /** + * Convenience method to set this entry's group and user ids. + * + * @param userId This entry's new user id. + * @param groupId This entry's new group id. + */ + public void setIds( int userId, int groupId ) + { + this.setUserId( userId ); + this.setGroupId( groupId ); + } + + /** + * Set this entry's modification time. The parameter passed to this method + * is in "Java time". + * + * @param time This entry's new modification time. + */ + public void setModTime( long time ) + { + this.modTime = time / 1000; + } + + /** + * Set this entry's modification time. + * + * @param time This entry's new modification time. + */ + public void setModTime( Date time ) + { + this.modTime = time.getTime() / 1000; + } + + /** + * Set the mode for this entry + * + * @param mode The new Mode value + */ + public void setMode( int mode ) + { + this.mode = mode; + } + + /** + * Set this entry's name. + * + * @param name This entry's new name. + */ + public void setName( String name ) + { + this.name = new StringBuffer( name ); + } + + /** + * Convenience method to set this entry's group and user names. + * + * @param userName This entry's new user name. + * @param groupName This entry's new group name. + */ + public void setNames( String userName, String groupName ) + { + this.setUserName( userName ); + this.setGroupName( groupName ); + } + + /** + * Set this entry's file size. + * + * @param size This entry's new file size. + */ + public void setSize( long size ) + { + this.size = size; + } + + /** + * Set this entry's user id. + * + * @param userId This entry's new user id. + */ + public void setUserId( int userId ) + { + this.userId = userId; + } + + /** + * Set this entry's user name. + * + * @param userName This entry's new user name. + */ + public void setUserName( String userName ) + { + this.userName = new StringBuffer( userName ); + } + + /** + * If this entry represents a file, and the file is a directory, return an + * array of TarEntries for this entry's children. + * + * @return An array of TarEntry's for this entry's children. + */ + public TarEntry[] getDirectoryEntries() + { + if( this.file == null || !this.file.isDirectory() ) + { + return new TarEntry[0]; + } + + String[] list = this.file.list(); + TarEntry[] result = new TarEntry[list.length]; + + for( int i = 0; i < list.length; ++i ) + { + result[i] = new TarEntry( new File( this.file, list[i] ) ); + } + + return result; + } + + /** + * Get this entry's file. + * + * @return This entry's file. + */ + public File getFile() + { + return this.file; + } + + /** + * Get this entry's group id. + * + * @return This entry's group id. + */ + public int getGroupId() + { + return this.groupId; + } + + /** + * Get this entry's group name. + * + * @return This entry's group name. + */ + public String getGroupName() + { + return this.groupName.toString(); + } + + /** + * Set this entry's modification time. + * + * @return The ModTime value + */ + public Date getModTime() + { + return new Date( this.modTime * 1000 ); + } + + /** + * Get this entry's mode. + * + * @return This entry's mode. + */ + public int getMode() + { + return this.mode; + } + + /** + * Get this entry's name. + * + * @return This entry's name. + */ + public String getName() + { + return this.name.toString(); + } + + /** + * Get this entry's file size. + * + * @return This entry's file size. + */ + public long getSize() + { + return this.size; + } + + + /** + * Get this entry's user id. + * + * @return This entry's user id. + */ + public int getUserId() + { + return this.userId; + } + + /** + * Get this entry's user name. + * + * @return This entry's user name. + */ + public String getUserName() + { + return this.userName.toString(); + } + + /** + * Determine if the given entry is a descendant of this entry. Descendancy + * is determined by the name of the descendant starting with this entry's + * name. + * + * @param desc Entry to be checked as a descendent of this. + * @return True if entry is a descendant of this. + */ + public boolean isDescendent( TarEntry desc ) + { + return desc.getName().startsWith( this.getName() ); + } + + /** + * Return whether or not this entry represents a directory. + * + * @return True if this entry is a directory. + */ + public boolean isDirectory() + { + if( this.file != null ) + { + return this.file.isDirectory(); + } + + if( this.linkFlag == LF_DIR ) + { + return true; + } + + if( this.getName().endsWith( "/" ) ) + { + return true; + } + + return false; + } + + + /** + * Indicate if this entry is a GNU long name block + * + * @return true if this is a long name extension provided by GNU tar + */ + public boolean isGNULongNameEntry() + { + return linkFlag == LF_GNUTYPE_LONGNAME && + name.toString().equals( GNU_LONGLINK ); + } + + /** + * Determine if the two entries are equal. Equality is determined by the + * header names being equal. + * + * @param it Description of Parameter + * @return it Entry to be checked for equality. + * @return True if the entries are equal. + */ + public boolean equals( TarEntry it ) + { + return this.getName().equals( it.getName() ); + } + + /** + * Parse an entry's header information from a header buffer. + * + * @param header The tar entry header buffer to get information from. + */ + public void parseTarHeader( byte[] header ) + { + int offset = 0; + + this.name = TarUtils.parseName( header, offset, NAMELEN ); + offset += NAMELEN; + this.mode = ( int )TarUtils.parseOctal( header, offset, MODELEN ); + offset += MODELEN; + this.userId = ( int )TarUtils.parseOctal( header, offset, UIDLEN ); + offset += UIDLEN; + this.groupId = ( int )TarUtils.parseOctal( header, offset, GIDLEN ); + offset += GIDLEN; + this.size = TarUtils.parseOctal( header, offset, SIZELEN ); + offset += SIZELEN; + this.modTime = TarUtils.parseOctal( header, offset, MODTIMELEN ); + offset += MODTIMELEN; + this.checkSum = ( int )TarUtils.parseOctal( header, offset, CHKSUMLEN ); + offset += CHKSUMLEN; + this.linkFlag = header[offset++]; + this.linkName = TarUtils.parseName( header, offset, NAMELEN ); + offset += NAMELEN; + this.magic = TarUtils.parseName( header, offset, MAGICLEN ); + offset += MAGICLEN; + this.userName = TarUtils.parseName( header, offset, UNAMELEN ); + offset += UNAMELEN; + this.groupName = TarUtils.parseName( header, offset, GNAMELEN ); + offset += GNAMELEN; + this.devMajor = ( int )TarUtils.parseOctal( header, offset, DEVLEN ); + offset += DEVLEN; + this.devMinor = ( int )TarUtils.parseOctal( header, offset, DEVLEN ); + } + + /** + * Write an entry's header information to a header buffer. + * + * @param outbuf The tar entry header buffer to fill in. + */ + public void writeEntryHeader( byte[] outbuf ) + { + int offset = 0; + + offset = TarUtils.getNameBytes( this.name, outbuf, offset, NAMELEN ); + offset = TarUtils.getOctalBytes( this.mode, outbuf, offset, MODELEN ); + offset = TarUtils.getOctalBytes( this.userId, outbuf, offset, UIDLEN ); + offset = TarUtils.getOctalBytes( this.groupId, outbuf, offset, GIDLEN ); + offset = TarUtils.getLongOctalBytes( this.size, outbuf, offset, SIZELEN ); + offset = TarUtils.getLongOctalBytes( this.modTime, outbuf, offset, MODTIMELEN ); + + int csOffset = offset; + + for( int c = 0; c < CHKSUMLEN; ++c ) + { + outbuf[offset++] = ( byte )' '; + } + + outbuf[offset++] = this.linkFlag; + offset = TarUtils.getNameBytes( this.linkName, outbuf, offset, NAMELEN ); + offset = TarUtils.getNameBytes( this.magic, outbuf, offset, MAGICLEN ); + offset = TarUtils.getNameBytes( this.userName, outbuf, offset, UNAMELEN ); + offset = TarUtils.getNameBytes( this.groupName, outbuf, offset, GNAMELEN ); + offset = TarUtils.getOctalBytes( this.devMajor, outbuf, offset, DEVLEN ); + offset = TarUtils.getOctalBytes( this.devMinor, outbuf, offset, DEVLEN ); + + while( offset < outbuf.length ) + { + outbuf[offset++] = 0; + } + + long checkSum = TarUtils.computeCheckSum( outbuf ); + + TarUtils.getCheckSumOctalBytes( checkSum, outbuf, csOffset, CHKSUMLEN ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/tar/TarInputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarInputStream.java new file mode 100644 index 000000000..5e340e6ae --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarInputStream.java @@ -0,0 +1,439 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * The TarInputStream reads a UNIX tar archive as an InputStream. methods are + * provided to position at each successive entry in the archive, and the read + * each entry as a normal input stream using read(). + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ +public class TarInputStream extends FilterInputStream +{ + protected TarBuffer buffer; + protected TarEntry currEntry; + + protected boolean debug; + protected int entryOffset; + protected int entrySize; + protected boolean hasHitEOF; + protected byte[] oneBuf; + protected byte[] readBuf; + + public TarInputStream( InputStream is ) + { + this( is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarInputStream( InputStream is, int blockSize ) + { + this( is, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarInputStream( InputStream is, int blockSize, int recordSize ) + { + super( is ); + + this.buffer = new TarBuffer( is, blockSize, recordSize ); + this.readBuf = null; + this.oneBuf = new byte[1]; + this.debug = false; + this.hasHitEOF = false; + } + + /** + * Sets the debugging flag. + * + * @param debug The new Debug value + */ + public void setDebug( boolean debug ) + { + this.debug = debug; + this.buffer.setDebug( debug ); + } + + /** + * Get the next entry in this tar archive. This will skip over any remaining + * data in the current entry, if there is one, and place the input stream at + * the header of the next entry, and read the header and instantiate a new + * TarEntry from the header bytes and return that entry. If there are no + * more entries in the archive, null will be returned to indicate that the + * end of the archive has been reached. + * + * @return The next TarEntry in the archive, or null. + * @exception IOException Description of Exception + */ + public TarEntry getNextEntry() + throws IOException + { + if( this.hasHitEOF ) + { + return null; + } + + if( this.currEntry != null ) + { + int numToSkip = this.entrySize - this.entryOffset; + + if( this.debug ) + { + System.err.println( "TarInputStream: SKIP currENTRY '" + + this.currEntry.getName() + "' SZ " + + this.entrySize + " OFF " + + this.entryOffset + " skipping " + + numToSkip + " bytes" ); + } + + if( numToSkip > 0 ) + { + this.skip( numToSkip ); + } + + this.readBuf = null; + } + + byte[] headerBuf = this.buffer.readRecord(); + + if( headerBuf == null ) + { + if( this.debug ) + { + System.err.println( "READ NULL RECORD" ); + } + this.hasHitEOF = true; + } + else if( this.buffer.isEOFRecord( headerBuf ) ) + { + if( this.debug ) + { + System.err.println( "READ EOF RECORD" ); + } + this.hasHitEOF = true; + } + + if( this.hasHitEOF ) + { + this.currEntry = null; + } + else + { + this.currEntry = new TarEntry( headerBuf ); + + if( !( headerBuf[257] == 'u' && headerBuf[258] == 's' + && headerBuf[259] == 't' && headerBuf[260] == 'a' + && headerBuf[261] == 'r' ) ) + { + this.entrySize = 0; + this.entryOffset = 0; + this.currEntry = null; + + throw new IOException( "bad header in block " + + this.buffer.getCurrentBlockNum() + + " record " + + this.buffer.getCurrentRecordNum() + + ", " + + "header magic is not 'ustar', but '" + + headerBuf[257] + + headerBuf[258] + + headerBuf[259] + + headerBuf[260] + + headerBuf[261] + + "', or (dec) " + + ( ( int )headerBuf[257] ) + + ", " + + ( ( int )headerBuf[258] ) + + ", " + + ( ( int )headerBuf[259] ) + + ", " + + ( ( int )headerBuf[260] ) + + ", " + + ( ( int )headerBuf[261] ) ); + } + + if( this.debug ) + { + System.err.println( "TarInputStream: SET CURRENTRY '" + + this.currEntry.getName() + + "' size = " + + this.currEntry.getSize() ); + } + + this.entryOffset = 0; + + // REVIEW How do we resolve this discrepancy?! + this.entrySize = ( int )this.currEntry.getSize(); + } + + if( this.currEntry != null && this.currEntry.isGNULongNameEntry() ) + { + // read in the name + StringBuffer longName = new StringBuffer(); + byte[] buffer = new byte[256]; + int length = 0; + while( ( length = read( buffer ) ) >= 0 ) + { + longName.append( new String( buffer, 0, length ) ); + } + getNextEntry(); + this.currEntry.setName( longName.toString() ); + } + + return this.currEntry; + } + + /** + * Get the record size being used by this stream's TarBuffer. + * + * @return The TarBuffer record size. + */ + public int getRecordSize() + { + return this.buffer.getRecordSize(); + } + + /** + * Get the available data that can be read from the current entry in the + * archive. This does not indicate how much data is left in the entire + * archive, only in the current entry. This value is determined from the + * entry's size header field and the amount of data already read from the + * current entry. + * + * @return The number of available bytes for the current entry. + * @exception IOException Description of Exception + */ + public int available() + throws IOException + { + return this.entrySize - this.entryOffset; + } + + /** + * Closes this stream. Calls the TarBuffer's close() method. + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + this.buffer.close(); + } + + /** + * Copies the contents of the current tar archive entry directly into an + * output stream. + * + * @param out The OutputStream into which to write the entry's data. + * @exception IOException Description of Exception + */ + public void copyEntryContents( OutputStream out ) + throws IOException + { + byte[] buf = new byte[32 * 1024]; + + while( true ) + { + int numRead = this.read( buf, 0, buf.length ); + + if( numRead == -1 ) + { + break; + } + + out.write( buf, 0, numRead ); + } + } + + /** + * Since we do not support marking just yet, we do nothing. + * + * @param markLimit The limit to mark. + */ + public void mark( int markLimit ) { } + + /** + * Since we do not support marking just yet, we return false. + * + * @return False. + */ + public boolean markSupported() + { + return false; + } + + /** + * Reads a byte from the current tar archive entry. This method simply calls + * read( byte[], int, int ). + * + * @return The byte read, or -1 at EOF. + * @exception IOException Description of Exception + */ + public int read() + throws IOException + { + int num = this.read( this.oneBuf, 0, 1 ); + + if( num == -1 ) + { + return num; + } + else + { + return ( int )this.oneBuf[0]; + } + } + + /** + * Reads bytes from the current tar archive entry. This method simply calls + * read( byte[], int, int ). + * + * @param buf The buffer into which to place bytes read. + * @return The number of bytes read, or -1 at EOF. + * @exception IOException Description of Exception + */ + public int read( byte[] buf ) + throws IOException + { + return this.read( buf, 0, buf.length ); + } + + /** + * Reads bytes from the current tar archive entry. This method is aware of + * the boundaries of the current entry in the archive and will deal with + * them as if they were this stream's start and EOF. + * + * @param buf The buffer into which to place bytes read. + * @param offset The offset at which to place bytes read. + * @param numToRead The number of bytes to read. + * @return The number of bytes read, or -1 at EOF. + * @exception IOException Description of Exception + */ + public int read( byte[] buf, int offset, int numToRead ) + throws IOException + { + int totalRead = 0; + + if( this.entryOffset >= this.entrySize ) + { + return -1; + } + + if( ( numToRead + this.entryOffset ) > this.entrySize ) + { + numToRead = ( this.entrySize - this.entryOffset ); + } + + if( this.readBuf != null ) + { + int sz = ( numToRead > this.readBuf.length ) ? this.readBuf.length + : numToRead; + + System.arraycopy( this.readBuf, 0, buf, offset, sz ); + + if( sz >= this.readBuf.length ) + { + this.readBuf = null; + } + else + { + int newLen = this.readBuf.length - sz; + byte[] newBuf = new byte[newLen]; + + System.arraycopy( this.readBuf, sz, newBuf, 0, newLen ); + + this.readBuf = newBuf; + } + + totalRead += sz; + numToRead -= sz; + offset += sz; + } + + while( numToRead > 0 ) + { + byte[] rec = this.buffer.readRecord(); + + if( rec == null ) + { + // Unexpected EOF! + throw new IOException( "unexpected EOF with " + numToRead + + " bytes unread" ); + } + + int sz = numToRead; + int recLen = rec.length; + + if( recLen > sz ) + { + System.arraycopy( rec, 0, buf, offset, sz ); + + this.readBuf = new byte[recLen - sz]; + + System.arraycopy( rec, sz, this.readBuf, 0, recLen - sz ); + } + else + { + sz = recLen; + + System.arraycopy( rec, 0, buf, offset, recLen ); + } + + totalRead += sz; + numToRead -= sz; + offset += sz; + } + + this.entryOffset += totalRead; + + return totalRead; + } + + /** + * Since we do not support marking just yet, we do nothing. + */ + public void reset() { } + + /** + * Skip bytes in the input buffer. This skips bytes in the current entry's + * data, not the entire archive, and will stop at the end of the current + * entry's data if the number to skip extends beyond that point. + * + * @param numToSkip The number of bytes to skip. + * @exception IOException Description of Exception + */ + public void skip( int numToSkip ) + throws IOException + { + + // REVIEW + // This is horribly inefficient, but it ensures that we + // properly skip over bytes via the TarBuffer... + // + byte[] skipBuf = new byte[8 * 1024]; + + for( int num = numToSkip; num > 0; ) + { + int numRead = this.read( skipBuf, 0, + ( num > skipBuf.length ? skipBuf.length + : num ) ); + + if( numRead == -1 ) + { + break; + } + + num -= numRead; + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/tar/TarOutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarOutputStream.java new file mode 100644 index 000000000..92914c329 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarOutputStream.java @@ -0,0 +1,335 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * The TarOutputStream writes a UNIX tar archive as an OutputStream. Methods are + * provided to put entries, and then write their contents by writing to this + * stream using write(). + * + * @author Timothy Gerard Endres time@ice.com + */ +public class TarOutputStream extends FilterOutputStream +{ + public final static int LONGFILE_ERROR = 0; + public final static int LONGFILE_TRUNCATE = 1; + public final static int LONGFILE_GNU = 2; + protected int longFileMode = LONGFILE_ERROR; + protected byte[] assemBuf; + protected int assemLen; + protected TarBuffer buffer; + protected int currBytes; + protected int currSize; + + protected boolean debug; + protected byte[] oneBuf; + protected byte[] recordBuf; + + public TarOutputStream( OutputStream os ) + { + this( os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarOutputStream( OutputStream os, int blockSize ) + { + this( os, blockSize, TarBuffer.DEFAULT_RCDSIZE ); + } + + public TarOutputStream( OutputStream os, int blockSize, int recordSize ) + { + super( os ); + + this.buffer = new TarBuffer( os, blockSize, recordSize ); + this.debug = false; + this.assemLen = 0; + this.assemBuf = new byte[recordSize]; + this.recordBuf = new byte[recordSize]; + this.oneBuf = new byte[1]; + } + + /** + * Sets the debugging flag in this stream's TarBuffer. + * + * @param debug The new BufferDebug value + */ + public void setBufferDebug( boolean debug ) + { + this.buffer.setDebug( debug ); + } + + + /** + * Sets the debugging flag. + * + * @param debugF True to turn on debugging. + */ + public void setDebug( boolean debugF ) + { + this.debug = debugF; + } + + public void setLongFileMode( int longFileMode ) + { + this.longFileMode = longFileMode; + } + + /** + * Get the record size being used by this stream's TarBuffer. + * + * @return The TarBuffer record size. + */ + public int getRecordSize() + { + return this.buffer.getRecordSize(); + } + + /** + * Ends the TAR archive and closes the underlying OutputStream. This means + * that finish() is called followed by calling the TarBuffer's close(). + * + * @exception IOException Description of Exception + */ + public void close() + throws IOException + { + this.finish(); + this.buffer.close(); + } + + /** + * Close an entry. This method MUST be called for all file entries that + * contain data. The reason is that we must buffer data written to the + * stream in order to satisfy the buffer's record based writes. Thus, there + * may be data fragments still being assembled that must be written to the + * output stream before this entry is closed and the next entry written. + * + * @exception IOException Description of Exception + */ + public void closeEntry() + throws IOException + { + if( this.assemLen > 0 ) + { + for( int i = this.assemLen; i < this.assemBuf.length; ++i ) + { + this.assemBuf[i] = 0; + } + + this.buffer.writeRecord( this.assemBuf ); + + this.currBytes += this.assemLen; + this.assemLen = 0; + } + + if( this.currBytes < this.currSize ) + { + throw new IOException( "entry closed at '" + this.currBytes + + "' before the '" + this.currSize + + "' bytes specified in the header were written" ); + } + } + + /** + * Ends the TAR archive without closing the underlying OutputStream. The + * result is that the EOF record of nulls is written. + * + * @exception IOException Description of Exception + */ + public void finish() + throws IOException + { + this.writeEOFRecord(); + } + + /** + * Put an entry on the output stream. This writes the entry's header record + * and positions the output stream for writing the contents of the entry. + * Once this method is called, the stream is ready for calls to write() to + * write the entry's contents. Once the contents are written, closeEntry() + * MUST be called to ensure that all buffered data is completely + * written to the output stream. + * + * @param entry The TarEntry to be written to the archive. + * @exception IOException Description of Exception + */ + public void putNextEntry( TarEntry entry ) + throws IOException + { + if( entry.getName().length() >= TarConstants.NAMELEN ) + { + + if( longFileMode == LONGFILE_GNU ) + { + // create a TarEntry for the LongLink, the contents + // of which are the entry's name + TarEntry longLinkEntry = new TarEntry( TarConstants.GNU_LONGLINK, + TarConstants.LF_GNUTYPE_LONGNAME ); + + longLinkEntry.setSize( entry.getName().length() + 1 ); + putNextEntry( longLinkEntry ); + write( entry.getName().getBytes() ); + write( 0 ); + closeEntry(); + } + else if( longFileMode != LONGFILE_TRUNCATE ) + { + throw new RuntimeException( "file name '" + entry.getName() + + "' is too long ( > " + + TarConstants.NAMELEN + " bytes)" ); + } + } + + entry.writeEntryHeader( this.recordBuf ); + this.buffer.writeRecord( this.recordBuf ); + + this.currBytes = 0; + + if( entry.isDirectory() ) + { + this.currSize = 0; + } + else + { + this.currSize = ( int )entry.getSize(); + } + } + + /** + * Writes a byte to the current tar archive entry. This method simply calls + * read( byte[], int, int ). + * + * @param b The byte written. + * @exception IOException Description of Exception + */ + public void write( int b ) + throws IOException + { + this.oneBuf[0] = ( byte )b; + + this.write( this.oneBuf, 0, 1 ); + } + + /** + * Writes bytes to the current tar archive entry. This method simply calls + * write( byte[], int, int ). + * + * @param wBuf The buffer to write to the archive. + * @exception IOException Description of Exception + */ + public void write( byte[] wBuf ) + throws IOException + { + this.write( wBuf, 0, wBuf.length ); + } + + /** + * Writes bytes to the current tar archive entry. This method is aware of + * the current entry and will throw an exception if you attempt to write + * bytes past the length specified for the current entry. The method is also + * (painfully) aware of the record buffering required by TarBuffer, and + * manages buffers that are not a multiple of recordsize in length, + * including assembling records from small buffers. + * + * @param wBuf The buffer to write to the archive. + * @param wOffset The offset in the buffer from which to get bytes. + * @param numToWrite The number of bytes to write. + * @exception IOException Description of Exception + */ + public void write( byte[] wBuf, int wOffset, int numToWrite ) + throws IOException + { + if( ( this.currBytes + numToWrite ) > this.currSize ) + { + throw new IOException( "request to write '" + numToWrite + + "' bytes exceeds size in header of '" + + this.currSize + "' bytes" ); + // + // We have to deal with assembly!!! + // The programmer can be writing little 32 byte chunks for all + // we know, and we must assemble complete records for writing. + // REVIEW Maybe this should be in TarBuffer? Could that help to + // eliminate some of the buffer copying. + // + } + + if( this.assemLen > 0 ) + { + if( ( this.assemLen + numToWrite ) >= this.recordBuf.length ) + { + int aLen = this.recordBuf.length - this.assemLen; + + System.arraycopy( this.assemBuf, 0, this.recordBuf, 0, + this.assemLen ); + System.arraycopy( wBuf, wOffset, this.recordBuf, + this.assemLen, aLen ); + this.buffer.writeRecord( this.recordBuf ); + + this.currBytes += this.recordBuf.length; + wOffset += aLen; + numToWrite -= aLen; + this.assemLen = 0; + } + else + { + System.arraycopy( wBuf, wOffset, this.assemBuf, this.assemLen, + numToWrite ); + + wOffset += numToWrite; + this.assemLen += numToWrite; + numToWrite -= numToWrite; + } + } + + // + // When we get here we have EITHER: + // o An empty "assemble" buffer. + // o No bytes to write (numToWrite == 0) + // + while( numToWrite > 0 ) + { + if( numToWrite < this.recordBuf.length ) + { + System.arraycopy( wBuf, wOffset, this.assemBuf, this.assemLen, + numToWrite ); + + this.assemLen += numToWrite; + + break; + } + + this.buffer.writeRecord( wBuf, wOffset ); + + int num = this.recordBuf.length; + + this.currBytes += num; + numToWrite -= num; + wOffset += num; + } + } + + /** + * Write an EOF (end of archive) record to the tar archive. An EOF record + * consists of a record of all zeros. + * + * @exception IOException Description of Exception + */ + private void writeEOFRecord() + throws IOException + { + for( int i = 0; i < this.recordBuf.length; ++i ) + { + this.recordBuf[i] = 0; + } + + this.buffer.writeRecord( this.recordBuf ); + } +} + diff --git a/proposal/myrmidon/src/todo/org/apache/tools/tar/TarUtils.java b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarUtils.java new file mode 100644 index 000000000..40bf84d59 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/tar/TarUtils.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.tar; + +/** + * This class provides static utility methods to work with byte streams. + * + * @author Timothy Gerard Endres time@ice.com + * @author Stefano Mazzocchi + * stefano@apache.org + */ +public class TarUtils +{ + + /** + * Parse the checksum octal integer from a header buffer. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param value Description of Parameter + * @param buf Description of Parameter + * @return The integer value of the entry's checksum. + */ + public static int getCheckSumOctalBytes( long value, byte[] buf, int offset, int length ) + { + getOctalBytes( value, buf, offset, length ); + + buf[offset + length - 1] = ( byte )' '; + buf[offset + length - 2] = 0; + + return offset + length; + } + + /** + * Parse an octal long integer from a header buffer. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param value Description of Parameter + * @param buf Description of Parameter + * @return The long value of the octal bytes. + */ + public static int getLongOctalBytes( long value, byte[] buf, int offset, int length ) + { + byte[] temp = new byte[length + 1]; + + getOctalBytes( value, temp, 0, length + 1 ); + System.arraycopy( temp, 0, buf, offset, length ); + + return offset + length; + } + + /** + * Determine the number of bytes in an entry name. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param name Description of Parameter + * @param buf Description of Parameter + * @return The number of bytes in a header's entry name. + */ + public static int getNameBytes( StringBuffer name, byte[] buf, int offset, int length ) + { + int i; + + for( i = 0; i < length && i < name.length(); ++i ) + { + buf[offset + i] = ( byte )name.charAt( i ); + } + + for( ; i < length; ++i ) + { + buf[offset + i] = 0; + } + + return offset + length; + } + + /** + * Parse an octal integer from a header buffer. + * + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @param value Description of Parameter + * @param buf Description of Parameter + * @return The integer value of the octal bytes. + */ + public static int getOctalBytes( long value, byte[] buf, int offset, int length ) + { + byte[] result = new byte[length]; + int idx = length - 1; + + buf[offset + idx] = 0; + --idx; + buf[offset + idx] = ( byte )' '; + --idx; + + if( value == 0 ) + { + buf[offset + idx] = ( byte )'0'; + --idx; + } + else + { + for( long val = value; idx >= 0 && val > 0; --idx ) + { + buf[offset + idx] = ( byte )( ( byte )'0' + ( byte )( val & 7 ) ); + val = val >> 3; + } + } + + for( ; idx >= 0; --idx ) + { + buf[offset + idx] = ( byte )' '; + } + + return offset + length; + } + + /** + * Compute the checksum of a tar entry header. + * + * @param buf The tar entry's header buffer. + * @return The computed checksum. + */ + public static long computeCheckSum( byte[] buf ) + { + long sum = 0; + + for( int i = 0; i < buf.length; ++i ) + { + sum += 255 & buf[i]; + } + + return sum; + } + + /** + * Parse an entry name from a header buffer. + * + * @param header The header buffer from which to parse. + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @return The header's entry name. + */ + public static StringBuffer parseName( byte[] header, int offset, int length ) + { + StringBuffer result = new StringBuffer( length ); + int end = offset + length; + + for( int i = offset; i < end; ++i ) + { + if( header[i] == 0 ) + { + break; + } + + result.append( ( char )header[i] ); + } + + return result; + } + + /** + * Parse an octal string from a header buffer. This is used for the file + * permission mode value. + * + * @param header The header buffer from which to parse. + * @param offset The offset into the buffer from which to parse. + * @param length The number of header bytes to parse. + * @return The long value of the octal string. + */ + public static long parseOctal( byte[] header, int offset, int length ) + { + long result = 0; + boolean stillPadding = true; + int end = offset + length; + + for( int i = offset; i < end; ++i ) + { + if( header[i] == 0 ) + { + break; + } + + if( header[i] == ( byte )' ' || header[i] == '0' ) + { + if( stillPadding ) + { + continue; + } + + if( header[i] == ( byte )' ' ) + { + break; + } + } + + stillPadding = false; + result = ( result << 3 ) + ( header[i] - '0' ); + } + + return result; + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/AsiExtraField.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/AsiExtraField.java new file mode 100644 index 000000000..e2e3c9122 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/AsiExtraField.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.util.zip.CRC32; +import java.util.zip.ZipException; + +/** + * Adds Unix file permission and UID/GID fields as well as symbolic link + * handling.

                  + * + * This class uses the ASi extra field in the format:

                  + *         Value         Size            Description
                  + *         -----         ----            -----------
                  + * (Unix3) 0x756e        Short           tag for this extra block type
                  + *         TSize         Short           total data size for this block
                  + *         CRC           Long            CRC-32 of the remaining data
                  + *         Mode          Short           file permissions
                  + *         SizDev        Long            symlink'd size OR major/minor dev num
                  + *         UID           Short           user ID
                  + *         GID           Short           group ID
                  + *         (var.)        variable        symbolic link filename
                  + * 
                  taken from appnote.iz (Info-ZIP note, 981119) found at + * ftp://ftp.uu.net/pub/archiving/zip/doc/

                  + * + * Short is two bytes and Long is four bytes in big endian byte and word order, + * device numbers are currently not supported.

                  + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class AsiExtraField implements ZipExtraField, UnixStat, Cloneable +{ + + private final static ZipShort HEADER_ID = new ZipShort( 0x756E ); + + /** + * Standard Unix stat(2) file mode. + * + * @since 1.1 + */ + private int mode = 0; + /** + * User ID. + * + * @since 1.1 + */ + private int uid = 0; + /** + * Group ID. + * + * @since 1.1 + */ + private int gid = 0; + /** + * File this entry points to, if it is a symbolic link.

                  + * + * empty string - if entry is not a symbolic link.

                  + * + * @since 1.1 + */ + private String link = ""; + /** + * Is this an entry for a directory? + * + * @since 1.1 + */ + private boolean dirFlag = false; + + /** + * Instance used to calculate checksums. + * + * @since 1.1 + */ + private CRC32 crc = new CRC32(); + + public AsiExtraField() { } + + /** + * Indicate whether this entry is a directory. + * + * @param dirFlag The new Directory value + * @since 1.1 + */ + public void setDirectory( boolean dirFlag ) + { + this.dirFlag = dirFlag; + mode = getMode( mode ); + } + + /** + * Set the group id. + * + * @param gid The new GroupId value + * @since 1.1 + */ + public void setGroupId( int gid ) + { + this.gid = gid; + } + + /** + * Indicate that this entry is a symbolic link to the given filename. + * + * @param name Name of the file this entry links to, empty String if it is + * not a symbolic link. + * @since 1.1 + */ + public void setLinkedFile( String name ) + { + link = name; + mode = getMode( mode ); + } + + /** + * File mode of this file. + * + * @param mode The new Mode value + * @since 1.1 + */ + public void setMode( int mode ) + { + this.mode = getMode( mode ); + } + + /** + * Set the user id. + * + * @param uid The new UserId value + * @since 1.1 + */ + public void setUserId( int uid ) + { + this.uid = uid; + } + + /** + * Delegate to local file data. + * + * @return The CentralDirectoryData value + * @since 1.1 + */ + public byte[] getCentralDirectoryData() + { + return getLocalFileDataData(); + } + + /** + * Delegate to local file data. + * + * @return The CentralDirectoryLength value + * @since 1.1 + */ + public ZipShort getCentralDirectoryLength() + { + return getLocalFileDataLength(); + } + + /** + * Get the group id. + * + * @return The GroupId value + * @since 1.1 + */ + public int getGroupId() + { + return gid; + } + + /** + * The Header-ID. + * + * @return The HeaderId value + * @since 1.1 + */ + public ZipShort getHeaderId() + { + return HEADER_ID; + } + + /** + * Name of linked file + * + * @return name of the file this entry links to if it is a symbolic link, + * the empty string otherwise. + * @since 1.1 + */ + public String getLinkedFile() + { + return link; + } + + /** + * The actual data to put into local file data - without Header-ID or length + * specifier. + * + * @return The LocalFileDataData value + * @since 1.1 + */ + public byte[] getLocalFileDataData() + { + // CRC will be added later + byte[] data = new byte[getLocalFileDataLength().getValue() - 4]; + System.arraycopy( ( new ZipShort( getMode() ) ).getBytes(), 0, data, 0, 2 ); + + byte[] linkArray = getLinkedFile().getBytes(); + System.arraycopy( ( new ZipLong( linkArray.length ) ).getBytes(), + 0, data, 2, 4 ); + + System.arraycopy( ( new ZipShort( getUserId() ) ).getBytes(), + 0, data, 6, 2 ); + System.arraycopy( ( new ZipShort( getGroupId() ) ).getBytes(), + 0, data, 8, 2 ); + + System.arraycopy( linkArray, 0, data, 10, linkArray.length ); + + crc.reset(); + crc.update( data ); + long checksum = crc.getValue(); + + byte[] result = new byte[data.length + 4]; + System.arraycopy( ( new ZipLong( checksum ) ).getBytes(), 0, result, 0, 4 ); + System.arraycopy( data, 0, result, 4, data.length ); + return result; + } + + /** + * Length of the extra field in the local file data - without Header-ID or + * length specifier. + * + * @return The LocalFileDataLength value + * @since 1.1 + */ + public ZipShort getLocalFileDataLength() + { + return new ZipShort( 4// CRC + + 2// Mode + + 4// SizDev + + 2// UID + + 2// GID + + getLinkedFile().getBytes().length ); + } + + /** + * File mode of this file. + * + * @return The Mode value + * @since 1.1 + */ + public int getMode() + { + return mode; + } + + /** + * Get the user id. + * + * @return The UserId value + * @since 1.1 + */ + public int getUserId() + { + return uid; + } + + /** + * Is this entry a directory? + * + * @return The Directory value + * @since 1.1 + */ + public boolean isDirectory() + { + return dirFlag && !isLink(); + } + + /** + * Is this entry a symbolic link? + * + * @return The Link value + * @since 1.1 + */ + public boolean isLink() + { + return getLinkedFile().length() != 0; + } + + /** + * Populate data from this array as if it was in local file data. + * + * @param data Description of Parameter + * @param offset Description of Parameter + * @param length Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + public void parseFromLocalFileData( byte[] data, int offset, int length ) + throws ZipException + { + + long givenChecksum = ( new ZipLong( data, offset ) ).getValue(); + byte[] tmp = new byte[length - 4]; + System.arraycopy( data, offset + 4, tmp, 0, length - 4 ); + crc.reset(); + crc.update( tmp ); + long realChecksum = crc.getValue(); + if( givenChecksum != realChecksum ) + { + throw new ZipException( "bad CRC checksum " + + Long.toHexString( givenChecksum ) + + " instead of " + + Long.toHexString( realChecksum ) ); + } + + int newMode = ( new ZipShort( tmp, 0 ) ).getValue(); + byte[] linkArray = new byte[( int )( new ZipLong( tmp, 2 ) ).getValue()]; + uid = ( new ZipShort( tmp, 6 ) ).getValue(); + gid = ( new ZipShort( tmp, 8 ) ).getValue(); + + if( linkArray.length == 0 ) + { + link = ""; + } + else + { + System.arraycopy( tmp, 10, linkArray, 0, linkArray.length ); + link = new String( linkArray ); + } + setDirectory( ( newMode & DIR_FLAG ) != 0 ); + setMode( newMode ); + } + + /** + * Get the file mode for given permissions with the correct file type. + * + * @param mode Description of Parameter + * @return The Mode value + * @since 1.1 + */ + protected int getMode( int mode ) + { + int type = FILE_FLAG; + if( isLink() ) + { + type = LINK_FLAG; + } + else if( isDirectory() ) + { + type = DIR_FLAG; + } + return type | ( mode & PERM_MASK ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/ExtraFieldUtils.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/ExtraFieldUtils.java new file mode 100644 index 000000000..0b97ce6a7 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/ExtraFieldUtils.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.util.Hashtable; +import java.util.Vector; +import java.util.zip.ZipException; + +/** + * ZipExtraField related methods + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ExtraFieldUtils +{ + + /** + * Static registry of known extra fields. + * + * @since 1.1 + */ + private static Hashtable implementations; + + static + { + implementations = new Hashtable(); + register( AsiExtraField.class ); + } + + /** + * Create an instance of the approriate ExtraField, falls back to {@link + * UnrecognizedExtraField UnrecognizedExtraField}. + * + * @param headerId Description of Parameter + * @return Description of the Returned Value + * @exception InstantiationException Description of Exception + * @exception IllegalAccessException Description of Exception + * @since 1.1 + */ + public static ZipExtraField createExtraField( ZipShort headerId ) + throws InstantiationException, IllegalAccessException + { + Class c = ( Class )implementations.get( headerId ); + if( c != null ) + { + return ( ZipExtraField )c.newInstance(); + } + UnrecognizedExtraField u = new UnrecognizedExtraField(); + u.setHeaderId( headerId ); + return u; + } + + /** + * Merges the central directory fields of the given ZipExtraFields. + * + * @param data Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public static byte[] mergeCentralDirectoryData( ZipExtraField[] data ) + { + int sum = 4 * data.length; + for( int i = 0; i < data.length; i++ ) + { + sum += data[i].getCentralDirectoryLength().getValue(); + } + byte[] result = new byte[sum]; + int start = 0; + for( int i = 0; i < data.length; i++ ) + { + System.arraycopy( data[i].getHeaderId().getBytes(), + 0, result, start, 2 ); + System.arraycopy( data[i].getCentralDirectoryLength().getBytes(), + 0, result, start + 2, 2 ); + byte[] local = data[i].getCentralDirectoryData(); + System.arraycopy( local, 0, result, start + 4, local.length ); + start += ( local.length + 4 ); + } + return result; + } + + /** + * Merges the local file data fields of the given ZipExtraFields. + * + * @param data Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public static byte[] mergeLocalFileDataData( ZipExtraField[] data ) + { + int sum = 4 * data.length; + for( int i = 0; i < data.length; i++ ) + { + sum += data[i].getLocalFileDataLength().getValue(); + } + byte[] result = new byte[sum]; + int start = 0; + for( int i = 0; i < data.length; i++ ) + { + System.arraycopy( data[i].getHeaderId().getBytes(), + 0, result, start, 2 ); + System.arraycopy( data[i].getLocalFileDataLength().getBytes(), + 0, result, start + 2, 2 ); + byte[] local = data[i].getLocalFileDataData(); + System.arraycopy( local, 0, result, start + 4, local.length ); + start += ( local.length + 4 ); + } + return result; + } + + /** + * Split the array into ExtraFields and populate them with the give data. + * + * @param data Description of Parameter + * @return Description of the Returned Value + * @exception ZipException Description of Exception + * @since 1.1 + */ + public static ZipExtraField[] parse( byte[] data ) + throws ZipException + { + Vector v = new Vector(); + int start = 0; + while( start <= data.length - 4 ) + { + ZipShort headerId = new ZipShort( data, start ); + int length = ( new ZipShort( data, start + 2 ) ).getValue(); + if( start + 4 + length > data.length ) + { + throw new ZipException( "data starting at " + start + " is in unknown format" ); + } + try + { + ZipExtraField ze = createExtraField( headerId ); + ze.parseFromLocalFileData( data, start + 4, length ); + v.addElement( ze ); + } + catch( InstantiationException ie ) + { + throw new ZipException( ie.getMessage() ); + } + catch( IllegalAccessException iae ) + { + throw new ZipException( iae.getMessage() ); + } + start += ( length + 4 ); + } + if( start != data.length ) + {// array not exhausted + throw new ZipException( "data starting at " + start + " is in unknown format" ); + } + + ZipExtraField[] result = new ZipExtraField[v.size()]; + v.copyInto( result ); + return result; + } + + /** + * Register a ZipExtraField implementation.

                  + * + * The given class must have a no-arg constructor and implement the {@link + * ZipExtraField ZipExtraField interface}.

                  + * + * @param c Description of Parameter + * @since 1.1 + */ + public static void register( Class c ) + { + try + { + ZipExtraField ze = ( ZipExtraField )c.newInstance(); + implementations.put( ze.getHeaderId(), c ); + } + catch( ClassCastException cc ) + { + throw new RuntimeException( c + + " doesn\'t implement ZipExtraField" ); + } + catch( InstantiationException ie ) + { + throw new RuntimeException( c + " is not a concrete class" ); + } + catch( IllegalAccessException ie ) + { + throw new RuntimeException( c + + "\'s no-arg constructor is not public" ); + } + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/UnixStat.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/UnixStat.java new file mode 100644 index 000000000..e238ed32e --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/UnixStat.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Constants from stat.h on Unix systems. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public interface UnixStat +{ + + /** + * Bits used for permissions (and sticky bit) + * + * @since 1.1 + */ + int PERM_MASK = 07777; + /** + * Indicates symbolic links. + * + * @since 1.1 + */ + int LINK_FLAG = 0120000; + /** + * Indicates plain files. + * + * @since 1.1 + */ + int FILE_FLAG = 0100000; + /** + * Indicates directories. + * + * @since 1.1 + */ + int DIR_FLAG = 040000; + + // ---------------------------------------------------------- + // somewhat arbitrary choices that are quite common for shared + // installations + // ----------------------------------------------------------- + + /** + * Default permissions for symbolic links. + * + * @since 1.1 + */ + int DEFAULT_LINK_PERM = 0777; + /** + * Default permissions for directories. + * + * @since 1.1 + */ + int DEFAULT_DIR_PERM = 0755; + /** + * Default permissions for plain files. + * + * @since 1.1 + */ + int DEFAULT_FILE_PERM = 0644; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/UnrecognizedExtraField.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/UnrecognizedExtraField.java new file mode 100644 index 000000000..092e1c60b --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/UnrecognizedExtraField.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Simple placeholder for all those extra fields we don't want to deal with.

                  + * + * Assumes local file data and central directory entries are identical - unless + * told the opposite.

                  + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class UnrecognizedExtraField implements ZipExtraField +{ + + /** + * Extra field data in central directory - without Header-ID or length + * specifier. + * + * @since 1.1 + */ + private byte[] centralData; + + /** + * The Header-ID. + * + * @since 1.1 + */ + private ZipShort headerId; + + /** + * Extra field data in local file data - without Header-ID or length + * specifier. + * + * @since 1.1 + */ + private byte[] localData; + + public void setCentralDirectoryData( byte[] data ) + { + centralData = data; + } + + public void setHeaderId( ZipShort headerId ) + { + this.headerId = headerId; + } + + public void setLocalFileDataData( byte[] data ) + { + localData = data; + } + + public byte[] getCentralDirectoryData() + { + if( centralData != null ) + { + return centralData; + } + return getLocalFileDataData(); + } + + public ZipShort getCentralDirectoryLength() + { + if( centralData != null ) + { + return new ZipShort( centralData.length ); + } + return getLocalFileDataLength(); + } + + public ZipShort getHeaderId() + { + return headerId; + } + + public byte[] getLocalFileDataData() + { + return localData; + } + + public ZipShort getLocalFileDataLength() + { + return new ZipShort( localData.length ); + } + + public void parseFromLocalFileData( byte[] data, int offset, int length ) + { + byte[] tmp = new byte[length]; + System.arraycopy( data, offset, tmp, 0, length ); + setLocalFileDataData( tmp ); + } +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipEntry.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipEntry.java new file mode 100644 index 000000000..078203ae2 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipEntry.java @@ -0,0 +1,435 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Vector; +import java.util.zip.ZipException; + +/** + * Extension that adds better handling of extra fields and provides access to + * the internal and external file attributes. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipEntry extends java.util.zip.ZipEntry +{ + + /** + * Helper for JDK 1.1 + * + * @since 1.2 + */ + private static Method setCompressedSizeMethod = null; + /** + * Helper for JDK 1.1 + * + * @since 1.2 + */ + private static Object lockReflection = new Object(); + /** + * Helper for JDK 1.1 + * + * @since 1.2 + */ + private static boolean triedToGetMethod = false; + + private int internalAttributes = 0; + private long externalAttributes = 0; + private Vector extraFields = new Vector(); + + /** + * Helper for JDK 1.1 <-> 1.2 incompatibility. + * + * @since 1.2 + */ + private Long compressedSize = null; + + /** + * Creates a new zip entry with the specified name. + * + * @param name Description of Parameter + * @since 1.1 + */ + public ZipEntry( String name ) + { + super( name ); + } + + /** + * Creates a new zip entry with fields taken from the specified zip entry. + * + * @param entry Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + public ZipEntry( java.util.zip.ZipEntry entry ) + throws ZipException + { + /* + * REVISIT: call super(entry) instead of this stuff in Ant2, + * "copy constructor" has not been available in JDK 1.1 + */ + super( entry.getName() ); + + setComment( entry.getComment() ); + setMethod( entry.getMethod() ); + setTime( entry.getTime() ); + + long size = entry.getSize(); + if( size > 0 ) + { + setSize( size ); + } + long cSize = entry.getCompressedSize(); + if( cSize > 0 ) + { + setComprSize( cSize ); + } + long crc = entry.getCrc(); + if( crc > 0 ) + { + setCrc( crc ); + } + + byte[] extra = entry.getExtra(); + if( extra != null ) + { + setExtraFields( ExtraFieldUtils.parse( extra ) ); + } + else + { + // initializes extra data to an empty byte array + setExtra(); + } + } + + /** + * Creates a new zip entry with fields taken from the specified zip entry. + * + * @param entry Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + public ZipEntry( ZipEntry entry ) + throws ZipException + { + this( ( java.util.zip.ZipEntry )entry ); + setInternalAttributes( entry.getInternalAttributes() ); + setExternalAttributes( entry.getExternalAttributes() ); + setExtraFields( entry.getExtraFields() ); + } + + /** + * Try to get a handle to the setCompressedSize method. + * + * @since 1.2 + */ + private static void checkSCS() + { + if( !triedToGetMethod ) + { + synchronized( lockReflection ) + { + triedToGetMethod = true; + try + { + setCompressedSizeMethod = + java.util.zip.ZipEntry.class.getMethod( "setCompressedSize", + new Class[]{Long.TYPE} ); + } + catch( NoSuchMethodException nse ) + { + } + } + } + } + + /** + * Are we running JDK 1.2 or higher? + * + * @return Description of the Returned Value + * @since 1.2 + */ + private static boolean haveSetCompressedSize() + { + checkSCS(); + return setCompressedSizeMethod != null; + } + + /** + * Invoke setCompressedSize via reflection. + * + * @param ze Description of Parameter + * @param size Description of Parameter + * @since 1.2 + */ + private static void performSetCompressedSize( ZipEntry ze, long size ) + { + Long[] s = {new Long( size )}; + try + { + setCompressedSizeMethod.invoke( ze, s ); + } + catch( InvocationTargetException ite ) + { + Throwable nested = ite.getTargetException(); + throw new RuntimeException( "Exception setting the compressed size " + + "of " + ze + ": " + + nested.getMessage() ); + } + catch( Throwable other ) + { + throw new RuntimeException( "Exception setting the compressed size " + + "of " + ze + ": " + + other.getMessage() ); + } + } + + /** + * Make this class work in JDK 1.1 like a 1.2 class.

                  + * + * This either stores the size for later usage or invokes setCompressedSize + * via reflection.

                  + * + * @param size The new ComprSize value + * @since 1.2 + */ + public void setComprSize( long size ) + { + if( haveSetCompressedSize() ) + { + performSetCompressedSize( this, size ); + } + else + { + compressedSize = new Long( size ); + } + } + + /** + * Sets the external file attributes. + * + * @param value The new ExternalAttributes value + * @since 1.1 + */ + public void setExternalAttributes( long value ) + { + externalAttributes = value; + } + + /** + * Throws an Exception if extra data cannot be parsed into extra fields. + * + * @param extra The new Extra value + * @exception RuntimeException Description of Exception + * @since 1.1 + */ + public void setExtra( byte[] extra ) + throws RuntimeException + { + try + { + setExtraFields( ExtraFieldUtils.parse( extra ) ); + } + catch( Exception e ) + { + throw new RuntimeException( e.getMessage() ); + } + } + + /** + * Replaces all currently attached extra fields with the new array. + * + * @param fields The new ExtraFields value + * @since 1.1 + */ + public void setExtraFields( ZipExtraField[] fields ) + { + extraFields.removeAllElements(); + for( int i = 0; i < fields.length; i++ ) + { + extraFields.addElement( fields[i] ); + } + setExtra(); + } + + /** + * Sets the internal file attributes. + * + * @param value The new InternalAttributes value + * @since 1.1 + */ + public void setInternalAttributes( int value ) + { + internalAttributes = value; + } + + /** + * Retrieves the extra data for the central directory. + * + * @return The CentralDirectoryExtra value + * @since 1.1 + */ + public byte[] getCentralDirectoryExtra() + { + return ExtraFieldUtils.mergeCentralDirectoryData( getExtraFields() ); + } + + /** + * Override to make this class work in JDK 1.1 like a 1.2 class. + * + * @return The CompressedSize value + * @since 1.2 + */ + public long getCompressedSize() + { + if( compressedSize != null ) + { + // has been set explicitly and we are running in a 1.1 VM + return compressedSize.longValue(); + } + return super.getCompressedSize(); + } + + /** + * Retrieves the external file attributes. + * + * @return The ExternalAttributes value + * @since 1.1 + */ + public long getExternalAttributes() + { + return externalAttributes; + } + + /** + * Retrieves extra fields. + * + * @return The ExtraFields value + * @since 1.1 + */ + public ZipExtraField[] getExtraFields() + { + ZipExtraField[] result = new ZipExtraField[extraFields.size()]; + extraFields.copyInto( result ); + return result; + } + + /** + * Retrieves the internal file attributes. + * + * @return The InternalAttributes value + * @since 1.1 + */ + public int getInternalAttributes() + { + return internalAttributes; + } + + /** + * Retrieves the extra data for the local file data. + * + * @return The LocalFileDataExtra value + * @since 1.1 + */ + public byte[] getLocalFileDataExtra() + { + byte[] extra = getExtra(); + return extra != null ? extra : new byte[0]; + } + + /** + * Adds an extra fields - replacing an already present extra field of the + * same type. + * + * @param ze The feature to be added to the ExtraField attribute + * @since 1.1 + */ + public void addExtraField( ZipExtraField ze ) + { + ZipShort type = ze.getHeaderId(); + boolean done = false; + for( int i = 0; !done && i < extraFields.size(); i++ ) + { + if( ( ( ZipExtraField )extraFields.elementAt( i ) ).getHeaderId().equals( type ) ) + { + extraFields.setElementAt( ze, i ); + done = true; + } + } + if( !done ) + { + extraFields.addElement( ze ); + } + setExtra(); + } + + /** + * Overwrite clone + * + * @return Description of the Returned Value + * @since 1.1 + */ + public Object clone() + { + ZipEntry e = null; + try + { + e = new ZipEntry( ( java.util.zip.ZipEntry )super.clone() ); + } + catch( Exception ex ) + { + // impossible as extra data is in correct format + ex.printStackTrace(); + } + e.setInternalAttributes( getInternalAttributes() ); + e.setExternalAttributes( getExternalAttributes() ); + e.setExtraFields( getExtraFields() ); + return e; + } + + /** + * Remove an extra fields. + * + * @param type Description of Parameter + * @since 1.1 + */ + public void removeExtraField( ZipShort type ) + { + boolean done = false; + for( int i = 0; !done && i < extraFields.size(); i++ ) + { + if( ( ( ZipExtraField )extraFields.elementAt( i ) ).getHeaderId().equals( type ) ) + { + extraFields.removeElementAt( i ); + done = true; + } + } + if( !done ) + { + throw new java.util.NoSuchElementException(); + } + setExtra(); + } + + /** + * Unfortunately {@link java.util.zip.ZipOutputStream + * java.util.zip.ZipOutputStream} seems to access the extra data directly, + * so overriding getExtra doesn't help - we need to modify super's data + * directly. + * + * @since 1.1 + */ + protected void setExtra() + { + super.setExtra( ExtraFieldUtils.mergeLocalFileDataData( getExtraFields() ) ); + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipExtraField.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipExtraField.java new file mode 100644 index 000000000..534530706 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipExtraField.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.util.zip.ZipException; + +/** + * General format of extra field data.

                  + * + * Extra fields usually appear twice per file, once in the local file data and + * once in the central directory. Usually they are the same, but they don't have + * to be. {@link java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream} + * will only use the local file data in both places.

                  + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public interface ZipExtraField +{ + + /** + * The Header-ID. + * + * @return The HeaderId value + * @since 1.1 + */ + ZipShort getHeaderId(); + + /** + * Length of the extra field in the local file data - without Header-ID or + * length specifier. + * + * @return The LocalFileDataLength value + * @since 1.1 + */ + ZipShort getLocalFileDataLength(); + + /** + * Length of the extra field in the central directory - without Header-ID or + * length specifier. + * + * @return The CentralDirectoryLength value + * @since 1.1 + */ + ZipShort getCentralDirectoryLength(); + + /** + * The actual data to put into local file data - without Header-ID or length + * specifier. + * + * @return The LocalFileDataData value + * @since 1.1 + */ + byte[] getLocalFileDataData(); + + /** + * The actual data to put central directory - without Header-ID or length + * specifier. + * + * @return The CentralDirectoryData value + * @since 1.1 + */ + byte[] getCentralDirectoryData(); + + /** + * Populate data from this array as if it was in local file data. + * + * @param data Description of Parameter + * @param offset Description of Parameter + * @param length Description of Parameter + * @exception ZipException Description of Exception + * @since 1.1 + */ + void parseFromLocalFileData( byte[] data, int offset, int length ) + throws ZipException; +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipLong.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipLong.java new file mode 100644 index 000000000..4b3573770 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipLong.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Utility class that represents a four byte integer with conversion rules for + * the big endian byte order of ZIP files. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipLong implements Cloneable +{ + + private long value; + + /** + * Create instance from a number. + * + * @param value Description of Parameter + * @since 1.1 + */ + public ZipLong( long value ) + { + this.value = value; + } + + /** + * Create instance from bytes. + * + * @param bytes Description of Parameter + * @since 1.1 + */ + public ZipLong( byte[] bytes ) + { + this( bytes, 0 ); + } + + /** + * Create instance from the four bytes starting at offset. + * + * @param bytes Description of Parameter + * @param offset Description of Parameter + * @since 1.1 + */ + public ZipLong( byte[] bytes, int offset ) + { + value = ( bytes[offset + 3] << 24 ) & 0xFF000000l; + value += ( bytes[offset + 2] << 16 ) & 0xFF0000; + value += ( bytes[offset + 1] << 8 ) & 0xFF00; + value += ( bytes[offset] & 0xFF ); + } + + /** + * Get value as two bytes in big endian byte order. + * + * @return The Bytes value + * @since 1.1 + */ + public byte[] getBytes() + { + byte[] result = new byte[4]; + result[0] = ( byte )( ( value & 0xFF ) ); + result[1] = ( byte )( ( value & 0xFF00 ) >> 8 ); + result[2] = ( byte )( ( value & 0xFF0000 ) >> 16 ); + result[3] = ( byte )( ( value & 0xFF000000l ) >> 24 ); + return result; + } + + /** + * Get value as Java int. + * + * @return The Value value + * @since 1.1 + */ + public long getValue() + { + return value; + } + + /** + * Override to make two instances with same value equal. + * + * @param o Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public boolean equals( Object o ) + { + if( o == null || !( o instanceof ZipLong ) ) + { + return false; + } + return value == ( ( ZipLong )o ).getValue(); + } + + /** + * Override to make two instances with same value equal. + * + * @return Description of the Returned Value + * @since 1.1 + */ + public int hashCode() + { + return ( int )value; + } + +}// ZipLong diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipOutputStream.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipOutputStream.java new file mode 100644 index 000000000..5ab382814 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipOutputStream.java @@ -0,0 +1,712 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.util.Date; +import java.util.Hashtable; +import java.util.Vector; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.ZipException; + +/** + * Reimplementation of {@link java.util.zip.ZipOutputStream + * java.util.zip.ZipOutputStream} that does handle the extended functionality of + * this package, especially internal/external file attributes and extra fields + * with different layouts for local file data and central directory entries.

                  + * + * This implementation will use a Data Descriptor to store size and CRC + * information for DEFLATED entries, this means, you don't need to calculate + * them yourself. Unfortunately this is not possible for the STORED method, here + * setting the CRC and uncompressed size information is required before {@link + * #putNextEntry putNextEntry} will be called.

                  + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipOutputStream extends DeflaterOutputStream +{ + + /** + * Helper, a 0 as ZipShort. + * + * @since 1.1 + */ + private final static byte[] ZERO = {0, 0}; + + /** + * Helper, a 0 as ZipLong. + * + * @since 1.1 + */ + private final static byte[] LZERO = {0, 0, 0, 0}; + + /** + * Compression method for deflated entries. + * + * @since 1.1 + */ + public final static int DEFLATED = ZipEntry.DEFLATED; + + /** + * Compression method for deflated entries. + * + * @since 1.1 + */ + public final static int STORED = ZipEntry.STORED; + + /* + * Various ZIP constants + */ + /** + * local file header signature + * + * @since 1.1 + */ + protected final static ZipLong LFH_SIG = new ZipLong( 0X04034B50L ); + /** + * data descriptor signature + * + * @since 1.1 + */ + protected final static ZipLong DD_SIG = new ZipLong( 0X08074B50L ); + /** + * central file header signature + * + * @since 1.1 + */ + protected final static ZipLong CFH_SIG = new ZipLong( 0X02014B50L ); + /** + * end of central dir signature + * + * @since 1.1 + */ + protected final static ZipLong EOCD_SIG = new ZipLong( 0X06054B50L ); + + /** + * Smallest date/time ZIP can handle. + * + * @since 1.1 + */ + private final static ZipLong DOS_TIME_MIN = new ZipLong( 0x00002100L ); + + /** + * The file comment. + * + * @since 1.1 + */ + private String comment = ""; + + /** + * Compression level for next entry. + * + * @since 1.1 + */ + private int level = Deflater.DEFAULT_COMPRESSION; + + /** + * Default compression method for next entry. + * + * @since 1.1 + */ + private int method = DEFLATED; + + /** + * List of ZipEntries written so far. + * + * @since 1.1 + */ + private Vector entries = new Vector(); + + /** + * CRC instance to avoid parsing DEFLATED data twice. + * + * @since 1.1 + */ + private CRC32 crc = new CRC32(); + + /** + * Count the bytes written to out. + * + * @since 1.1 + */ + private long written = 0; + + /** + * Data for current entry started here. + * + * @since 1.1 + */ + private long dataStart = 0; + + /** + * Start of central directory. + * + * @since 1.1 + */ + private ZipLong cdOffset = new ZipLong( 0 ); + + /** + * Length of central directory. + * + * @since 1.1 + */ + private ZipLong cdLength = new ZipLong( 0 ); + + /** + * Holds the offsets of the LFH starts for each entry + * + * @since 1.1 + */ + private Hashtable offsets = new Hashtable(); + + /** + * The encoding to use for filenames and the file comment.

                  + * + * For a list of possible values see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * . Defaults to the platform's default character encoding.

                  + * + * @since 1.3 + */ + private String encoding = null; + + /** + * Current entry. + * + * @since 1.1 + */ + private ZipEntry entry; + + /** + * Creates a new ZIP OutputStream filtering the underlying stream. + * + * @param out Description of Parameter + * @since 1.1 + */ + public ZipOutputStream( OutputStream out ) + { + super( out, new Deflater( Deflater.DEFAULT_COMPRESSION, true ) ); + } + + /** + * Convert a Date object to a DOS date/time field.

                  + * + * Stolen from InfoZip's fileio.c

                  + * + * @param time Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + protected static ZipLong toDosTime( Date time ) + { + int year = time.getYear() + 1900; + int month = time.getMonth() + 1; + if( year < 1980 ) + { + return DOS_TIME_MIN; + } + long value = ( ( year - 1980 ) << 25 ) + | ( month << 21 ) + | ( time.getDate() << 16 ) + | ( time.getHours() << 11 ) + | ( time.getMinutes() << 5 ) + | ( time.getSeconds() >> 1 ); + + byte[] result = new byte[4]; + result[0] = ( byte )( ( value & 0xFF ) ); + result[1] = ( byte )( ( value & 0xFF00 ) >> 8 ); + result[2] = ( byte )( ( value & 0xFF0000 ) >> 16 ); + result[3] = ( byte )( ( value & 0xFF000000l ) >> 24 ); + return new ZipLong( result ); + } + + /** + * Set the file comment. + * + * @param comment The new Comment value + * @since 1.1 + */ + public void setComment( String comment ) + { + this.comment = comment; + } + + /** + * The encoding to use for filenames and the file comment.

                  + * + * For a list of possible values see + * http://java.sun.com/products/jdk/1.2/docs/guide/internat/encoding.doc.html + * . Defaults to the platform's default character encoding.

                  + * + * @param encoding The new Encoding value + * @since 1.3 + */ + public void setEncoding( String encoding ) + { + this.encoding = encoding; + } + + /** + * Sets the compression level for subsequent entries.

                  + * + * Default is Deflater.DEFAULT_COMPRESSION.

                  + * + * @param level The new Level value + * @since 1.1 + */ + public void setLevel( int level ) + { + this.level = level; + } + + /** + * Sets the default compression method for subsequent entries.

                  + * + * Default is DEFLATED.

                  + * + * @param method The new Method value + * @since 1.1 + */ + public void setMethod( int method ) + { + this.method = method; + } + + /** + * The encoding to use for filenames and the file comment. + * + * @return null if using the platform's default character encoding. + * @since 1.3 + */ + public String getEncoding() + { + return encoding; + } + + /** + * Writes all necessary data for this entry. + * + * @exception IOException Description of Exception + * @since 1.1 + */ + public void closeEntry() + throws IOException + { + if( entry == null ) + { + return; + } + + long realCrc = crc.getValue(); + crc.reset(); + + if( entry.getMethod() == DEFLATED ) + { + def.finish(); + while( !def.finished() ) + { + deflate(); + } + + entry.setSize( def.getTotalIn() ); + entry.setComprSize( def.getTotalOut() ); + entry.setCrc( realCrc ); + + def.reset(); + + written += entry.getCompressedSize(); + } + else + { + if( entry.getCrc() != realCrc ) + { + throw new ZipException( "bad CRC checksum for entry " + + entry.getName() + ": " + + Long.toHexString( entry.getCrc() ) + + " instead of " + + Long.toHexString( realCrc ) ); + } + + if( entry.getSize() != written - dataStart ) + { + throw new ZipException( "bad size for entry " + + entry.getName() + ": " + + entry.getSize() + + " instead of " + + ( written - dataStart ) ); + } + + } + + writeDataDescriptor( entry ); + entry = null; + } + + /* + * Found out by experiment, that DeflaterOutputStream.close() + * will call finish() - so we don't need to override close + * ourselves. + */ + /** + * Finishs writing the contents and closes this as well as the underlying + * stream. + * + * @exception IOException Description of Exception + * @since 1.1 + */ + public void finish() + throws IOException + { + closeEntry(); + cdOffset = new ZipLong( written ); + for( int i = 0; i < entries.size(); i++ ) + { + writeCentralFileHeader( ( ZipEntry )entries.elementAt( i ) ); + } + cdLength = new ZipLong( written - cdOffset.getValue() ); + writeCentralDirectoryEnd(); + offsets.clear(); + entries.removeAllElements(); + } + + /** + * Begin writing next entry. + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + public void putNextEntry( ZipEntry ze ) + throws IOException + { + closeEntry(); + + entry = ze; + entries.addElement( entry ); + + if( entry.getMethod() == -1 ) + {// not specified + entry.setMethod( method ); + } + + if( entry.getTime() == -1 ) + {// not specified + entry.setTime( System.currentTimeMillis() ); + } + + if( entry.getMethod() == STORED ) + { + if( entry.getSize() == -1 ) + { + throw new ZipException( "uncompressed size is required for STORED method" ); + } + if( entry.getCrc() == -1 ) + { + throw new ZipException( "crc checksum is required for STORED method" ); + } + entry.setComprSize( entry.getSize() ); + } + else + { + def.setLevel( level ); + } + writeLocalFileHeader( entry ); + } + + /** + * Writes bytes to ZIP entry.

                  + * + * Override is necessary to support STORED entries, as well as calculationg + * CRC automatically for DEFLATED entries.

                  + * + * @param b Description of Parameter + * @param offset Description of Parameter + * @param length Description of Parameter + * @exception IOException Description of Exception + */ + public void write( byte[] b, int offset, int length ) + throws IOException + { + if( entry.getMethod() == DEFLATED ) + { + super.write( b, offset, length ); + } + else + { + out.write( b, offset, length ); + written += length; + } + crc.update( b, offset, length ); + } + + /** + * Retrieve the bytes for the given String in the encoding set for this + * Stream. + * + * @param name Description of Parameter + * @return The Bytes value + * @exception ZipException Description of Exception + * @since 1.3 + */ + protected byte[] getBytes( String name ) + throws ZipException + { + if( encoding == null ) + { + return name.getBytes(); + } + else + { + try + { + return name.getBytes( encoding ); + } + catch( UnsupportedEncodingException uee ) + { + throw new ZipException( uee.getMessage() ); + } + } + } + + /** + * Writes the "End of central dir record" + * + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeCentralDirectoryEnd() + throws IOException + { + out.write( EOCD_SIG.getBytes() ); + + // disk numbers + out.write( ZERO ); + out.write( ZERO ); + + // number of entries + byte[] num = ( new ZipShort( entries.size() ) ).getBytes(); + out.write( num ); + out.write( num ); + + // length and location of CD + out.write( cdLength.getBytes() ); + out.write( cdOffset.getBytes() ); + + // ZIP file comment + byte[] data = getBytes( comment ); + out.write( ( new ZipShort( data.length ) ).getBytes() ); + out.write( data ); + } + + /** + * Writes the central file header entry + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeCentralFileHeader( ZipEntry ze ) + throws IOException + { + out.write( CFH_SIG.getBytes() ); + written += 4; + + // version made by + out.write( ( new ZipShort( 20 ) ).getBytes() ); + written += 2; + + // version needed to extract + // general purpose bit flag + if( ze.getMethod() == DEFLATED ) + { + // requires version 2 as we are going to store length info + // in the data descriptor + out.write( ( new ZipShort( 20 ) ).getBytes() ); + + // bit3 set to signal, we use a data descriptor + out.write( ( new ZipShort( 8 ) ).getBytes() ); + } + else + { + out.write( ( new ZipShort( 10 ) ).getBytes() ); + out.write( ZERO ); + } + written += 4; + + // compression method + out.write( ( new ZipShort( ze.getMethod() ) ).getBytes() ); + written += 2; + + // last mod. time and date + out.write( toDosTime( new Date( ze.getTime() ) ).getBytes() ); + written += 4; + + // CRC + // compressed length + // uncompressed length + out.write( ( new ZipLong( ze.getCrc() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getCompressedSize() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); + written += 12; + + // file name length + byte[] name = getBytes( ze.getName() ); + out.write( ( new ZipShort( name.length ) ).getBytes() ); + written += 2; + + // extra field length + byte[] extra = ze.getCentralDirectoryExtra(); + out.write( ( new ZipShort( extra.length ) ).getBytes() ); + written += 2; + + // file comment length + String comm = ze.getComment(); + if( comm == null ) + { + comm = ""; + } + byte[] comment = getBytes( comm ); + out.write( ( new ZipShort( comment.length ) ).getBytes() ); + written += 2; + + // disk number start + out.write( ZERO ); + written += 2; + + // internal file attributes + out.write( ( new ZipShort( ze.getInternalAttributes() ) ).getBytes() ); + written += 2; + + // external file attributes + out.write( ( new ZipLong( ze.getExternalAttributes() ) ).getBytes() ); + written += 4; + + // relative offset of LFH + out.write( ( ( ZipLong )offsets.get( ze ) ).getBytes() ); + written += 4; + + // file name + out.write( name ); + written += name.length; + + // extra field + out.write( extra ); + written += extra.length; + + // file comment + out.write( comment ); + written += comment.length; + } + + /** + * Writes the data descriptor entry + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeDataDescriptor( ZipEntry ze ) + throws IOException + { + if( ze.getMethod() != DEFLATED ) + { + return; + } + out.write( DD_SIG.getBytes() ); + out.write( ( new ZipLong( entry.getCrc() ) ).getBytes() ); + out.write( ( new ZipLong( entry.getCompressedSize() ) ).getBytes() ); + out.write( ( new ZipLong( entry.getSize() ) ).getBytes() ); + written += 16; + } + + /** + * Writes the local file header entry + * + * @param ze Description of Parameter + * @exception IOException Description of Exception + * @since 1.1 + */ + protected void writeLocalFileHeader( ZipEntry ze ) + throws IOException + { + offsets.put( ze, new ZipLong( written ) ); + + out.write( LFH_SIG.getBytes() ); + written += 4; + + // version needed to extract + // general purpose bit flag + if( ze.getMethod() == DEFLATED ) + { + // requires version 2 as we are going to store length info + // in the data descriptor + out.write( ( new ZipShort( 20 ) ).getBytes() ); + + // bit3 set to signal, we use a data descriptor + out.write( ( new ZipShort( 8 ) ).getBytes() ); + } + else + { + out.write( ( new ZipShort( 10 ) ).getBytes() ); + out.write( ZERO ); + } + written += 4; + + // compression method + out.write( ( new ZipShort( ze.getMethod() ) ).getBytes() ); + written += 2; + + // last mod. time and date + out.write( toDosTime( new Date( ze.getTime() ) ).getBytes() ); + written += 4; + + // CRC + // compressed length + // uncompressed length + if( ze.getMethod() == DEFLATED ) + { + out.write( LZERO ); + out.write( LZERO ); + out.write( LZERO ); + } + else + { + out.write( ( new ZipLong( ze.getCrc() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); + out.write( ( new ZipLong( ze.getSize() ) ).getBytes() ); + } + written += 12; + + // file name length + byte[] name = getBytes( ze.getName() ); + out.write( ( new ZipShort( name.length ) ).getBytes() ); + written += 2; + + // extra field length + byte[] extra = ze.getLocalFileDataExtra(); + out.write( ( new ZipShort( extra.length ) ).getBytes() ); + written += 2; + + // file name + out.write( name ); + written += name.length; + + // extra field + out.write( extra ); + written += extra.length; + + dataStart = written; + } + +} diff --git a/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipShort.java b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipShort.java new file mode 100644 index 000000000..b06f040e1 --- /dev/null +++ b/proposal/myrmidon/src/todo/org/apache/tools/zip/ZipShort.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE file. + */ +package org.apache.tools.zip; + +/** + * Utility class that represents a two byte integer with conversion rules for + * the big endian byte order of ZIP files. + * + * @author Stefan Bodewig + * @version $Revision$ + */ +public class ZipShort implements Cloneable +{ + + private int value; + + /** + * Create instance from a number. + * + * @param value Description of Parameter + * @since 1.1 + */ + public ZipShort( int value ) + { + this.value = value; + } + + /** + * Create instance from bytes. + * + * @param bytes Description of Parameter + * @since 1.1 + */ + public ZipShort( byte[] bytes ) + { + this( bytes, 0 ); + } + + /** + * Create instance from the two bytes starting at offset. + * + * @param bytes Description of Parameter + * @param offset Description of Parameter + * @since 1.1 + */ + public ZipShort( byte[] bytes, int offset ) + { + value = ( bytes[offset + 1] << 8 ) & 0xFF00; + value += ( bytes[offset] & 0xFF ); + } + + /** + * Get value as two bytes in big endian byte order. + * + * @return The Bytes value + * @since 1.1 + */ + public byte[] getBytes() + { + byte[] result = new byte[2]; + result[0] = ( byte )( value & 0xFF ); + result[1] = ( byte )( ( value & 0xFF00 ) >> 8 ); + return result; + } + + /** + * Get value as Java int. + * + * @return The Value value + * @since 1.1 + */ + public int getValue() + { + return value; + } + + /** + * Override to make two instances with same value equal. + * + * @param o Description of Parameter + * @return Description of the Returned Value + * @since 1.1 + */ + public boolean equals( Object o ) + { + if( o == null || !( o instanceof ZipShort ) ) + { + return false; + } + return value == ( ( ZipShort )o ).getValue(); + } + + /** + * Override to make two instances with same value equal. + * + * @return Description of the Returned Value + * @since 1.1 + */ + public int hashCode() + { + return value; + } + +}// ZipShort