これは、なにをしたくて書いたもの?
Javaのソースコードを解析できるライブラリーのひとつとして、JavaParserというものがあります。
Javaのソースコードをパースできるものをちょっと探していたので、まずはこちらを試してみようかなということで。
JavaParser
JavaParserのWebサイトはこちら。
GitHubリポジトリーはこちら。
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を使ってノードをたどって処理を行うこともできます。
VoidVisitorとGenericVisitorのどちらかを使いますが、どちらのクラスも実装しなくてはならないメソッドが多いので、
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クラスを使った例になっています。
ノードの種類を見る
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を試してみました。
最初のとっかかりが、大変とっつきにくかったです。ドキュメントは書籍だったのですが、購入してもよかったかなと
ちょっと思いました…。
ソースコードから情報を抽出したい時には大変便利なライブラリーかなと思いますので、覚えておきましょう。