What is the expected behaviour when Gradle casts collections of K,V properties with null values

Hi,

My build is using the combination of a CopyTask along with property expansion using org.apache.tools.ant.filters.ReplaceTokens.

Gradle makes this work by casting a j.u.LinkedHashMap collection to a j.u.Hashtable. In the properties collection many of the values are null. As they reasonably can be.

But Gradle has a problem casting K,V pairs when the value is null.

I am seeing this stack trace.

  whitingjr@f19lite GradleSampleNPEProject $ gradle expandProperties --stacktrace

FAILURE: Build failed with an exception.

  • Where: Build file ‘/somewhereoverthefilesystem/GradleSampleNPEProject/build.gradle’ line: 4

  • What went wrong: A problem occurred evaluating root project ‘GradleSampleNPEProject’. > Could not copy file ‘/somewhereoverthefilesystem/GradleSampleNPEProject/build.gradle’ to ‘/somewhereoverthefilesystem/GradleSampleNPEProject/expanded.txt’.

  • Try: Run with --info or --debug option to get more log output.

  • Exception is: org.gradle.api.GradleScriptException: A problem occurred evaluating root project ‘GradleSampleNPEProject’.

at org.gradle.groovy.scripts.internal.DefaultScriptRunnerFactory$ScriptRunnerImpl.run(DefaultScriptRunnerFactory.java:54)

at org.gradle.configuration.DefaultScriptPluginFactory$ScriptPluginImpl.apply(DefaultScriptPluginFactory.java:132)

at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:38)

at org.gradle.configuration.project.BuildScriptProcessor.execute(BuildScriptProcessor.java:25)

at org.gradle.configuration.project.ConfigureActionsProjectEvaluator.evaluate(ConfigureActionsProjectEvaluator.java:34)

at org.gradle.configuration.project.LifecycleProjectEvaluator.evaluate(LifecycleProjectEvaluator.java:55)

at org.gradle.api.internal.project.AbstractProject.evaluate(AbstractProject.java:507)

at org.gradle.api.internal.project.AbstractProject.evaluate(AbstractProject.java:82)

at org.gradle.configuration.DefaultBuildConfigurer.configure(DefaultBuildConfigurer.java:31)

at org.gradle.initialization.DefaultGradleLauncher.doBuildStages(DefaultGradleLauncher.java:142)

at org.gradle.initialization.DefaultGradleLauncher.doBuild(DefaultGradleLauncher.java:113)

at org.gradle.initialization.DefaultGradleLauncher.run(DefaultGradleLauncher.java:81)

at org.gradle.launcher.exec.InProcessBuildActionExecuter$DefaultBuildController.run(InProcessBuildActionExecuter.java:64)

at org.gradle.launcher.cli.ExecuteBuildAction.run(ExecuteBuildAction.java:33)

at org.gradle.launcher.cli.ExecuteBuildAction.run(ExecuteBuildAction.java:24)

at org.gradle.launcher.exec.InProcessBuildActionExecuter.execute(InProcessBuildActionExecuter.java:35)

at org.gradle.launcher.daemon.server.exec.ExecuteBuild.doBuild(ExecuteBuild.java:45)

at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:34)

at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:125)

at org.gradle.launcher.daemon.server.exec.WatchForDisconnection.execute(WatchForDisconnection.java:42)

at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:125)

at org.gradle.launcher.daemon.server.exec.ResetDeprecationLogger.execute(ResetDeprecationLogger.java:24)

at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:125)

at org.gradle.launcher.daemon.server.exec.StartStopIfBuildAndStop.execute(StartStopIfBuildAndStop.java:33)

at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:125)

at org.gradle.launcher.daemon.server.exec.ReturnResult.execute(ReturnResult.java:34)

at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:125)

at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:71)

at org.gradle.launcher.daemon.server.exec.ForwardClientInput$2.call(ForwardClientInput.java:69)

at org.gradle.util.Swapper.swap(Swapper.java:38)

at org.gradle.launcher.daemon.server.exec.ForwardClientInput.execute(ForwardClientInput.java:69)

at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:125)

at org.gradle.launcher.daemon.server.exec.LogToClient.doBuild(LogToClient.java:60)

at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:34)

at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:125)

at org.gradle.launcher.daemon.server.exec.EstablishBuildEnvironment.doBuild(EstablishBuildEnvironment.java:60)

at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:34)

at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:125)

at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy$1.run(StartBuildOrRespondWithBusy.java:45)

at org.gradle.launcher.daemon.server.DaemonStateCoordinator.runCommand(DaemonStateCoordinator.java:186)

at org.gradle.launcher.daemon.server.exec.StartBuildOrRespondWithBusy.doBuild(StartBuildOrRespondWithBusy.java:49)

at org.gradle.launcher.daemon.server.exec.BuildCommandOnly.execute(BuildCommandOnly.java:34)

at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:125)

at org.gradle.launcher.daemon.server.exec.HandleStop.execute(HandleStop.java:36)

at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:125)

at org.gradle.launcher.daemon.server.exec.DaemonHygieneAction.execute(DaemonHygieneAction.java:36)

at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:125)

at org.gradle.launcher.daemon.server.exec.CatchAndForwardDaemonFailure.execute(CatchAndForwardDaemonFailure.java:32)

at org.gradle.launcher.daemon.server.exec.DaemonCommandExecution.proceed(DaemonCommandExecution.java:125)

at org.gradle.launcher.daemon.server.exec.DefaultDaemonCommandExecuter.executeCommand(DefaultDaemonCommandExecuter.java:51)

at org.gradle.launcher.daemon.server.DefaultIncomingConnectionHandler$ConnectionWorker.handleCommand(DefaultIncomingConnectionHandler.java:155)

at org.gradle.launcher.daemon.server.DefaultIncomingConnectionHandler$ConnectionWorker.receiveAndHandleCommand(DefaultIncomingConnectionHandler.java:128)

at org.gradle.launcher.daemon.server.DefaultIncomingConnectionHandler$ConnectionWorker.run(DefaultIncomingConnectionHandler.java:116)

at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:66) Caused by: org.gradle.api.GradleException: Could not copy file ‘/somewhereoverthefilesystem/GradleSampleNPEProject/build.gradle’ to ‘/somewhereoverthefilesystem/GradleSampleNPEProject/expanded.txt’.

at org.gradle.api.internal.file.AbstractFileTreeElement.copyTo(AbstractFileTreeElement.java:70)

at org.gradle.api.internal.file.copy.DefaultFileCopyDetails.copyTo(DefaultFileCopyDetails.java:104)

at org.gradle.api.internal.file.copy.FileCopyAction$FileCopyDetailsInternalAction.processFile(FileCopyAction.java:44)

at org.gradle.api.internal.file.copy.NormalizingCopyActionDecorator$1$1.processFile(NormalizingCopyActionDecorator.java:62)

at org.gradle.api.internal.file.copy.DuplicateHandlingCopyActionDecorator$1$1.processFile(DuplicateHandlingCopyActionDecorator.java:60)

at org.gradle.api.internal.file.copy.CopyFileVisitorImpl.processFile(CopyFileVisitorImpl.java:60)

at org.gradle.api.internal.file.copy.CopyFileVisitorImpl.visitFile(CopyFileVisitorImpl.java:44)

at org.gradle.api.internal.file.AbstractFileTree$FilteredFileTree$1.visitFile(AbstractFileTree.java:145)

at org.gradle.api.internal.file.collections.SingletonFileTree.visit(SingletonFileTree.java:40)

at org.gradle.api.internal.file.collections.FileTreeAdapter.visit(FileTreeAdapter.java:96)

at org.gradle.api.internal.file.AbstractFileTree$FilteredFileTree.visit(AbstractFileTree.java:136)

at org.gradle.api.internal.file.CompositeFileTree.visit(CompositeFileTree.java:54)

at org.gradle.api.internal.file.copy.CopySpecActionImpl.execute(CopySpecActionImpl.java:37)

at org.gradle.api.internal.file.copy.CopySpecActionImpl.execute(CopySpecActionImpl.java:24)

at org.gradle.api.internal.file.copy.DelegatingCopySpecInternal.walk(DelegatingCopySpecInternal.java:223)

at org.gradle.api.internal.file.copy.CopySpecBackedCopyActionProcessingStream.process(CopySpecBackedCopyActionProcessingStream.java:36)

at org.gradle.api.internal.file.copy.DuplicateHandlingCopyActionDecorator$1.process(DuplicateHandlingCopyActionDecorator.java:44)

at org.gradle.api.internal.file.copy.NormalizingCopyActionDecorator$1.process(NormalizingCopyActionDecorator.java:53)

at org.gradle.api.internal.file.copy.FileCopyAction.execute(FileCopyAction.java:35)

at org.gradle.api.internal.file.copy.NormalizingCopyActionDecorator.execute(NormalizingCopyActionDecorator.java:49)

at org.gradle.api.internal.file.copy.DuplicateHandlingCopyActionDecorator.execute(DuplicateHandlingCopyActionDecorator.java:42)

at org.gradle.api.internal.file.copy.CopyActionExecuter.execute(CopyActionExecuter.java:38)

at org.gradle.api.internal.file.copy.FileCopier.doCopy(FileCopier.java:63)

at org.gradle.api.internal.file.copy.FileCopier.copy(FileCopier.java:48)

at org.gradle.api.internal.file.DefaultFileOperations.copy(DefaultFileOperations.java:139)

at org.gradle.api.internal.project.AbstractProject.copy(AbstractProject.java:855)

at org.gradle.groovy.scripts.DefaultScript.copy(DefaultScript.java:157)

at build_fr8oct40gdh2gqnb373ms71h4$_run_closure1.doCall(/somewhereoverthefilesystem/GradleSampleNPEProject/build.gradle:4)

at org.gradle.api.internal.ClosureBackedAction.execute(ClosureBackedAction.java:58)

at org.gradle.util.ConfigureUtil.configure(ConfigureUtil.java:130)

at org.gradle.util.ConfigureUtil.configure(ConfigureUtil.java:110)

at org.gradle.api.internal.AbstractTask.configure(AbstractTask.java:436)

at org.gradle.api.internal.project.AbstractProject.task(AbstractProject.java:942)

at org.gradle.api.internal.BeanDynamicObject$MetaClassAdapter.invokeMethod(BeanDynamicObject.java:246)

at org.gradle.api.internal.BeanDynamicObject.invokeMethod(BeanDynamicObject.java:134)

at org.gradle.api.internal.CompositeDynamicObject.invokeMethod(CompositeDynamicObject.java:147)

at org.gradle.groovy.scripts.BasicScript.methodMissing(BasicScript.java:79)

at build_fr8oct40gdh2gqnb373ms71h4.run(/somewhereoverthefilesystem/GradleSampleNPEProject/build.gradle:3)

at org.gradle.groovy.scripts.internal.DefaultScriptRunnerFactory$ScriptRunnerImpl.run(DefaultScriptRunnerFactory.java:52)

… 53 more Caused by: org.gradle.api.InvalidUserDataException: Error - Invalid filter specification for org.apache.tools.ant.filters.ReplaceTokens

at org.gradle.api.internal.file.copy.FilterChain$1.transform(FilterChain.java:69)

at org.gradle.api.internal.file.copy.FilterChain$1.transform(FilterChain.java:58)

at org.gradle.api.internal.ChainingTransformer.transform(ChainingTransformer.java:37)

at org.gradle.api.internal.file.copy.FilterChain.transform(FilterChain.java:39)

at org.gradle.api.internal.file.copy.FilterChain.transform(FilterChain.java:46)

at org.gradle.api.internal.file.copy.DefaultFileCopyDetails.open(DefaultFileCopyDetails.java:88)

at org.gradle.api.internal.file.AbstractFileTreeElement.copyTo(AbstractFileTreeElement.java:47)

at org.gradle.api.internal.file.copy.DefaultFileCopyDetails.copyTo(DefaultFileCopyDetails.java:96)

at org.gradle.api.internal.file.AbstractFileTreeElement.copyFile(AbstractFileTreeElement.java:88)

at org.gradle.api.internal.file.AbstractFileTreeElement.copyTo(AbstractFileTreeElement.java:65)

… 91 more Caused by: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object ‘{ACME=null}’ with class ‘java.util.LinkedHashMap’ to class ‘java.util.Hashtable’ due to: java.lang.NullPointerException

at org.gradle.api.internal.BeanDynamicObject$MetaClassAdapter.setProperty(BeanDynamicObject.java:192)

at org.gradle.api.internal.BeanDynamicObject.setProperty(BeanDynamicObject.java:117)

at org.gradle.util.ConfigureUtil.configureByMap(ConfigureUtil.java:40)

at org.gradle.api.internal.file.copy.FilterChain$1.transform(FilterChain.java:65)

… 100 more

BUILD FAILED

Total time: 2.003 secs whitingjr@f19lite GradleSampleNPEProject $

What’s the expected behaviour of Gradle ? In my opinion a Gradle should be tolerant of build projects using properties without a value.

Using a j.u.Hashtable in this situation has highlighted a problem that any project build using this combination of Copy and ReplaceTokens is very brittle. I am wondering if there is a better way.

To test this problem I created a reproducer. This is my sample build.gradle file.

import org.apache.tools.ant.filters.ReplaceTokens
  task expandProperties {
   copy {
      from 'build.gradle'
      into './'
      rename {'expanded.txt'}
      filter ( ReplaceTokens, tokens: [ ACME: ant.properties['duff.property']])
   }
}

Regards, Jeremy

$ gradle --version

------------------------------------------------------------ Gradle 1.9 ------------------------------------------------------------

Build time:

2013-11-19 08:20:02 UTC Build number: none Revision:

7970ec3503b4f5767ee1c1c69f8b4186c4763e3d

Groovy:

1.8.6 Ant:

Apache Ant™ version 1.9.2 compiled on July 8 2013 Ivy:

2.2.0 JVM:

1.7.0_55 (Oracle Corporation 24.51-b03) OS:

Linux 3.14.7-100.fc19.x86_64 amd64 $ java -version java version “1.7.0_55” OpenJDK Runtime Environment (fedora-2.4.7.0.fc19-x86_64 u55-b13) OpenJDK 64-Bit Server VM (build 24.51-b03, mixed mode)

Ok, to get round this I created a method in the script. The method walks the map to check each value, then substitutes the value if it is a null reference.

def nvl = {
   logger.info ("Processing map
$it")
   it.keySet().each{ k ->
      logger.info ("Processing key [$k]")
      if (null == it.get(k))
      {
         it.put(k, "")
      }
   }
} //this is a workaround for http://forums.gradle.org/gradle/topics/what_is_the_expected_behaviour_when_gradle_casts_collections_of_k_v_properties_with_null_values

I also changed my task to assign the map to a variable. Then call the method passing the variable as a parameter. The variable is then provided as the tokens value to the filter.

The cast of the types now works and my script completes without issue.

Regards, Jeremy