以下の内容はhttps://kazuhira-r.hatenablog.com/entry/2026/02/28/183656より取得しました。


Javaのソースコードを解析できるJavaParserを試す

これは、なにをしたくて書いたもの?

Javaのソースコードを解析できるライブラリーのひとつとして、JavaParserというものがあります。

Javaのソースコードをパースできるものをちょっと探していたので、まずはこちらを試してみようかなということで。

JavaParser

JavaParserのWebサイトはこちら。

JavaParser - Home

GitHubリポジトリーはこちら。

GitHub - javaparser/javaparser: Java 1-25 Parser and Abstract Syntax Tree for Java with advanced analysis functionalities.

JavaParserは以下のことができるライブラリーです。

  • Javaソースコードのパース
  • Javaソースコードの解析
  • Javaソースコードの変換
  • Javaソースコードの生成

現時点では、Java 1.0から25までのソースコードをパースできるようです。

またJavaParserには以下の2種類が含まれています。

  • javaparser-core … Javaソースコードを解析するコア機能
  • javaparser-symbol-solver-core … コア機能が生成したAST(Abstract Syntax Tree)を解析する機能

生成されたASTは、JSONにシリアライズすることもできます。

今回はコア機能であるjavaparser-coreを使ってみたいと思います。

サンプルはこちら。

GitHub - javaparser/javaparser-maven-sample: Sample project with a basic Maven + JavaParser setup

JavaParserのJavadocはこちら。

javaparser-core 3.28.0 javadoc (com.github.javaparser)

環境

今回の環境はこちら。

$ java --version
openjdk 25.0.2 2026-01-20
OpenJDK Runtime Environment (build 25.0.2+10-Ubuntu-124.04)
OpenJDK 64-Bit Server VM (build 25.0.2+10-Ubuntu-124.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.12 (848fbb4bf2d427b72bdb2471c22fced7ebd9a7a1)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 25.0.2, vendor: Ubuntu, runtime: /usr/lib/jvm/java-25-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-101-generic", arch: "amd64", family: "unix"

準備

Maven依存関係など。

    <properties>
        <maven.compiler.release>25</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.github.javaparser</groupId>
            <artifactId>javaparser-core</artifactId>
            <version>3.28.0</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>6.0.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.27.7</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.smallrye</groupId>
            <artifactId>jandex</artifactId>
            <version>3.5.3</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

確認はテストコードで行います。

またサブクラスの抽出が目的で、Jandexを入れています。

JavaParserを使ってみる

それでは、JavaParserを使ってみましょう。

テストコードの雛形はこちら。

src/test/java/org/littlewings/javaparser/JavaParserTest.java

package org.littlewings.javaparser;

import com.github.javaparser.ParseProblemException;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.comments.TraditionalJavadocComment;
import com.github.javaparser.ast.type.ClassOrInterfaceType;
import com.github.javaparser.ast.type.VoidType;
import com.github.javaparser.ast.visitor.GenericVisitorAdapter;
import com.github.javaparser.ast.visitor.Visitable;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.github.javaparser.javadoc.JavadocBlockTag;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.jboss.jandex.Indexer;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class JavaParserTest {

    // ここに、テストを書く!
}
ソースコードを解析してみる

ソースコードの解析はStaticJavaParserクラスを起点に行うようです。

    @Test
    void parseGettingStarted() {
        CompilationUnit compilationUnit = StaticJavaParser.parse("""
                class A {
                    String property;
                
                    String getProperty() {
                        return property;
                    }
                
                    void setProperty(String property) {
                        this.property = property;
                    }
                }""");

        assertThat(compilationUnit.toString()).isEqualTo("""
                class A {
                
                    String property;
                
                    String getProperty() {
                        return property;
                    }
                
                    void setProperty(String property) {
                        this.property = property;
                    }
                }
                """);

        // クラス
        Optional<ClassOrInterfaceDeclaration> optionalClassA =
                compilationUnit.getClassByName("A");

        assertThat(optionalClassA).isPresent();

        ClassOrInterfaceDeclaration classA = optionalClassA.get();

        // クラス、フィールド、getter、setter
        assertThat(classA.getChildNodes()).hasSize(4);

        // フィールド
        Optional<FieldDeclaration> optionalProperty = classA.getFieldByName("property");
        assertThat(optionalProperty).isPresent();

        FieldDeclaration property = optionalProperty.get();
        assertThat(property.getElementType()).isExactlyInstanceOf(ClassOrInterfaceType.class);
        assertThat(property.getElementType().asString()).isEqualTo("String");
        assertThat(property.getVariable(0).getName().asString()).isEqualTo("property");
        assertThat(property.getBegin().get().toString()).isEqualTo("(line 2,col 5)");
        assertThat(property.getEnd().get().toString()).isEqualTo("(line 2,col 20)");

        // 親はクラス
        assertThat(property.getParentNode().get()).isEqualTo(classA);

        // getter
        List<MethodDeclaration> getProperties = classA.getMethodsByName("getProperty");
        MethodDeclaration getProperty = getProperties.getFirst();
        assertThat(getProperty.getNameAsString()).isEqualTo("getProperty");

        // 戻り値
        assertThat(getProperty.getType()).isExactlyInstanceOf(ClassOrInterfaceType.class);
        assertThat(getProperty.getType().asString()).isEqualTo("String");

        // 引数
        assertThat(getProperty.getParameters()).isEmpty();

        // 位置
        assertThat(getProperty.getBegin().get().toString()).isEqualTo("(line 4,col 5)");
        assertThat(getProperty.getEnd().get().toString()).isEqualTo("(line 6,col 5)");

        // コード
        assertThat(getProperty.getBody().get().toString())
                .isEqualTo("""
                        {
                            return property;
                        }""");
        assertThat(getProperty.getBody().get().getStatements().stream().map(Node::toString).collect(Collectors.joining()))
                .isEqualTo("return property;");

        // setter
        List<MethodDeclaration> setProperties = classA.getMethodsByName("setProperty");
        MethodDeclaration setProperty = setProperties.getFirst();
        assertThat(setProperty.getNameAsString()).isEqualTo("setProperty");

        // 戻り値
        assertThat(setProperty.getType()).isExactlyInstanceOf(VoidType.class);

        // 引数
        assertThat(setProperty.getParameters()).hasSize(1);
        Parameter setPropertyParameter = setProperty.getParameter(0);
        assertThat(setPropertyParameter.getType()).isExactlyInstanceOf(ClassOrInterfaceType.class);
        assertThat(setPropertyParameter.getType().asString()).isEqualTo("String");
        assertThat(setPropertyParameter.getNameAsString()).isEqualTo("property");

        // 位置
        assertThat(setProperty.getBegin().get().toString()).isEqualTo("(line 8,col 5)");
        assertThat(setProperty.getEnd().get().toString()).isEqualTo("(line 10,col 5)");

        // コード
        assertThat(setProperty.getBody().get().toString())
                .isEqualTo("""
                        {
                            this.property = property;
                        }""");
        assertThat(setProperty.getBody().get().getStatements().stream().map(Node::toString).collect(Collectors.joining()))
                .isEqualTo("this.property = property;");
    }

順に見ていきましょう。

StaticJavaParserに渡すソースコードの形態はいろいろありますが、今回は文字列として与えました。パースに成功すると
CompilationUnitというものが返ってきます。

        CompilationUnit compilationUnit = StaticJavaParser.parse("""
                class A {
                    String property;
                
                    String getProperty() {
                        return property;
                    }
                
                    void setProperty(String property) {
                        this.property = property;
                    }
                }""");

CompilationUnitを文字列にすると、パースした内容がほぼそのまま取得できますね。

        assertThat(compilationUnit.toString()).isEqualTo("""
                class A {
                
                    String property;
                
                    String getProperty() {
                        return property;
                    }
                
                    void setProperty(String property) {
                        this.property = property;
                    }
                }
                """);

完全に同じというわけではなさそうですが。今回の例だと、クラスとフィールドの宣言の間に空行がひとつ増えています。

CompilationUnitを起点に、パースした内容に含まれる情報を取得できます。クラスを取得。

        // クラス
        Optional<ClassOrInterfaceDeclaration> optionalClassA =
                compilationUnit.getClassByName("A");

        assertThat(optionalClassA).isPresent();

        ClassOrInterfaceDeclaration classA = optionalClassA.get();

クラスに含まれる「ノード」の数。クラス自身も含まれるようです。

        // クラス、フィールド、getter、setter
        assertThat(classA.getChildNodes()).hasSize(4);

この「ノード」というのは、また後で載せます。

フィールド。

        // フィールド
        Optional<FieldDeclaration> optionalProperty = classA.getFieldByName("property");
        assertThat(optionalProperty).isPresent();

        FieldDeclaration property = optionalProperty.get();
        assertThat(property.getElementType()).isExactlyInstanceOf(ClassOrInterfaceType.class);
        assertThat(property.getElementType().asString()).isEqualTo("String");
        assertThat(property.getVariable(0).getName().asString()).isEqualTo("property");
        assertThat(property.getBegin().get().toString()).isEqualTo("(line 2,col 5)");
        assertThat(property.getEnd().get().toString()).isEqualTo("(line 2,col 20)");

        // 親はクラス
        assertThat(property.getParentNode().get()).isEqualTo(classA);

行番号などの位置情報も取得できます。あと、JavaParserのAPIにはOptionalが出てくるのでそのあたりは慣れですね。

getterの情報を解析してみましょう。要するにメソッドですね。

        // getter
        List<MethodDeclaration> getProperties = classA.getMethodsByName("getProperty");
        MethodDeclaration getProperty = getProperties.getFirst();
        assertThat(getProperty.getNameAsString()).isEqualTo("getProperty");

        // 戻り値
        assertThat(getProperty.getType()).isExactlyInstanceOf(ClassOrInterfaceType.class);
        assertThat(getProperty.getType().asString()).isEqualTo("String");

        // 引数
        assertThat(getProperty.getParameters()).isEmpty();

        // 位置
        assertThat(getProperty.getBegin().get().toString()).isEqualTo("(line 4,col 5)");
        assertThat(getProperty.getEnd().get().toString()).isEqualTo("(line 6,col 5)");

        // コード
        assertThat(getProperty.getBody().get().toString())
                .isEqualTo("""
                        {
                            return property;
                        }""");
        assertThat(getProperty.getBody().get().getStatements().stream().map(Node::toString).collect(Collectors.joining()))
                .isEqualTo("return property;");

コードの内容も取得できます。Bodyを直接文字列にするか、Statementをまとめて文字列にするかで少し結果が
変わりますが、このあたりはお好みで。

setter。

        // setter
        List<MethodDeclaration> setProperties = classA.getMethodsByName("setProperty");
        MethodDeclaration setProperty = setProperties.getFirst();
        assertThat(setProperty.getNameAsString()).isEqualTo("setProperty");

        // 戻り値
        assertThat(setProperty.getType()).isExactlyInstanceOf(VoidType.class);

        // 引数
        assertThat(setProperty.getParameters()).hasSize(1);
        Parameter setPropertyParameter = setProperty.getParameter(0);
        assertThat(setPropertyParameter.getType()).isExactlyInstanceOf(ClassOrInterfaceType.class);
        assertThat(setPropertyParameter.getType().asString()).isEqualTo("String");
        assertThat(setPropertyParameter.getNameAsString()).isEqualTo("property");

        // 位置
        assertThat(setProperty.getBegin().get().toString()).isEqualTo("(line 8,col 5)");
        assertThat(setProperty.getEnd().get().toString()).isEqualTo("(line 10,col 5)");

        // コード
        assertThat(setProperty.getBody().get().toString())
                .isEqualTo("""
                        {
                            this.property = property;
                        }""");
        assertThat(setProperty.getBody().get().getStatements().stream().map(Node::toString).collect(Collectors.joining()))
                .isEqualTo("this.property = property;");

こんな感じで、パースした内容からいろいろと情報が取得できることがわかります。

検索する

先ほどはCompilationUnitからクラス → フィールドといった順で取得していましたが、find系のメソッドを使うことでクエリーを
使うように検索もできます。

    @Test
    void find() {
        CompilationUnit compilationUnit = StaticJavaParser.parse("""
                class A {
                    String property;
                
                    String getProperty() {
                        return property;
                    }
                
                    void setProperty(String property) {
                        this.property = property;
                    }
                }""");

        List<FieldDeclaration> fields = compilationUnit.findAll(FieldDeclaration.class);
        assertThat(fields).hasSize(1);
        assertThat(fields.getFirst().getVariable(0).getName().asString()).isEqualTo("property");

        List<MethodDeclaration> methods = compilationUnit.findAll(MethodDeclaration.class);
        assertThat(methods).hasSize(2);
        assertThat(methods.getFirst().getNameAsString()).isEqualTo("getProperty");
        assertThat(methods.getLast().getNameAsString()).isEqualTo("setProperty");
    }
パースに失敗する場合

パースに失敗するようなソースコードを与えた場合は、例外がスローされます。それはそうですね、という感じですが。

    @Test
    void parseError() {
        assertThatThrownBy(() -> StaticJavaParser.parse("""
                class A {
                    String property;
                
                    String getProperty() {
                        return property;
                    }
                
                """)
        )
                .isExactlyInstanceOf(ParseProblemException.class)
                .hasMessageContaining("""
                        (line 6,col 5) Parse error. Found <EOF>, expected one of  ";" "<" "@" "_" "abstract" "assert" "boolean" "byte" "char" "class" "default" "double" "enum" "exports" "final" "float" "int" "interface" "long" "module" "native" "non-sealed" "open" "opens" "permits" "private" "protected" "provides" "public" "record" "requires" "sealed" "short" "static" "strictfp" "synchronized" "to" "transient" "transitive" "uses" "void" "volatile" "when" "with" "yield" "{" "}" <IDENTIFIER>
                        Problem stacktrace :\s
                          com.github.javaparser.GeneratedJavaParser.generateParseException(GeneratedJavaParser.java:14940)
                          com.github.javaparser.GeneratedJavaParser.jj_consume_token(GeneratedJavaParser.java:14785)
                          com.github.javaparser.GeneratedJavaParser.ClassOrInterfaceBody(GeneratedJavaParser.java:1476)""");
    }
もうちょっと大きな例で

ここまでHello Worldのような短い例で試してみましたが、もう少し大きなソースコードでやってみましょう。

お題はJakarta Contexts and Dependency Injection(CDI)のCDIクラスにします。

/*
 * JBoss, Home of Professional Open Source
 * Copyright 2013, 2015, Red Hat, Inc., and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,  
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jakarta.enterprise.inject.spi;

import jakarta.enterprise.inject.Instance;

import java.util.Collections;
import java.util.Comparator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.TreeSet;

/**
 * Provides access to the current container.
 *
 * <p>
 * CDI implements {@link Instance} and therefore might be used to perform programmatic lookup.
 * If no qualifier is passed to {@link #select} method, the <code>@Default</code> qualifier is assumed.
 * </p>
 *
 *
 * @author Pete Muir
 * @author Antoine Sabot-Durand
 * @author John D. Ament
 * @since 1.1
 * @param <T> type inherited from {@link Instance}. Always Object for CDI
 */
public abstract class CDI<T> implements Instance<T> {

    private static final Object lock = new Object();
    private static volatile boolean providerSetManually = false;
    protected static volatile Set<CDIProvider> discoveredProviders = null;
    protected static volatile CDIProvider configuredProvider = null;

    /**
     * <p>
     * Get the CDI instance that provides access to the current container.
     * </p>
     * 
     * <p>
     * If there are no providers available, an {@link IllegalStateException} is thrown, otherwise the first provider which can
     * access the container is used.
     * </p>
     * 
     * @throws IllegalStateException if no {@link CDIProvider} is available
     * @return the CDI instance
     */
    public static CDI<Object> current() {
        return getCDIProvider().getCDI();
    }

    /**
     *
     * Obtain the {@link CDIProvider} the user set with {@link #setCDIProvider(CDIProvider)} or the last returned
     * {@link CDIProvider} if it returns valid CDI container. Otherwise use the serviceloader to retrieve the
     * {@link CDIProvider} with the highest priority.
     *
     * @return the {@link CDIProvider} set by user or retrieved by serviceloader
     */
    private static CDIProvider getCDIProvider() {
        try {
            if (configuredProvider != null && configuredProvider.getCDI() != null) {
                return configuredProvider;
            }
        } catch (IllegalStateException e) {
            //if the provider is set manually we do not look for a different provider.
            if (providerSetManually) {
                throw e;
            }
        }
        configuredProvider = null;
        // Discover providers and cache
        if (discoveredProviders == null) {
            synchronized (lock) {
                if (discoveredProviders == null) {
                    findAllProviders();
                }
            }
        }
        configuredProvider = discoveredProviders.stream()
                .filter(CDI::checkProvider)
                .findFirst().orElseThrow(() -> new IllegalStateException("Unable to access CDI"));
        return configuredProvider;
    }

    private static boolean checkProvider(CDIProvider c) {
        try {
            return c.getCDI() != null;
        } catch (IllegalStateException e) {
            return false;
        }
    }

    /**
     * <p>
     * Set the {@link CDIProvider} to use.
     * </p>
     *
     * <p>
     * If a {@link CDIProvider} is set using this method, any provider specified as a service provider will not be used.
     * </p>
     *
     * @param provider the provider to use
     * @throws IllegalStateException if the {@link CDIProvider} is already set
     */
    public static void setCDIProvider(CDIProvider provider) {
        if (provider != null) {
            providerSetManually = true;
            configuredProvider = provider;
        } else {
            throw new IllegalStateException("CDIProvider must not be null");
        }
    }

    private static void findAllProviders() {

        ServiceLoader<CDIProvider> providerLoader;
        Set<CDIProvider> providers = new TreeSet<>(Comparator.comparingInt(CDIProvider::getPriority).reversed());

        providerLoader = SecurityActions.loadService(CDIProvider.class, CDI.class.getClassLoader());

        if(! providerLoader.iterator().hasNext()) {
            throw new IllegalStateException("Unable to locate CDIProvider");
        }

        try {
            providerLoader.forEach(providers::add);
        } catch (ServiceConfigurationError e) {
            throw new IllegalStateException(e);
        }
        CDI.discoveredProviders = Collections.unmodifiableSet(providers);
    }

    // Helper methods

    /**
     * Get the CDI BeanManager for the current context
     *
     * @return the {@link BeanManager}
     */
    public abstract BeanManager getBeanManager();

    /**
     * Get the CDI {@link BeanContainer} for the current context.
     *
     * Default implementation just forwards the call to {@link #getBeanManager()}.
     *
     * @return the {@link BeanContainer}
     */
    public BeanContainer getBeanContainer() {
        return getBeanManager();
    }

}

https://github.com/jakartaee/cdi/blob/4.0.1/api/src/main/java/jakarta/enterprise/inject/spi/CDI.java

メソッドの実装やJavadocコメントを抽出してみましょう。

    @Test
    void moreComplexExample() throws IOException {
        try (InputStream is = URI.create("https://raw.githubusercontent.com/jakartaee/cdi/refs/tags/4.0.1/api/src/main/java/jakarta/enterprise/inject/spi/CDI.java").toURL().openStream()) {
            CompilationUnit compilationUnit = StaticJavaParser.parse(is);

            ClassOrInterfaceDeclaration cdiClass = compilationUnit.getClassByName("CDI").get();

            assertThat(cdiClass.getMethods()).hasSize(7);
            assertThat(cdiClass.getMethods().stream().filter(MethodDeclaration::isPublic).count()).isEqualTo(4);

            MethodDeclaration setCDIProviderMethod = cdiClass.getMethodsByName("setCDIProvider").getFirst();

            // コード
            assertThat(setCDIProviderMethod.getBody().get().toString()).isEqualTo("""
                    {
                        if (provider != null) {
                            providerSetManually = true;
                            configuredProvider = provider;
                        } else {
                            throw new IllegalStateException("CDIProvider must not be null");
                        }
                    }""");
            assertThat(setCDIProviderMethod.getBody().get().getStatements().stream().map(Node::toString).collect(Collectors.joining())).isEqualTo("""
                    if (provider != null) {
                        providerSetManually = true;
                        configuredProvider = provider;
                    } else {
                        throw new IllegalStateException("CDIProvider must not be null");
                    }""");

            // コメントとして
            assertThat(setCDIProviderMethod.getComment().get().asString()).isEqualTo("""
                    /**
                         * <p>
                         * Set the {@link CDIProvider} to use.
                         * </p>
                         *
                         * <p>
                         * If a {@link CDIProvider} is set using this method, any provider specified as a service provider will not be used.
                         * </p>
                         *
                         * @param provider the provider to use
                         * @throws IllegalStateException if the {@link CDIProvider} is already set
                         */""");
            assertThat(setCDIProviderMethod.getComment().get()).isExactlyInstanceOf(TraditionalJavadocComment.class);
            assertThat(setCDIProviderMethod.getJavadocComment().get()).isExactlyInstanceOf(TraditionalJavadocComment.class);

            // Javadocとして
            assertThat(setCDIProviderMethod.getJavadoc().get().getDescription().toText()).isEqualTo("""
                    <p>
                    Set the {@link CDIProvider} to use.
                    </p>
                    
                    <p>
                    If a {@link CDIProvider} is set using this method, any provider specified as a service provider will not be used.
                    </p>""");
            assertThat(setCDIProviderMethod.getJavadoc().get().getBlockTags()).hasSize(2);
            assertThat(setCDIProviderMethod.getJavadoc().get().getBlockTags().getFirst().getType()).isEqualTo(JavadocBlockTag.Type.PARAM);
            assertThat(setCDIProviderMethod.getJavadoc().get().getBlockTags().getLast().getType()).isEqualTo(JavadocBlockTag.Type.THROWS);
            assertThat(setCDIProviderMethod.getJavadoc().get().toText()).isEqualTo("""
                    <p>
                    Set the {@link CDIProvider} to use.
                    </p>
                    
                    <p>
                    If a {@link CDIProvider} is set using this method, any provider specified as a service provider will not be used.
                    </p>
                    
                    @param provider the provider to use
                    @throws IllegalStateException if the {@link CDIProvider} is already set
                    """);
        }
    }

メソッドの実装コードもちゃんと取れています。

コメントについては、「コメント」として扱うか「Javadoc」として扱うかで使い方が変わるようです。

コメントとしての情報が欲しい場合と、内容が欲しい場合で分ける感じでしょうね。

Visitor

CompilationUnitに対して、Visitorを使ってノードをたどって処理を行うこともできます。

VoidVisitorGenericVisitorのどちらかを使いますが、どちらのクラスも実装しなくてはならないメソッドが多いので、
VoidVisitorAdapterクラスおよびGenericVisitorAdapterクラスのサブクラスを作り、興味のあるメソッドをオーバーライド
する感じですね。

    @Test
    void visitor() {
        CompilationUnit compilationUnit = StaticJavaParser.parse("""
                class A {
                    String property;
                
                    String getProperty() {
                        return property;
                    }
                
                    void setProperty(String property) {
                        this.property = property;
                    }
                }""");

        // VoidVisitor
        compilationUnit.accept(
                new VoidVisitorAdapter<>() {
                    @Override
                    public void visit(Parameter parameter, String arg) {
                        assertThat(parameter.getType().asString()).isEqualTo("String");
                        assertThat(parameter.getName().asString()).isEqualTo("property");

                        assertThat(arg).isEqualTo("test");

                        super.visit(parameter, arg);
                    }
                },
                "test");

        // GenericVisitor
        compilationUnit.accept(
                new GenericVisitorAdapter<>() {
                    @Override
                    public Visitable visit(Parameter parameter, String arg) {
                        assertThat(parameter.getType().asString()).isEqualTo("String");
                        assertThat(parameter.getName().asString()).isEqualTo("property");

                        assertThat(arg).isEqualTo("test");

                        return parameter;
                    }
                },
                "test");
    }

今回はどちらもメソッドの引数に対してVisitorを仕込んでみました。

VoidVisitorの場合、ノードをたどり続ける場合はsuper#visitを呼び出す必要があります。GenericVisitorの場合は
第1引数のVisitableをメソッドの戻り値にします。nullを返した場合はそこで打ち切りです。

詳しくはこのあたりを。

VoidVisitor - javaparser-core 3.28.0 javadoc

VoidVisitorAdapter - javaparser-core 3.28.0 javadoc

GenericVisitor - javaparser-core 3.28.0 javadoc

GenericVisitorAdapter - javaparser-core 3.28.0 javadoc

ちなみにJavaParserのサンプルは、GenericVisitorを実装したModifierVisitorクラスを使った例になっています。

https://github.com/javaparser/javaparser-maven-sample/blob/master/src/main/java/com/yourorganization/maven_sample/LogicPositivizer.java

ノードの種類を見る

JavaParserではパースした結果を「ノード」として扱います。

Node - javaparser-core 3.28.0 javadoc

どれくらいの種類があるのか、Jandexで抽出してみました。

    @Test
    void nodeSubClasses() throws IOException {
        Indexer indexer = new Indexer();

        try (JarFile javaParserJarFile = new JarFile("/path/to/.m2/repository/com/github/javaparser/javaparser-core/3.28.0/javaparser-core-3.28.0.jar")) {
            Collections.list(javaParserJarFile.entries()).stream()
                    .filter(entry -> entry.getName().endsWith(".class"))
                    .forEach(entry -> {
                        try (InputStream is = javaParserJarFile.getInputStream(entry)) {
                            indexer.index(is);
                        } catch (IOException e) {
                            throw new UncheckedIOException(e);
                        }
                    });
        }

        Index index = indexer.complete();

        Collection<ClassInfo> nodeSubClasses =
                index.getAllKnownSubclasses(DotName.createSimple(Node.class));
        assertThat(nodeSubClasses.stream().map(ClassInfo::name)).containsExactlyInAnyOrder(
                Stream.of(
                        com.github.javaparser.ast.ArrayCreationLevel.class,
                        com.github.javaparser.ast.CompilationUnit.class,
                        com.github.javaparser.ast.ImportDeclaration.class,
                        com.github.javaparser.ast.Modifier.class,
                        com.github.javaparser.ast.PackageDeclaration.class,
                        com.github.javaparser.ast.body.AnnotationDeclaration.class,
                        com.github.javaparser.ast.body.AnnotationMemberDeclaration.class,
                        com.github.javaparser.ast.body.BodyDeclaration.class,
                        com.github.javaparser.ast.body.CallableDeclaration.class,
                        com.github.javaparser.ast.body.ClassOrInterfaceDeclaration.class,
                        com.github.javaparser.ast.body.CompactConstructorDeclaration.class,
                        com.github.javaparser.ast.body.ConstructorDeclaration.class,
                        com.github.javaparser.ast.body.EnumConstantDeclaration.class,
                        com.github.javaparser.ast.body.EnumDeclaration.class,
                        com.github.javaparser.ast.body.FieldDeclaration.class,
                        com.github.javaparser.ast.body.InitializerDeclaration.class,
                        com.github.javaparser.ast.body.MethodDeclaration.class,
                        com.github.javaparser.ast.body.Parameter.class,
                        com.github.javaparser.ast.body.ReceiverParameter.class,
                        com.github.javaparser.ast.body.RecordDeclaration.class,
                        com.github.javaparser.ast.body.TypeDeclaration.class,
                        com.github.javaparser.ast.body.VariableDeclarator.class,
                        com.github.javaparser.ast.comments.BlockComment.class,
                        com.github.javaparser.ast.comments.Comment.class,
                        com.github.javaparser.ast.comments.JavadocComment.class,
                        com.github.javaparser.ast.comments.LineComment.class,
                        com.github.javaparser.ast.comments.MarkdownComment.class,
                        com.github.javaparser.ast.comments.TraditionalJavadocComment.class,
                        com.github.javaparser.ast.expr.AnnotationExpr.class,
                        com.github.javaparser.ast.expr.ArrayAccessExpr.class,
                        com.github.javaparser.ast.expr.ArrayCreationExpr.class,
                        com.github.javaparser.ast.expr.ArrayInitializerExpr.class,
                        com.github.javaparser.ast.expr.AssignExpr.class,
                        com.github.javaparser.ast.expr.BinaryExpr.class,
                        com.github.javaparser.ast.expr.BooleanLiteralExpr.class,
                        com.github.javaparser.ast.expr.CastExpr.class,
                        com.github.javaparser.ast.expr.CharLiteralExpr.class,
                        com.github.javaparser.ast.expr.ClassExpr.class,
                        com.github.javaparser.ast.expr.ComponentPatternExpr.class,
                        com.github.javaparser.ast.expr.ConditionalExpr.class,
                        com.github.javaparser.ast.expr.DoubleLiteralExpr.class,
                        com.github.javaparser.ast.expr.EnclosedExpr.class,
                        com.github.javaparser.ast.expr.Expression.class,
                        com.github.javaparser.ast.expr.FieldAccessExpr.class,
                        com.github.javaparser.ast.expr.InstanceOfExpr.class,
                        com.github.javaparser.ast.expr.IntegerLiteralExpr.class,
                        com.github.javaparser.ast.expr.LambdaExpr.class,
                        com.github.javaparser.ast.expr.LiteralExpr.class,
                        com.github.javaparser.ast.expr.LiteralStringValueExpr.class,
                        com.github.javaparser.ast.expr.LongLiteralExpr.class,
                        com.github.javaparser.ast.expr.MarkerAnnotationExpr.class,
                        com.github.javaparser.ast.expr.MatchAllPatternExpr.class,
                        com.github.javaparser.ast.expr.MemberValuePair.class,
                        com.github.javaparser.ast.expr.MethodCallExpr.class,
                        com.github.javaparser.ast.expr.MethodReferenceExpr.class,
                        com.github.javaparser.ast.expr.Name.class,
                        com.github.javaparser.ast.expr.NameExpr.class,
                        com.github.javaparser.ast.expr.NormalAnnotationExpr.class,
                        com.github.javaparser.ast.expr.NullLiteralExpr.class,
                        com.github.javaparser.ast.expr.ObjectCreationExpr.class,
                        com.github.javaparser.ast.expr.PatternExpr.class,
                        com.github.javaparser.ast.expr.RecordPatternExpr.class,
                        com.github.javaparser.ast.expr.SimpleName.class,
                        com.github.javaparser.ast.expr.SingleMemberAnnotationExpr.class,
                        com.github.javaparser.ast.expr.StringLiteralExpr.class,
                        com.github.javaparser.ast.expr.SuperExpr.class,
                        com.github.javaparser.ast.expr.SwitchExpr.class,
                        com.github.javaparser.ast.expr.TextBlockLiteralExpr.class,
                        com.github.javaparser.ast.expr.ThisExpr.class,
                        com.github.javaparser.ast.expr.TypeExpr.class,
                        com.github.javaparser.ast.expr.TypePatternExpr.class,
                        com.github.javaparser.ast.expr.UnaryExpr.class,
                        com.github.javaparser.ast.expr.VariableDeclarationExpr.class,
                        com.github.javaparser.ast.modules.ModuleDeclaration.class,
                        com.github.javaparser.ast.modules.ModuleDirective.class,
                        com.github.javaparser.ast.modules.ModuleExportsDirective.class,
                        com.github.javaparser.ast.modules.ModuleOpensDirective.class,
                        com.github.javaparser.ast.modules.ModuleProvidesDirective.class,
                        com.github.javaparser.ast.modules.ModuleRequiresDirective.class,
                        com.github.javaparser.ast.modules.ModuleUsesDirective.class,
                        com.github.javaparser.ast.stmt.AssertStmt.class,
                        com.github.javaparser.ast.stmt.BlockStmt.class,
                        com.github.javaparser.ast.stmt.BreakStmt.class,
                        com.github.javaparser.ast.stmt.CatchClause.class,
                        com.github.javaparser.ast.stmt.ContinueStmt.class,
                        com.github.javaparser.ast.stmt.DoStmt.class,
                        com.github.javaparser.ast.stmt.EmptyStmt.class,
                        com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt.class,
                        com.github.javaparser.ast.stmt.ExpressionStmt.class,
                        com.github.javaparser.ast.stmt.ForEachStmt.class,
                        com.github.javaparser.ast.stmt.ForStmt.class,
                        com.github.javaparser.ast.stmt.IfStmt.class,
                        com.github.javaparser.ast.stmt.LabeledStmt.class,
                        com.github.javaparser.ast.stmt.LocalClassDeclarationStmt.class,
                        com.github.javaparser.ast.stmt.LocalRecordDeclarationStmt.class,
                        com.github.javaparser.ast.stmt.ReturnStmt.class,
                        com.github.javaparser.ast.stmt.Statement.class,
                        com.github.javaparser.ast.stmt.SwitchEntry.class,
                        com.github.javaparser.ast.stmt.SwitchStmt.class,
                        com.github.javaparser.ast.stmt.SynchronizedStmt.class,
                        com.github.javaparser.ast.stmt.ThrowStmt.class,
                        com.github.javaparser.ast.stmt.TryStmt.class,
                        com.github.javaparser.ast.stmt.UnparsableStmt.class,
                        com.github.javaparser.ast.stmt.WhileStmt.class,
                        com.github.javaparser.ast.stmt.YieldStmt.class,
                        com.github.javaparser.ast.type.ArrayType.class,
                        com.github.javaparser.ast.type.ClassOrInterfaceType.class,
                        com.github.javaparser.ast.type.IntersectionType.class,
                        com.github.javaparser.ast.type.PrimitiveType.class,
                        com.github.javaparser.ast.type.ReferenceType.class,
                        com.github.javaparser.ast.type.Type.class,
                        com.github.javaparser.ast.type.TypeParameter.class,
                        com.github.javaparser.ast.type.UnionType.class,
                        com.github.javaparser.ast.type.UnknownType.class,
                        com.github.javaparser.ast.type.VarType.class,
                        com.github.javaparser.ast.type.VoidType.class,
                        com.github.javaparser.ast.type.WildcardType.class
                ).map(DotName::createSimple).toList().toArray(new DotName[0])
        );
    }

けっこうな種類がありますね。Javaの構文が反映されている感じです。

よくわからないノードの場合は、Javadocを見ましょう…。

今回はこんなところでしょうか。

おわりに

Javaのソースコードを解析できるJavaParserを試してみました。

最初のとっかかりが、大変とっつきにくかったです。ドキュメントは書籍だったのですが、購入してもよかったかなと
ちょっと思いました…。

ソースコードから情報を抽出したい時には大変便利なライブラリーかなと思いますので、覚えておきましょう。




以上の内容はhttps://kazuhira-r.hatenablog.com/entry/2026/02/28/183656より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14