Skip to content

Commit 9f55da2

Browse files
committed
Version v0.5.8. Accept callable input, improve error messages. Let directories be absolute.
1 parent c3e2bf7 commit 9f55da2

File tree

3 files changed

+115
-62
lines changed

3 files changed

+115
-62
lines changed

RELEASENOTES.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
### 0.5.8:
2-
* Fix diagnostics
2+
* Much better diagnostics and error handling
3+
* Instead of requiring the input to be realized immediately, it can be a
4+
Callable instead, that will be resolved if there is no doGenerate method
5+
that takes Callables. The result of the callable is used to resolve the
6+
actually invoked doGenerate method.
7+
* Allow for absolute paths, by making sure not to resolve them against
8+
the base directory when they are absolute.
39

410
### 0.5.7:
511
* Resolve most things lazilly. In particularly only set the "files" for the generate

build.gradle

+7-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ apply plugin: 'kotlin'
5252
sourceCompatibility = JavaVersion.VERSION_1_6
5353
targetCompatibility = JavaVersion.VERSION_1_6
5454

55-
version = '0.5.7'
55+
version = '0.5.8'
5656
group = 'net.devrieze'
5757

5858
bintray {
@@ -119,6 +119,12 @@ task sourceJar(type: Jar) {
119119
from sourceSets.main.allSource
120120
}
121121

122+
task publishAll {
123+
group "publishing"
124+
dependsOn(bintrayUpload)
125+
dependsOn(publishPlugins)
126+
}
127+
122128
repositories {
123129
jcenter()
124130
}

src/main/kotlin/net/devrieze/gradlecodegen/plugin.kt

+101-60
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import java.net.URL
4444
import java.net.URLClassLoader
4545
import java.util.*
4646
import java.util.concurrent.Callable
47+
import kotlin.reflect.KClass
4748

4849
val Project.sourceSets: SourceSetContainer
4950
get() = project.convention.getPlugin(JavaPluginConvention::class.java).sourceSets
@@ -86,8 +87,7 @@ open class GenerateTask: DefaultTask() {
8687
}
8788

8889
if (dirGenerator.generator!=null) {
89-
val outDir = if (dirGenerator.outputDir ==null) project.file(outputDir) else File(project.file(outputDir),dirGenerator.outputDir)
90-
outDir.mkdirs() // ensure the output directory exists
90+
val outDir = if (dirGenerator.outputDir ==null) project.file(outputDir) else resolveFile(project.file(outputDir),dirGenerator.outputDir!!)
9191
if (dirGenerator.classpath!=null) {
9292
URLClassLoader(combinedClasspath(dirGenerator.classpath)). use {
9393
generateDir(outDir, it)
@@ -101,71 +101,114 @@ open class GenerateTask: DefaultTask() {
101101
}
102102

103103
private fun generateDir(outDir: File, classLoader: ClassLoader) {
104+
if (! outDir.exists()) {
105+
if (!outDir.mkdirs()) throw InvalidUserDataException("The output directory $outDir could not be created")
106+
} // ensure the output directory exists
107+
if (!outDir.canWrite()) throw InvalidUserDataException("The output directory $outDir is not writeable")
108+
104109
val generatorClass = classLoader.loadClass(dirGenerator.generator)
105-
val m = generatorClass.getGenerateDirMethod(dirGenerator.input)
106110

107-
val generatorInst = if (Modifier.isStatic(m.modifiers)) null else generatorClass.newInstance()
108-
if (m.parameterCount==1) {
109-
m.invoke(generatorInst, outDir)
110-
} else {
111-
m.invoke(generatorInst, outDir, dirGenerator.input)
112-
}
111+
val baseError = """
112+
Directory generators must have a unique public method "doGenerate(File, [Object])"
113+
where the second parameter is optional iff the input is null. If not a static
114+
method, the class must have a noArg constructor. """.trimIndent()
113115

116+
generatorClass.execute(outDir, dirGenerator.input, baseError)
114117
}
115118

119+
private fun resolveFile(context:File, fileName:String):File {
120+
return File(fileName).let {
121+
if (it.isAbsolute) it
122+
else File(project.file(outputDir), fileName)
123+
}
124+
}
116125

117126
private fun generateFile(spec: GenerateSpec, classLoader: ClassLoader) {
118127
if (spec.output != null) {
119-
val outFile = File(project.file(outputDir), spec.output)
128+
val outFile = resolveFile(project.file(outputDir), spec.output!!)
120129

121-
if (project.logger.isInfoEnabled) {
122-
project.logger.info("Generating ${spec.name} as '${spec.output}' as '${outFile}'")
123-
} else {
124-
project.logger.lifecycle("Generating ${spec.name} as '${spec.output}'")
125-
}
126130
if (spec.generator != null) {
127131
val gname = spec.generator
128132
val generatorClass = classLoader.loadClass(gname)
129-
if (!outFile.isFile) {
130-
outFile.parentFile.mkdirs()
133+
if (outFile.isDirectory) throw InvalidUserDataException("The output can not be a directory, it must be a file ($outFile)")
134+
if (!outFile.exists()) {
135+
outFile.parentFile.apply { if (! exists()) mkdirs() || throw InvalidUserDataException("The target directory for the output file $outFile could not be created")}
131136
outFile.createNewFile()
132137
}
138+
if (!outFile.canWrite()) throw InvalidUserDataException("The output file ($outFile) is not writeable.")
133139

134-
val m = generatorClass.getGenerateMethod(spec.input)
135-
val generatorInst = if (Modifier.isStatic(m.modifiers)) null else generatorClass.newInstance()
136-
outFile.writer().use { writer ->
137-
if (m.parameterCount == 2) {
138-
m.invoke(generatorInst, writer, spec.input)
139-
} else {
140-
m.invoke(generatorInst, writer)
141-
}
140+
if (project.logger.isInfoEnabled) {
141+
project.logger.info("Generating ${spec.name} as '${spec.output}' as '${outFile}'")
142+
} else {
143+
project.logger.lifecycle("Generating ${spec.name} as '${spec.output}'")
142144
}
143145

146+
val baseError = """
147+
Generators must have a unique public method "doGenerate(Writer|Appendable, [Object])"
148+
where the second parameter is optional iff the input is null. If not a static
149+
method, the class must have a noArg constructor.""".trimIndent()
150+
151+
generatorClass.execute({outFile.writer()}, spec.input, baseError)
152+
144153
} else {
145-
logger.quiet("Missing output code for generateSpec ${spec.name}, no generator provided")
154+
throw InvalidUserDataException("Missing output code for generateSpec ${spec.name}, no generator provided")
146155
}
147156
}
148157
}
149158

150-
private fun Class<*>.getGenerateMethod(input: Any?):Method {
159+
private fun Class<*>.getGeneratorMethods(firstParamWriter: Boolean, input:Any?):List<Method> {
151160
return methods.asSequence()
152161
.filter { it.name=="doGenerate" }
153162
.filter { Modifier.isPublic(it.modifiers) }
154163
.filter { if (input==null) it.parameterCount in 1..2 else it.parameterCount==2 }
155-
.let {
156-
val candidates = it.toList()
157-
candidates.asSequence()
158-
.filter { Appendable::class.java.isAssignableFrom(it.parameterTypes[0]) && it.parameterTypes[0].isAssignableFrom(Writer::class.java) }
159-
.filter { if (it.parameterCount==1) true else (isSecondParameterCompatible(input, it)) }
160-
.singleOrNull() ?: throw NoSuchMethodError("""
161-
Generators must have a unique public method "doGenerate(Writer|Appendable, [Object])"
162-
where the second parameter is optional iff the input is null. If not a static
163-
method, the class must have a noArg constructor.
164+
.filter {
165+
if(firstParamWriter) {
166+
Appendable::class.java.isAssignableFrom(it.parameterTypes[0]) && it.parameterTypes[0].isAssignableFrom(Writer::class.java)
167+
} else {
168+
File::class.java==it.parameterTypes[0]
169+
}
170+
}.toList()
171+
}
164172

165-
Candidates were: (with input = ${input?.javaClass?.name?:"null"}):
166-
${candidates.joinToString("\n${" ".repeat(18)}") { it.toString() }}
167-
""".trimIndent() )
173+
private fun Class<out Any>.execute(firstParam: Any, input:Any?, baseErrorMsg:String) {
174+
getGeneratorMethods(firstParam !is File, input).let { candidates ->
175+
try {
176+
var resolvedInput = input
177+
var methodIterator = candidates
178+
.asSequence()
179+
.filter { if (it.parameterCount == 1) true else isSecondParameterCompatible(input, it) }
180+
.iterator()
181+
182+
if (input is Callable<*> && !methodIterator.hasNext()) {
183+
resolvedInput = input.call()
184+
methodIterator = candidates.asSequence()
185+
.filter { isSecondParameterCompatible(resolvedInput, it) }.iterator()
168186
}
187+
188+
if (! methodIterator.hasNext()) throw InvalidUserDataException(errorMsg("No candidate method found", candidates, baseErrorMsg, input))
189+
190+
val m = methodIterator.next()
191+
192+
if (methodIterator.hasNext()) { throw InvalidUserCodeException(ambiguousChoice(candidates, baseErrorMsg, input)) }
193+
m.doInvoke(this, firstParam, resolvedInput)
194+
return
195+
} catch (e:Exception) {
196+
throw InvalidUserDataException("Could not execute the generator code", e)
197+
}
198+
}
199+
}
200+
201+
private fun ambiguousChoice(candidates: Iterable<Method>, baseErrorMsg: String, input:Any?): String {
202+
return errorMsg("More than 1 valid candidate found.", candidates, baseErrorMsg, input)
203+
}
204+
205+
private fun errorMsg(error:String, candidates: Iterable<Method>, baseErrorMsg: String, input:Any?): String {
206+
return buildString {
207+
appendln(error)
208+
appendln(baseErrorMsg).appendln()
209+
appendln("Candidates were: (with input = ${input?.javaClass?.name ?: "null"}):").append(" ")
210+
candidates.joinTo(this, "\n ") { candidates.toString() }
211+
}
169212
}
170213

171214
private fun isSecondParameterCompatible(input: Any?, method: Method): Boolean {
@@ -176,27 +219,6 @@ open class GenerateTask: DefaultTask() {
176219
}
177220
}
178221

179-
private fun Class<*>.getGenerateDirMethod(input: Any?):Method {
180-
return methods.asSequence()
181-
.filter { it.name=="doGenerate" }
182-
.filter { Modifier.isPublic(it.modifiers) }
183-
.filter { if (input==null) it.parameterCount in 1..2 else it.parameterCount==2 }
184-
.let {
185-
val candidates = it.toList()
186-
candidates.asSequence().filter { (File::class.java==it.parameterTypes[0].apply { project.logger.debug("Rejecting $it as the first parameter is not a file") }) }
187-
.filter { if (it.parameterCount==1) true else isSecondParameterCompatible(input, it) }
188-
.singleOrNull() ?: throw NoSuchMethodError("""
189-
Generators must have a unique public method "doGenerate(File, [Object])"
190-
where the second parameter is optional iff the input is null. If not a static
191-
method, the class must have a noArg constructor.
192-
193-
Candidates were (with input = ${input?.javaClass?.name?:"null"}):
194-
${candidates.joinToString("\n ") { it.toString() }}
195-
""".trimIndent() )
196-
197-
}
198-
}
199-
200222
private fun combinedClasspath(others: FileCollection?): Array<out URL>? {
201223

202224
fun Iterable<File>.toUrls():Sequence<URL> = asSequence().map { it.toURI().toURL() }
@@ -209,6 +231,25 @@ open class GenerateTask: DefaultTask() {
209231

210232
}
211233

234+
fun Method.doInvoke(receiver:Class<out Any>, firstParam: Any, input: Any?) {
235+
val generatorInst = if (Modifier.isStatic(modifiers)) null else receiver.newInstance()
236+
237+
val body = { output:Any ->
238+
if (this.parameterCount == 1) {
239+
invoke(generatorInst, output)
240+
} else {
241+
invoke(generatorInst, output, input)
242+
}
243+
}
244+
245+
if (firstParam is File) {
246+
body(firstParam)
247+
} else {
248+
@Suppress("UNCHECKED_CAST")
249+
(((firstParam as ()->Any).invoke()) as Writer).use(body)
250+
}
251+
}
252+
212253
public const val INPUT_SOURCE_SET = "generatorSources"
213254
public const val OUTPUT_SOURCE_SET = "generatedSources"
214255
public const val DEFAULT_GEN_DIR = "gen"

0 commit comments

Comments
 (0)