我将把我一直使用的框架分离成一个单独的类,然后写一个测试程序来锻炼它。
在这个系列中,我正在开发几个脚本来帮助清理我的音乐收藏。在上一篇文章中,我编写并测试了一个Groovy脚本来清理标签字段的杂乱组合。在这篇文章中,我将把我一直使用的框架分离成一个单独的类,然后写一个测试程序来锻炼它。
安装Java和Groovy
Groovy是基于Java的,需要安装Java。最近的、像样的Java和Groovy版本可能都在你的Linux发行版的软件库中。Groovy也可以按照Groovy主页上的说明进行安装。对于Linux用户来说,一个不错的选择是SDKMan,它可以用来获取多个版本的Java、Groovy和许多其他相关工具。在这篇文章中,我使用了SDK的以下版本。
- Java:OpenJDK 11的11.0.12-open版本。
- Groovy:3.0.8版本。
框架类
正如我多次提到的,由于音乐目录的结构,我们有一个标准的框架来读取艺术家子目录、专辑子目录、音乐以及其中包含的其他文件。与其将这些代码复制到每个脚本中,不如创建一个Groovy类,将一般的框架行为封装起来,并将特定的应用行为委托给调用它的脚本。
这里是框架,被移到了Groovy类中。
1 public class TagAnalyzerFramework {
2 // called before any data is processed
3 Closure atBeginning
4 // called for each file to be processed
5 Closure onEachLine
6 // called after all data is processed
7 Closure atEnd
8 // the full path name to the music library
9 String musicLibraryDirName
10 public void processMusicLibrary() {
11 // Before we start processing...
12 atBeginning()
13 // Iterate over each dir in music library
14 // These are assumed to be artist directories
15 new File(musicLibraryDirName).eachDir { artistDir ->
16 // Iterate over each dir in artist dir
17 // These are assumed to be album directories
18 artistDir.eachDir { albumDir ->
19 // Iterate over each file in the album directory
20 // These are assumed to be content or related
21 // (cover.jpg, PDFs with liner notes etc)
22 albumDir.eachFile { contentFile ->
23 // Then on each line...
24 onEachLine(artistDir, albumDir, contentFile)
25 }
26 }
27 }
28 // And before we finish...
29 atEnd()
30 }
31 }
第1行介绍了公共类的名称。
第2-7行声明了三个闭包,应用脚本用它们来定义所需处理的具体内容。这就是所谓的行为授权。
第8-9行声明持有音乐目录文件名的字符串。
第10-30行声明实际处理的方法。
第12行调用Closure ,在处理任何数据之前运行。
第15-27行循环处理艺术家/专辑/内容文件结构。
第24行调用Closure ,处理每个内容文件。
第29行调用Closure ,在所有数据处理完毕后运行。
我想在使用这个类之前对其进行编译,如下所示。
$ groovyc TagAnalyzerFramework.groovy$
这就是框架的内容了。
在一个脚本中使用该框架
下面是一个简单的脚本,它打印出音乐目录中所有文件的条形分隔值列表。
1 int fileCount
2 def myTagAnalyzer = new TagAnalyzerFramework()
3 myTagAnalyzer.atBeginning = {
4 // Print the CSV file header and initialize the file counter
5 println "artistDir|albumDir|contentFile"
6 fileCount = 0
7 }
8 myTagAnalyzer.onEachLine = { artistDir, albumDir, contentFile ->
9 // Print the line for this file
10 println "$artistDir.name|$albumDir.name|$contentFile.name"
11 fileCount++
12 }
13 myTagAnalyzer.atEnd = {
14 // Print the file counter value
15 System.err.println "fileCount $fileCount"
16 }
17 myTagAnalyzer.musicLibraryDirName = '/home/clh/Test/Music'
18 myTagAnalyzer.processMusicLibrary()
第1行定义了一个局部变量,fileCount ,用来计算内容文件的数量。注意,这个变量不需要是最终变量。
第2行调用TagAnalyzerFramework 类的构造函数。
第3行做了一个看起来像Java中的错误。它似乎指的是一个外国类中的一个字段。然而,在Groovy中,这实际上是在调用该字段的一个setter,所以这是可以接受的,只要实现类 "记住 "它有一个为这个属性提供setter的契约。
第3-7行创建了一个Closure ,用于打印条形分隔值标题,并初始化了fileCount 变量。
第8-12行同样定义了处理每一行的逻辑的Closure 。在这种情况下,它只是打印艺术家、专辑和内容文件名。如果我回到TagAnalyzerFramework 的第24行,我看到它调用这个Closure ,有三个参数与这里显示的参数对应。
第13-16行定义了Closure,一旦所有的数据被读取,它就结束了处理。在这种情况下,它将文件的数量打印到标准错误。
第17行设置音乐库目录名称。
第18行调用方法来处理音乐库。
运行该脚本。
$ groovy MyTagAnalyzer.groovy
artistDir|albumDir|contentFile
Bombino|Azel|07_Igmayagh_Dum_1.6.16.mp3
Bombino|Azel|08_Ashuhada_1.6.16.mp3
Bombino|Azel|04_Tamiditine_Tarhanam_1.6.16.mp3
Bombino|Azel|10_Naqqim_Dagh_Timshar_1.6.16.mp3
[...]
St Germain|Tourist|04_-_St Germain_-_Land Of....flac
fileCount 55
$
当然,编译框架类所创建的.class 文件必须在classpath上,这样才能发挥作用。当然,我可以用jar来打包这些类文件。
那些对在外国类中设置字段的做法感到不安的人可以定义本地的闭包实例,并将其作为参数传递给构造函数或processMusicLibrary() ,从而达到同样的效果。
我可以回到前面的文章中提供的代码样本中,对这个框架类进行改造。我将把这个练习留给读者。
行为的委托
对我来说,这里发生的最酷的事情是行为的委托,这在其他语言中需要各种诡异的手段。许多年来,Java需要匿名类和相当多的额外代码。Lambdas已经在很大程度上解决了这个问题,但它们仍然不能引用其范围之外的非最终变量。