1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

文鳥は家鴨の夢を見るか

Last updated at Posted at 2024-12-20

はじめに

この記事はJava Advent Calendar 2024の第20日目のエントリーです。そして一日目のエントリーの続きです。
なお19日目のエントリーはmikoto2000さんによるSpring Data JPA の JPQL エラー備忘録(LocalDate がエラーになる) でした。そちらもご覧ください

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

続きです。AdventMessageProviderFactoryに細工して、jp.hatano.advent2024.providersパッケージに新しいAdventMessageProviderを実装したクラスを追加すると、自動的に読み込まれて実行される、というところまで行きました。
まずはAdventApp.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インターフェース。

AdventMessageProvider.java
package jp.hatano.advent2024.main;

public interface AdventMessageProvider {
    String getHello();
}

さらにAdventMessageProviderFactory.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というクラスを用意します。

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の方も(長くなるので抜粋にしましょう…。)

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で包んでやはりlistProvidersadd()するという動作をしているわけです。
わざとAdventMessageProviderインターフェースを実装していないItalianMessageProviderクラスを用意してやります。

ItalianMessageProvider.java
package jp.hatano.advent2024.providers;

public class ItalianMessageProvider {
    public String getHello() {
        return "Ciao mondo.";
    }
}

これを、jp/hatano/advent2024/providersの下に置いてやってAdventAppを実行してやると、こうなります。

% java -cp out/production/advent2024 jp.hatano.advent2024.main.AdventApp
Hello, World!
Bonjour le monde.
Ciao mondo.
こんにちは、世界

はい、EnglishMessageProviderFrenchMessageProviderJapaneseMessageProviderと並んで、ItalianMessageProviderもちゃんと呼び出せていますね。
こういう「想定した一連のメソッドを抱えていることが確認できれば想定したクラスのオブジェクトだとみなしてしまってもかまわない」というやり方を家鴨型付と言うとか言わないとか。まあでも、Javaの場合きちんとインターフェースを実装すればすむ話なので、あえてこんなことをする価値はないんですよね。やればできるというところをやってみたかっただけなのですよね。

おわりに

相変わらずオチはありません。コードは引き続きgithubに公開しています。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?