ソースコード上のクラスとメソッドを一覧化したい & メソッドが呼び出しているメソッドを一覧化したい (Java Parser)

お仕事の話ですが、次のような要求をJava Parserを用いて実現したので、メモとして残しておきます。

- javaファイルに定義されているクラスとメソッドを一覧化したい
- あわせてそのメソッドが呼び出しているメソッドをメソッドごとに一覧化したい

以下はjava.util.Properties.javaを対象のソースコードとして、上記の要求を満たすサンプルプログラムです。[なお、Properties.javaはopenjdk-jdk11より拝借しています。](https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.base/share/classes/java/util/Properties.java)

```java
public static void main(String[] args) throws IOException {
	CompilationUnit compilationUnit = StaticJavaParser.parse(Paths.get("Properties.java"));
	compilationUnit.accept(new ClassOrInterfaceDeclarationVisitor(), null);
}

static class ClassOrInterfaceDeclarationVisitor extends VoidVisitorAdapter<Void> {
	@Override
	public void visit(ClassOrInterfaceDeclaration n, Void arg) {
		System.out.println(n.getFullyQualifiedName().get());
		for (MethodDeclaration md : n.getMethods()) {
			System.out.println("\t" + md.getDeclarationAsString(true, true, true));
			md.accept(new MethodCallExprVisitor(), null);
		}
		super.visit(n, arg);
	}
}

static class MethodCallExprVisitor extends VoidVisitorAdapter<Void> {
	@Override
	public void visit(MethodCallExpr n, Void arg) {
		System.out.println("\t\t" + n);
		super.visit(n, arg);
	}
}
```

これを実行すると、以下のような結果がコンソールに出力されるはずです。

<details>
<summary>長いのでおりたたみ</summary>

```
java.util.Properties
	public synchronized Object setProperty(String key, String value)
		put(key, value)
	public synchronized void load(Reader reader) throws IOException
		Objects.requireNonNull(reader, "reader parameter is null")
		load0(new LineReader(reader))
	public synchronized void load(InputStream inStream) throws IOException
		Objects.requireNonNull(inStream, "inStream parameter is null")
		load0(new LineReader(inStream))
	private void load0(LineReader lr) throws IOException
		loadConvert(lr.lineBuf, 0, keyLen, convtBuf)
		loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf)
		put(key, value)
		lr.readLine()
	private String loadConvert(char[] in, int off, int len, char[] convtBuf)
	private String saveConvert(String theString, boolean escapeSpace, boolean escapeUnicode)
		theString.length()
		theString.charAt(x)
		outBuffer.append('\\')
		outBuffer.append('\\')
		outBuffer.append(aChar)
		outBuffer.append('\\')
		outBuffer.append(' ')
		outBuffer.append('\\')
		outBuffer.append('t')
		outBuffer.append('\\')
		outBuffer.append('n')
		outBuffer.append('\\')
		outBuffer.append('r')
		outBuffer.append('\\')
		outBuffer.append('f')
		outBuffer.append('\\')
		outBuffer.append(aChar)
		outBuffer.append(aChar)
		outBuffer.append('\\')
		outBuffer.append('u')
		outBuffer.append(toHex((aChar >> 12) & 0xF))
		toHex((aChar >> 12) & 0xF)
		outBuffer.append(toHex((aChar >> 8) & 0xF))
		toHex((aChar >> 8) & 0xF)
		outBuffer.append(toHex((aChar >> 4) & 0xF))
		toHex((aChar >> 4) & 0xF)
		outBuffer.append(toHex(aChar & 0xF))
		toHex(aChar & 0xF)
		outBuffer.toString()
	private static void writeComments(BufferedWriter bw, String comments) throws IOException
		bw.write("#")
		comments.length()
		comments.charAt(current)
		bw.write(comments.substring(last, current))
		comments.substring(last, current)
		bw.newLine()
		comments.charAt(current + 1)
		comments.charAt(current + 1)
		comments.charAt(current + 1)
		bw.write("#")
		toHex((c >> 12) & 0xf)
		toHex((c >> 8) & 0xf)
		toHex((c >> 4) & 0xf)
		toHex(c & 0xf)
		bw.write(new String(uu))
		bw.write(comments.substring(last, current))
		comments.substring(last, current)
		bw.newLine()
	public void save(OutputStream out, String comments)
		store(out, comments)
	public void store(Writer writer, String comments) throws IOException
		store0((writer instanceof BufferedWriter) ? (BufferedWriter) writer : new BufferedWriter(writer), comments, false)
	public void store(OutputStream out, String comments) throws IOException
		store0(new BufferedWriter(new OutputStreamWriter(out, "8859_1")), comments, true)
	private void store0(BufferedWriter bw, String comments, boolean escUnicode) throws IOException
		writeComments(bw, comments)
		bw.write("#" + new Date().toString())
		new Date().toString()
		bw.newLine()
		e.getKey()
		e.getValue()
		saveConvert(key, true, escUnicode)
		saveConvert(val, false, escUnicode)
		bw.write(key + "=" + val)
		bw.newLine()
		entrySet()
		bw.flush()
	public synchronized void loadFromXML(InputStream in) throws IOException, InvalidPropertiesFormatException
		Objects.requireNonNull(in)
		handler.load(this, in)
		in.close()
	public void storeToXML(OutputStream os, String comment) throws IOException
		storeToXML(os, comment, "UTF-8")
	public void storeToXML(OutputStream os, String comment, String encoding) throws IOException
		Objects.requireNonNull(os)
		Objects.requireNonNull(encoding)
		Charset.forName(encoding)
		storeToXML(os, comment, charset)
	public void storeToXML(OutputStream os, String comment, Charset charset) throws IOException
		Objects.requireNonNull(os, "OutputStream")
		Objects.requireNonNull(charset, "Charset")
		handler.store(this, os, comment, charset)
	public String getProperty(String key)
		map.get(key)
		defaults.getProperty(key)
	public String getProperty(String key, String defaultValue)
		getProperty(key)
	public Enumeration<?> propertyNames()
		enumerate(h)
		h.keys()
	public Set<String> stringPropertyNames()
		enumerateStringProperties(h)
		Collections.unmodifiableSet(h.keySet())
		h.keySet()
	public void list(PrintStream out)
		out.println("-- listing properties --")
		enumerate(h)
		e.getKey()
		e.getValue()
		val.length()
		val.substring(0, 37)
		out.println(key + "=" + val)
		h.entrySet()
	public void list(PrintWriter out)
		out.println("-- listing properties --")
		enumerate(h)
		e.getKey()
		e.getValue()
		val.length()
		val.substring(0, 37)
		out.println(key + "=" + val)
		h.entrySet()
	private void enumerate(Map<String, Object> h)
		defaults.enumerate(h)
		e.getKey()
		h.put(key, e.getValue())
		e.getValue()
		entrySet()
	private void enumerateStringProperties(Map<String, String> h)
		defaults.enumerateStringProperties(h)
		e.getKey()
		e.getValue()
		h.put((String) k, (String) v)
		entrySet()
	private static char toHex(int nibble)
	public int size()
		map.size()
	public boolean isEmpty()
		map.isEmpty()
	public Enumeration<Object> keys()
		Collections.enumeration(map.keySet())
		map.keySet()
	public Enumeration<Object> elements()
		Collections.enumeration(map.values())
		map.values()
	public boolean contains(Object value)
		map.contains(value)
	public boolean containsValue(Object value)
		map.containsValue(value)
	public boolean containsKey(Object key)
		map.containsKey(key)
	public Object get(Object key)
		map.get(key)
	public synchronized Object put(Object key, Object value)
		map.put(key, value)
	public synchronized Object remove(Object key)
		map.remove(key)
	public synchronized void putAll(Map<?, ?> t)
		map.putAll(t)
	public synchronized void clear()
		map.clear()
	public synchronized String toString()
		map.toString()
	public Set<Object> keySet()
		Collections.synchronizedSet(map.keySet(), this)
		map.keySet()
	public Collection<Object> values()
		Collections.synchronizedCollection(map.values(), this)
		map.values()
	public Set<Map.Entry<Object, Object>> entrySet()
		Collections.synchronizedSet(new EntrySet(map.entrySet()), this)
		map.entrySet()
	public synchronized boolean equals(Object o)
		map.equals(o)
	public synchronized int hashCode()
		map.hashCode()
	public Object getOrDefault(Object key, Object defaultValue)
		map.getOrDefault(key, defaultValue)
	public synchronized void forEach(BiConsumer<? super Object, ? super Object> action)
		map.forEach(action)
	public synchronized void replaceAll(BiFunction<? super Object, ? super Object, ?> function)
		map.replaceAll(function)
	public synchronized Object putIfAbsent(Object key, Object value)
		map.putIfAbsent(key, value)
	public synchronized boolean remove(Object key, Object value)
		map.remove(key, value)
	public synchronized boolean replace(Object key, Object oldValue, Object newValue)
		map.replace(key, oldValue, newValue)
	public synchronized Object replace(Object key, Object value)
		map.replace(key, value)
	public synchronized Object computeIfAbsent(Object key, Function<? super Object, ?> mappingFunction)
		map.computeIfAbsent(key, mappingFunction)
	public synchronized Object computeIfPresent(Object key, BiFunction<? super Object, ? super Object, ?> remappingFunction)
		map.computeIfPresent(key, remappingFunction)
	public synchronized Object compute(Object key, BiFunction<? super Object, ? super Object, ?> remappingFunction)
		map.compute(key, remappingFunction)
	public synchronized Object merge(Object key, Object value, BiFunction<? super Object, ? super Object, ?> remappingFunction)
		map.merge(key, value, remappingFunction)
	protected void rehash()
	public synchronized Object clone()
		cloneHashtable()
	 void writeHashtable(ObjectOutputStream s) throws IOException
		map.size()
		entryStack.add(entry.getValue())
		entry.getValue()
		entryStack.add(entry.getKey())
		entry.getKey()
		map.entrySet()
		entryStack.size()
		defaultWriteHashtable(s, length, loadFactor)
		s.writeInt(length)
		s.writeInt(count)
		s.writeObject(entryStack.get(i))
		entryStack.get(i)
		entryStack.size()
	 void readHashtable(ObjectInputStream s) throws IOException, ClassNotFoundException
		s.defaultReadObject()
		s.readInt()
		s.readInt()
		SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Map.Entry[].class, HashMap.tableSizeFor((int) (elements / 0.75)))
		HashMap.tableSizeFor((int) (elements / 0.75))
		SharedSecrets.getJavaObjectInputStreamAccess()
		s.readObject()
		s.readObject()
		map.put(key, value)
java.util.Properties.LineReader
	 int readLine() throws IOException
		inStream.read(inByteBuf)
		reader.read(inCharBuf)
		inStream.read(inByteBuf)
		reader.read(inCharBuf)
		System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length)
java.util.Properties.EntrySet
	public int size()
		entrySet.size()
	public boolean isEmpty()
		entrySet.isEmpty()
	public boolean contains(Object o)
		entrySet.contains(o)
	public Object[] toArray()
		entrySet.toArray()
	public T[] toArray(T[] a)
		entrySet.toArray(a)
	public void clear()
		entrySet.clear()
	public boolean remove(Object o)
		entrySet.remove(o)
	public boolean add(Map.Entry<Object, Object> e)
	public boolean addAll(Collection<? extends Map.Entry<Object, Object>> c)
	public boolean containsAll(Collection<?> c)
		entrySet.containsAll(c)
	public boolean removeAll(Collection<?> c)
		entrySet.removeAll(c)
	public boolean retainAll(Collection<?> c)
		entrySet.retainAll(c)
	public Iterator<Map.Entry<Object, Object>> iterator()
		entrySet.iterator()

```
</details>

サンプリングでチェックしてみましょう。storeToXMLメソッドの実装は以下の通りです。

```java
public void storeToXML(OutputStream os, String comment, String encoding)
    throws IOException {
    Objects.requireNonNull(os);
    Objects.requireNonNull(encoding);

    try {
        Charset charset = Charset.forName(encoding);
        storeToXML(os, comment, charset);
    } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
        throw new UnsupportedEncodingException(encoding);
    }
}
```

これに対応する解析結果は以下の通りです。

```
public void storeToXML(OutputStream os, String comment, String encoding) throws IOException
	Objects.requireNonNull(os)
	Objects.requireNonNull(encoding)
	Charset.forName(encoding)
	storeToXML(os, comment, charset)
```

ただしく解析できていそうですね。

**環境 : pom.xml抜粋**

```xml
<properties>
  <maven.compiler.target>11</maven.compiler.target>
  <maven.compiler.source>11</maven.compiler.source>
</properties>
<dependencies>
  <dependency>
    <groupId>com.github.javaparser</groupId>
    <artifactId>javaparser-core</artifactId>
    <version>3.25.4</version>
  </dependency>
</dependencies>
```