文鳥は家鴨の夢を見るか

# はじめに
この記事は[Java Advent Calendar 2024](https://qiita.com/advent-calendar/2024/java)の第20日目のエントリーです。そして[一日目のエントリー](https://qiita.com/hatano/items/e791fb95205fd721cf4d)の続きです。
なお19日目のエントリーは[mikoto2000](https://qiita.com/mikoto2000)さんによる[Spring Data JPA の JPQL エラー備忘録(LocalDate がエラーになる) ](https://mikoto2000.blogspot.com/2024/12/spring-data-jpa-jpql-localdate.html)でした。そちらもご覧ください

# しつこく、こんにちは、世界。

続きです。`AdventMessageProviderFactory`に細工して、`jp.hatano.advent2024.providers`パッケージに新しい`AdventMessageProvider`を実装したクラスを追加すると、自動的に読み込まれて実行される、というところまで行きました。
まずは`AdventApp.java`の中身。
```java:AdventApp.java
package jp.hatano.advent2024.main;

import java.io.*;

public class AdventApp {
    public static void main(String[] args) {
        AdventMessageProviderFactory factory = new AdventMessageProviderFactory("jp.hatano.advent2024.providers");
        for ( AdventMessageProvider provider : factory ) {
            AdventApp application = new AdventApp(provider);
            application.sayHello(System.out);
        }
    }

    private final AdventMessageProvider provider;
    public AdventApp(AdventMessageProvider provider) {
        this.provider = provider;
    }

    public void sayHello(PrintStream out) {
        out.println(provider.getHello());
    }
}
```
そして`AdventMessageProvider`インターフェース。
```java:AdventMessageProvider.java
package jp.hatano.advent2024.main;

public interface AdventMessageProvider {
    String getHello();
}
```
さらに`AdventMessageProviderFactory.java`の部分。
```java:AdventMessageProviderFactory.java
package jp.hatano.advent2024.main;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.nio.file.Paths;

public class AdventMessageProviderFactory implements Iterable<AdventMessageProvider>, Iterator<AdventMessageProvider> {
    private final List<AdventMessageProvider> listProviders = new ArrayList<>();
    public AdventMessageProviderFactory(String packageName) {
        String currentClassPath;
        String thisPackageName = getClass().getPackageName().replace(".",File.separator);
        String targetPackageName = packageName.replace(".",File.separator);
        try {
            URL classPath = getClass().getResource(".");
            if ( classPath != null ) {
                currentClassPath = Paths.get(classPath.toURI()).toAbsolutePath().toString().replace(thisPackageName,targetPackageName);
                File[] providerFiles = new File(currentClassPath).listFiles();
                if ( providerFiles != null ) {
                    for (File providerFile : providerFiles) {
                        String providerAbsolutePath = providerFile.getAbsolutePath();
                        String filename = providerAbsolutePath.substring(providerAbsolutePath.lastIndexOf(File.separatorChar)+1);
                        String className = packageName + "." + filename.replace(".class","");
                        Class<?> aClass = Class.forName(className);
                        listProviders.add((AdventMessageProvider) aClass.getConstructor(new Class[]{}).newInstance(new Object[]{}));
                    }
                }
            }
        } catch (URISyntaxException | NoSuchMethodException | IllegalAccessException | InstantiationException |
                 InvocationTargetException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public int size() {
        return listProviders.size();
    }

    public AdventMessageProvider get(int i) {
        return listProviders.get(i);
    }

    public AdventMessageProvider get(String name) {
        for ( AdventMessageProvider provider : listProviders ) {
            if ( provider.getClass().getName().equals(name) ) {
                return provider;
            }
        }
        return null;
    }

    @Override
    public Iterator<AdventMessageProvider> iterator() {
        return listProviders.iterator();
    }

    @Override
    public boolean hasNext() {
        return iterator().hasNext();
    }

    @Override
    public AdventMessageProvider next() {
        return iterator().next();
    }
}
```
ここまではいいですよね…。（いいですよ…ね？）

# 家鴨は家鴨、でも呼べるよ
ここで、`AdventMessageProviderContainer`というクラスを用意します。
```java:AdventMessageProviderContainer.java
package jp.hatano.advent2024.main;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class AdventMessageProviderContainer implements AdventMessageProvider {
    private final Class<?> aClass;
    private Object anObject = null;
    private boolean instantiateSuccessful = true;
    public AdventMessageProviderContainer(Class<?> aClass) {
        this.aClass = aClass;
        try {
            anObject = aClass.getConstructor(new Class<?>[] {}).newInstance(new Object[] {});
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {
            instantiateSuccessful = false;
        }
    }

    private Method methodGetHello = null;

    public boolean applicable() {
        if ( !instantiateSuccessful ) {
            return false;
        }
        if ( methodGetHello != null ) {
            return true;
        }
        try {
            methodGetHello = aClass.getMethod("getHello",new Class<?>[] {});
        } catch (NoSuchMethodException | SecurityException e) {
            return false;
        }
        return true;
    }

    @Override
    public String getHello() {
        if ( methodGetHello == null && !applicable()) {
            return null;
        }
        return getHello0();
    }

    private String getHello0() {
        if ( methodGetHello != null ) {
            try {
                return (String) methodGetHello.invoke(anObject,new Object[] {});
            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                return null;
            }
        }
        return null;
    }
}
```
コンストラクタはひとまず受け取ったクラスオブジェクトを格納するだけにとどめ、`getHello()`を呼び出す場合その前に`applicable()`というメソッドを呼ぶ必要があるようにしました。ここで本当に渡されたクラスが`getHello()`というメソッドを保持しているかどうかをチェックするようにしました。ここでは渡されたクラスが`AdventMessageProvider`インターフェースを実装しているかどうかはチェックしていません。単純に`getHello()`というシグネチャのメソッドを抱えているかしか見ていません。あれば`getHello()`を外から呼ばれたときに実際の`getHello()`実装に繋ぐだけしかしていないのです。
さらに`AdventMessageProviderFactory`の方も（長くなるので抜粋にしましょう…。）
```java:AdventMessageProviderFactory（抜粋）
    Class<?> aClass = Class.forName(className);
    Class<?>[] interfaces = aClass.getInterfaces();
    boolean interfaceFound = false;
    for ( Class<?> anInterface : interfaces ) {
        if ( anInterface.getName().equals("jp.hatano.advent2024.main.AdventMessageProvider") ) {
            listProviders.add((AdventMessageProvider) aClass.getConstructor(new Class[] {}).newInstance(new Object[] {}));
            interfaceFound = true;
            break;
        }
    }
    if ( !interfaceFound ) {
        AdventMessageProviderContainer container = new AdventMessageProviderContainer(aClass);
        if ( container.applicable() ) {
            listProviders.add(container);
        }
    }
```
インターフェースに`AdventMessageProvider`が含まれていれば`listProviders`へそのまま`add()`しておき、なかった場合にはそのクラスを`AdventMessageProviderContainer`で包んでやはり`listProviders`へ`add()`するという動作をしているわけです。
わざと`AdventMessageProvider`インターフェースを実装していない`ItalianMessageProvider`クラスを用意してやります。
```java:ItalianMessageProvider.java
package jp.hatano.advent2024.providers;

public class ItalianMessageProvider {
    public String getHello() {
        return "Ciao mondo.";
    }
}
```
これを、`jp/hatano/advent2024/providers`の下に置いてやって`AdventApp`を実行してやると、こうなります。
```sh
% java -cp out/production/advent2024 jp.hatano.advent2024.main.AdventApp
Hello, World!
Bonjour le monde.
Ciao mondo.
こんにちは、世界
```
はい、`EnglishMessageProvider`、`FrenchMessageProvider`、`JapaneseMessageProvider`と並んで、`ItalianMessageProvider`もちゃんと呼び出せていますね。
こういう「想定した一連のメソッドを抱えていることが確認できれば想定したクラスのオブジェクトだとみなしてしまってもかまわない」というやり方を家鴨型付と言うとか言わないとか。まあでも、Javaの場合きちんとインターフェースを実装すればすむ話なので、あえてこんなことをする価値はないんですよね。やればできるというところをやってみたかっただけなのですよね。

# おわりに
相変わらずオチはありません。コードは引き続き[github](https://github.com/mhatano/advent2024)に公開しています。