java源文件编译成jar_从源文件和JAR文件构建Java代码模型

java源文件编译成jar

最近,我花了一些时间来研究有效java ,该方法正在GitHub上达到300星(可以免费帮助实现目标:D)。

Effectivejava是在您的Java代码上运行查询的工具。 它基于我参与的另一个项目javaparser 。 Javaparser将Java源代码作为输入,并生成一个抽象语法树(AST)。 我们可以直接在AST上执行简单的分析。 例如,我们可以找出哪些方法采用超过5个参数(您可能希望对其进行重构……)。 但是,更复杂的分析要求解析符号

在这篇文章中,我将介绍在考虑源代码和JAR文件的情况下如何实现符号解析。 在第一篇文章中,我们将在源代码和JAR文件上建立同质视图,在下一篇文章中,我们将探索这些模型来解决这些符号。

代码可在GitHub上的有效Java的分支symbolsolver中获得。

解析符号

出于什么原因,我们需要解析符号?

给出以下代码:

foo.method(a, b, c);

我们需要弄清楚foomethodabc是什么。 它们是否引用局部变量? 给当前方法的参数? 到在类中声明的字段? 要从超类继承的字段? 他们有什么类型? 为了回答这个问题,我们需要能够解析符号。

为了解决符号,我们可以浏览AST并应用作用域规则。 例如,我们可以查看某个符号是否对应于局部变量。 如果没有,我们可以在该方法的参数中查找。 如果仍然找不到对应关系,则需要在类声明的字段中查找,如果仍然不走运,则可能必须在此类继承的字段中走运。

现在,作用域规则比我刚刚描述的一小步要复杂得多。 由于重载,解决方法特别复杂。 但是,一个关键点是,要解决符号,我们通常需要在导入的类,扩展的类和外部类中进行查找,这些类可能是项目的一部分,也可以作为依赖项导入。

因此,要解决符号,我们需要寻找相应的声明:

  1. 根据我们正在检查的项目类别的AST
  2. 在用作依赖项的JAR文件中包含的类中

Javaparser为我们提供了第一点所需的AST,对于第二点,我们将使用Javassist在JAR文件中构建类的模型。

建立JAR文件中包含的类的模型

我们的符号求解器应按顺序在条目列表(我们的类路径条目)中查找,并查看是否可以在其中找到某个类。 为此,我们需要打开JAR文件并在其内容中查找。 出于性能原因,我们可能希望构建给定JAR中包含的元素的缓存。

(ns app.jarloading
  (:use [app.javaparser])
  (:use [app.operations])
  (:use [app.utils])
  (:import [app.operations Operation]))

(import java.net.URLDecoder)
(import java.util.jar.JarEntry)
(import java.util.jar.JarFile)
(import javassist.ClassPool)
(import javassist.CtClass)

; An element on the classpath (a single class, interface, enum or resource file)
(defrecord ClasspathElement [resource path contentAsStreamThunk])

(defn- jarEntryToClasspathElement [jarFile jarEntry]
  (let [name (.getName jarEntry)
        content (fn [] (.getInputStream jarFile jarEntry))]
    (ClasspathElement. jarFile name content)))

(defn getElementsEntriesInJar
  "Return a set of ClasspathElements"
  [pathToJarFile]
  (let [url (URLDecoder/decode pathToJarFile "UTF-8")
        jarfile (new JarFile url)
        entries (enumeration-seq (.entries jarfile))
        entries' (filter (fn [e] (not (.isDirectory e))) entries )]
    (map (partial jarEntryToClasspathElement jarfile) entries')))

(defn getClassesEntriesInJar
  "Return a set of ClasspathElements"
  [pathToJarFile]
  (filter (fn [e] (.endsWith (.path e) ".class")) (getElementsEntriesInJar pathToJarFile)))

(defn pathToTypeName [path]
  (if (.endsWith path ".class")
    (let [path' (.substring path 0 (- (.length path) 6))
          path'' (clojure.string/replace path' #"/" ".")
          path''' (clojure.string/replace path'' "$" ".")]
      path''')
    (throw (IllegalArgumentException. "Path not ending with .class"))))

(defn findEntry
  "return the ClasspathElement corresponding to the given name, or nil"
  [typeName classEntries]
  (first (filter (fn [e] (= typeName (pathToTypeName (.path e)))) classEntries)))

(defn findType
  "return the CtClass corresponding to the given name, or nil"
  [typeName classEntries]
  (let [entry (findEntry typeName classEntries)
        classPool (ClassPool/getDefault)]
    (if entry
      (.makeClass classPool ((.contentAsStreamThunk entry)))
      nil)))

我们如何开始? 首先,我们阅读jar中列出的条目( getElementEntriesInJar )。 这样,我们得到了ClasspathElements的列表。 然后,我们仅关注.class文件( getClassesEntriesInJar )。 每个jar应调用一次此方法,并且应将结果缓存。 给定ClasspathElement列表,然后我们可以搜索与给定名称对应的元素(例如com.github.javaparser.ASTParser )。 为此,我们可以使用方法findEntry 或者,我们也可以使用Javassist加载该类: findType方法执行的操作,返回CtClass的实例。

为什么不仅仅使用反射?

有人可能会认为,仅在有效java的类路径中添加依赖项,然后使用常规的类加载器和反射来获取所需的信息会更容易。 虽然这会更容易,但是存在一些缺点:

  1. 当加载一个类时,将执行静态初始化程序,这可能不是我们想要的
  2. 它可能与有效Java的实际依赖项冲突。
  3. 最后,并非所有字节码中可用的信息都可以通过反射API轻松检索到

解决符号:结合异构模型

现在,要解决符号问题,我们将必须实现作用域规则,并浏览从Javaparser获得的AST和从Javassist获得的CtClass 我们将在以后的博客文章中看到详细信息,但是我们需要首先考虑另一个方面。 考虑以下代码:

package me.tomassetti;

import com.github.someproject.ClassInJar;

public class MyClass extends ClassInJar {
    private int myDeclaredField;

    public int foo(){
        return myDeclaredField + myInheritedField;
    }
}

在这种情况下,我们假设有一个包含类com.github.someproject.ClassInJar的JAR,该类声明了字段myInheritedField 当我们求解符号时,将具有以下映射:

  • myDeclaredField将被解析为com.github.javaparser.ast.body.VariableDeclarator的一个实例(在JavaParser类我们有映射到结构,如私人INT A,B,C型FieldDeclaration的节点; VariableDeclarators代替点到单个字段例如abc
  • myInheritedField将解析为javassist.CtField的实例

问题在于我们希望能够以同质的方式对待它们:我们应该能够使用相同的函数来对待每个字段,而不管它们的起源(JAR文件还是Java源文件)。 为此,我们将使用clojure 协议构建通用视图。 我倾向于将clojure的协议视为与Java 接口等效。

(defprotocol FieldDecl
  (fieldName [this]))

(extend-protocol FieldDecl
  com.github.javaparser.ast.body.VariableDeclarator
  (fieldName [this]
    (.getName (.getId this))))

(extend-protocol FieldDecl
  javassist.CtField
  (fieldName [this]
    (.getName this)))

在Java中,我们必须构建适配器,实现新的接口( FieldDecl )并将现有的类( VariableDeclaratorCtField )包装在Clojure中,我们只能说这些类扩展了协议,我们已经完成了。

现在我们可以将每个字段都视为fieldDecl ,并且可以在每个字段上调用fieldName 我们仍然需要弄清楚如何解决字段类型 为此,我们需要研究符号解析,尤其是类型解析,这是我们的下一步。

结论

Java代码的构建模型使我着迷了一段时间。 作为我的硕士论文的一部分,我写了一个与现有Java代码交互的DSL(我也有编辑器,写为Eclipse插件和代码生成器:这很酷)。 在DSL中,可以使用源代码和JAR文件指定对Java类的引用。 我使用的是EMF,并且可能在该项目中采用了JaMoPP和Javassist。

后来,我建立了CodeModels库,以分析几种语言(Java,JavaScript,Ruby,Html等)的AST。

我认为构建用于操作代码的工具是元编程的一种非常有趣的形式,并且应该在每个开发人员的工具箱中。 我计划花更多的时间来使用有效的java。 有趣的时刻来了。

随时分享评论和建议!

翻译自: https://www.javacodegeeks.com/2015/08/building-models-of-java-code-from-source-and-jar-files.html

java源文件编译成jar

你可能感兴趣的:(java源文件编译成jar_从源文件和JAR文件构建Java代码模型)