それでは実際に設定ファイル(classworlds.conf)を解析する、ConfigurationParser#parse(classworlds.conf)メソッドに入って行きます。
今回はmavenのぶらり旅なので、mavenの起動時に実際に読み込まれるclassworlds.conf(m2.conf)を見てみる。
main is org.apache.maven.cli.MavenCli from plexus.core
set maven.home default ${user.home}/m2
[plexus.core]
optionally ${maven.home}/lib/ext/*.jar
load ${maven.home}/lib/*.jar
load ${maven.home}/conf/logging
今から旅するのは、要するに上の設定ファイルをどの様にパースしていくか、を見ていくということになる訳だ。また、それぞれの記述が何を意味しているのかも探っていく必要がある。私がネット上で検索する限り、このclassworlds.confの仕様は見当たらなかった。
plexus-classworlds-*.jarを使うためには、classworlds.confを適切に設定してあげないといけないことは想像できるが、使用方法の説明もないので、どの様に書けば良いのかさっぱり分からん。こんな時どうすれば良いか。
通常、仕様や仕様方法のドキュメントがなければお手上げだよね。でも、今回は素晴らしいことにオープンソースなので、仕様方法がわからなければソースを読めば良いだけなのだ。下手なドキュメントを見るより、よっぽど明確に分かることもある。もちろん、仕様が複雑な場合はソースを読むのも大変な場合もあるが、今回のはそれほどでもなさそう。
では、まずは、parse()メソッドの全体を見渡してみしょ。引数のInputStreamからBufferdReaderを作成して、そのreaderから一行ずつ読み込んでいるようですな。while( ture )として、reader.readLine()がnullを返すまで全行読み込むという仕組みということだな。その後は、line.starsWith( MAIN_PREFIX )のように行の先頭の文字列が何であるのかによって、処理を分岐させて設定ファイルを解析している。
public void parse( InputStream is )
throws IOException, ConfigurationException, DuplicateRealmException, NoSuchRealmException
{
BufferedReader reader = new BufferedReader( new InputStreamReader( is, "UTF-8" ) );
String line = null;
int lineNo = 0;
boolean mainSet = false;
String curRealm = null;
while ( true )
{
line = reader.readLine();
if ( line == null )
{
break;
}
++lineNo;
line = line.trim();
if ( canIgnore( line ) )
{
continue;
}
if ( line.startsWith( MAIN_PREFIX ) )
{
/* 詳細は後述 */
}
else if ( line.startsWith( SET_PREFIX ) )
{
/* 詳細は後述 */
}
else if ( line.startsWith( "[" ) )
{
/* 詳細は後述 */
}
else if ( line.startsWith( IMPORT_PREFIX ) )
{
/* 詳細は後述 */
}
else if ( line.startsWith( LOAD_PREFIX ) )
{
/* 詳細は後述 */
}
else if ( line.startsWith( OPTIONALLY_PREFIX ) )
{
/* 詳細は後述 */
}
else
{
throw new ConfigurationException( "Unhandled configuration", lineNo, line );
}
}
reader.close();
}
処理の分岐は以下の6パターンあるようだ。
- if ( line.startsWith( MAIN_PREFIX ) )
- else if ( line.startsWith( SET_PREFIX ) )
- else if ( line.startsWith( "[" ) )
- else if ( line.startsWith( IMPORT_PREFIX ) )
- else if ( line.startsWith( LOAD_PREFIX ) )
- else if ( line.startsWith( OPTIONALLY_PREFIX ) )
そしてそれぞれの定数の定義は以下のようになっている。
public static final String MAIN_PREFIX = "main is";
public static final String SET_PREFIX = "set";
public static final String IMPORT_PREFIX = "import";
public static final String LOAD_PREFIX = "load";
public static final String OPTIONALLY_PREFIX = "optionally";
今回の解析対象である、m2.confの中には、IMPORT_PREFIX以外の全てが登場しているな。
では、最初の「MAIN_PREFIX = "main is"」から見てみますか。
main is org.apache.maven.cli.MavenCli from plexus.core
上が設定ファイルの解析対象行。下が解析ロジック。
if ( line.startsWith( MAIN_PREFIX ) )
{
if ( mainSet )
{
throw new ConfigurationException( "Duplicate main configuration", lineNo, line );
}
String conf = line.substring( MAIN_PREFIX.length() ).trim();
int fromLoc = conf.indexOf( "from" );
if ( fromLoc < 0 )
{
throw new ConfigurationException( "Missing from clause", lineNo, line );
}
String mainClassName = filter( conf.substring( 0, fromLoc ).trim() );
String mainRealmName = filter( conf.substring( fromLoc + 4 ).trim() );
this.handler.setAppMain( mainClassName, mainRealmName );
mainSet = true;
}
どうやらthrow new ConfigurationException( "Duplicate main configuration", lineNo, line );
とあるので、「main is」で始まるは一つしか書いてはいけないようだ。という感じで、このように例外をスローするようにコーディングしておくと、特にコメント等で書き表さずとも、ソースコードそのもので、仕様を明確に伝えられる。
さらには、throw new ConfigurationException( "Missing from clause", lineNo, line );
というロジックも続きます。ここでも例外をスローするようになっていて、ここから読み解ける仕様としては、「main is」で始まる行には、必ず「from」というキーワードが含まれるfrom句がなければならないようだ。
で、結局のところ、「from」より前をmainClassNameとして取り出し、fromより後を、mainRelmNameとして取り出している。
つまりは、以下のようになる訳だ。
String mainClassName = "org.apache.maven.cli.MavenCli";
String mainRealmName = "plexus.core";
mainClassNameというのは分かりやすいとして、mainRelmNameってのは何でしょね。。そのうち理解していくとしますか。
お次は以下のsetで始まる行のパース処理。
set maven.home default ${user.home}/m2
パースするコードは次の通り。
else if ( line.startsWith( SET_PREFIX ) )
{
String conf = line.substring( SET_PREFIX.length() ).trim();
int usingLoc = conf.indexOf( " using" ) + 1;
String property = null;
String propertiesFileName = null;
if ( usingLoc > 0 )
{
property = conf.substring( 0, usingLoc ).trim();
propertiesFileName = filter( conf.substring( usingLoc + 5 ).trim() );
conf = propertiesFileName;
}
String defaultValue = null;
int defaultLoc = conf.indexOf( " default" ) + 1;
if ( defaultLoc > 0 )
{
defaultValue = filter( conf.substring( defaultLoc + 7 ).trim() );
if ( property == null )
{
property = conf.substring( 0, defaultLoc ).trim();
}
else
{
propertiesFileName = conf.substring( 0, defaultLoc ).trim();
}
}
String value = systemProperties.getProperty( property );
if ( value != null )
{
continue;
}
if ( propertiesFileName != null )
{
File propertiesFile = new File( propertiesFileName );
if ( propertiesFile.exists() )
{
Properties properties = new Properties();
try
{
properties.load( new FileInputStream( propertiesFileName ) );
value = properties.getProperty( property );
}
catch ( Exception e )
{
// do nothing
}
}
}
if ( value == null && defaultValue != null )
{
value = defaultValue;
}
if ( value != null )
{
value = filter( value );
systemProperties.setProperty( property, value );
}
}
今回は例外をスローしている箇所はないので、書式の厳密な仕様は特なさそう。書き方を誤れば何も起こらないとか、別の例外が発生するとかですかね。で結局のところ、「maven.home」という名前のキーでプロパティに値を設定する処理をしたいみたいですね。「using」というキーワードの後にプロパティファイルの名前が設定されていた場合は、そのファイルから「maven.home」の値を取り出すみたいだが、今回は「using」句は使っていない。その場合、「default」というキーワードにあるキー名を使って、SystemPropertiesから読み込むようだ。すでに設定されている場合は何もしない。設定されていない場合は、default値をSystemPropertiesに書き込む。この時のdefault値は「${user.home}/m2」。
ん?でも、ちょっと待て。「${user.home}/m2」ってなんだ??
上のソースをよーく見ると、filter()というメソッドに「${user.home}/m2」を渡して、その戻り値を使っているな。では、この
filter()メドッドくんを覗いてみよう。
protected String filter( String text )
throws ConfigurationException
{
String result = "";
int cur = 0;
int textLen = text.length();
int propStart = -1;
int propStop = -1;
String propName = null;
String propValue = null;
while ( cur < textLen )
{
propStart = text.indexOf( "${", cur );
if ( propStart < 0 )
{
break;
}
result += text.substring( cur, propStart );
propStop = text.indexOf( "}", propStart );
if ( propStop < 0 )
{
throw new ConfigurationException( "Unterminated property: " + text.substring( propStart ) );
}
propName = text.substring( propStart + 2, propStop );
propValue = systemProperties.getProperty( propName );
/* do our best if we are not running from surefire */
if ( propName.equals( "basedir" ) && ( propValue == null || propValue.equals( "" ) ) )
{
propValue = ( new File( "" ) ).getAbsolutePath();
}
if ( propValue == null )
{
throw new ConfigurationException( "No such property: " + propName );
}
result += propValue;
cur = propStop + 1;
}
result += text.substring( cur );
return result;
}
ざっと見る限「${」と「}」で囲まれた部分、今回で言うと「user.home」という文字列を取り出して、この文字列をキーとしてSystemPropertiesから値を読み出してますね。「}」がなかったり、取り出した文字列のキーに該当する値がなかった場合は、それぞれ適切な例外をスローしてますな。で、結局のところ、システムプロパティのuser.homeに設定されているパス+「/m2」というパスを作成して呼び出し元に返しているってことのようだ。
なーのーでー、
set maven.home default ${user.home}/m2
という設定を読み込むとシステムプロパティに「mavne.home」というプロパティが追加されて、そこには「user.home/m2」というパスがセットされることになる。
これで最初の2行の解析処理が完了した。お次は以下の4行。
[plexus.core]
optionally ${maven.home}/lib/ext/*.jar
load ${maven.home}/lib/*.jar
load ${maven.home}/conf/logging