1+ package com .realtimetech .reflection .classfile .compiler ;
2+
3+ import java .io .File ;
4+ import java .io .IOException ;
5+ import java .nio .charset .StandardCharsets ;
6+ import java .nio .file .Files ;
7+ import java .util .ArrayList ;
8+ import java .util .Arrays ;
9+ import java .util .HashMap ;
10+ import java .util .LinkedList ;
11+ import java .util .List ;
12+ import java .util .UUID ;
13+
14+ import javax .tools .Diagnostic ;
15+ import javax .tools .DiagnosticCollector ;
16+ import javax .tools .JavaCompiler ;
17+ import javax .tools .JavaFileObject ;
18+ import javax .tools .StandardJavaFileManager ;
19+ import javax .tools .ToolProvider ;
20+
21+ import com .realtimetech .reflection .classfile .compiler .Message .MessageType ;
22+ import com .realtimetech .reflection .classloader .ClassDynamicLoader ;
23+
24+ public class ClassCompiler {
25+ private File jdkDirectory ;
26+
27+ private HashMap <String , List <Source >> sourcesMap ;
28+
29+ private List <File > classpathFiles ;
30+
31+ private ClassLoader parentClassLoader ;
32+ private ClassDynamicLoader classDynamicLoader ;
33+
34+ private ArrayList <Message > compileMessages ;
35+
36+ public ClassCompiler (File jdkDirectory ) {
37+ this (ClassLoader .getSystemClassLoader (), jdkDirectory );
38+ }
39+
40+ public ClassCompiler (ClassLoader parentClassLoader , File jdkDirectory ) {
41+ this .parentClassLoader = parentClassLoader ;
42+
43+ this .jdkDirectory = jdkDirectory ;
44+ this .sourcesMap = new HashMap <String , List <Source >>();
45+ this .classpathFiles = new LinkedList <File >();
46+ this .compileMessages = new ArrayList <Message >();
47+ }
48+
49+ public ClassCompiler addSource (String packageName , Source source ) {
50+ if (!this .sourcesMap .containsKey (packageName )) {
51+ this .sourcesMap .put (packageName , new LinkedList <Source >());
52+ }
53+
54+ this .sourcesMap .get (packageName ).add (source );
55+
56+ return this ;
57+ }
58+
59+ public Source getSource (String packageName , String fileName ) {
60+ if (this .sourcesMap .containsKey (packageName )) {
61+ List <Source > sources = this .sourcesMap .get (packageName );
62+
63+ for (Source source : sources ) {
64+ if (source .getFileName ().equalsIgnoreCase (fileName )) {
65+ return source ;
66+ }
67+ }
68+ }
69+
70+ return null ;
71+ }
72+
73+ public ClassCompiler addClasspath (File classpath ) {
74+ if (classpath .exists ()) {
75+ String classpathName = classpath .getName ();
76+
77+ if (classpathName .endsWith (".jar" ) && classpathName .endsWith (".class" ) && classpathName .endsWith (".zip" )) {
78+ this .classpathFiles .add (classpath );
79+ }
80+ }
81+
82+ return this ;
83+ }
84+
85+ public List <File > getClasspathFiles () {
86+ return classpathFiles ;
87+ }
88+
89+ public File getJdkDirectory () {
90+ return jdkDirectory ;
91+ }
92+
93+ public ClassDynamicLoader getClassDynamicLoader () {
94+ return this .classDynamicLoader ;
95+ }
96+
97+ private void searchClassInDirectory (List <File > classFiles , File directory ) {
98+ File [] files = directory .listFiles ();
99+
100+ if (files != null ) {
101+ for (File file : files ) {
102+ if (file .isDirectory ()) {
103+ searchClassInDirectory (classFiles , file );
104+ } else if (file .getName ().endsWith (".class" )) {
105+ classFiles .add (file );
106+ }
107+ }
108+ }
109+ }
110+
111+ public boolean compile () throws IOException {
112+ System .setProperty ("java.home" , this .jdkDirectory .getAbsolutePath ());
113+
114+ File workingDirectory ;
115+
116+ do {
117+ workingDirectory = new File ("build" + File .separator + UUID .randomUUID ().toString () + File .separator );
118+ } while (workingDirectory .exists ());
119+
120+ List <File > sourceFiles = new LinkedList <File >();
121+ List <File > classFiles = new LinkedList <File >();
122+
123+ for (String packageName : sourcesMap .keySet ()) {
124+ List <Source > sources = sourcesMap .get (packageName );
125+
126+ for (Source source : sources ) {
127+ File packageFolder = new File (workingDirectory .getAbsolutePath () + File .separator + packageName .replace ("." , File .separator ) + File .separator );
128+ File sourceFile = new File (packageFolder .getPath () + File .separator + source .getFileName ());
129+ packageFolder .mkdirs ();
130+
131+ Files .write (sourceFile .toPath (), source .getSourceCode ().getBytes (StandardCharsets .UTF_8 ));
132+
133+ sourceFiles .add (sourceFile );
134+ }
135+ }
136+
137+ JavaCompiler compiler = ToolProvider .getSystemJavaCompiler ();
138+
139+ String classPath = System .getProperty ("java.class.path" );
140+
141+ for (File classpathFile : classpathFiles ) {
142+ classPath += File .pathSeparator + classpathFile .getAbsolutePath ();
143+ }
144+
145+ DiagnosticCollector <JavaFileObject > diagnostics = new DiagnosticCollector <JavaFileObject >();
146+ Iterable <String > options = Arrays .asList ("-classpath" , classPath );
147+ StandardJavaFileManager fileManager = compiler .getStandardFileManager (diagnostics , null , null );
148+
149+ Iterable <? extends JavaFileObject > compilationUnit = fileManager .getJavaFileObjectsFromFiles (sourceFiles );
150+ JavaCompiler .CompilationTask task = compiler .getTask (null , fileManager , diagnostics , options , null , compilationUnit );
151+
152+ boolean result = task .call ();
153+ try {
154+ if (result ) {
155+ this .searchClassInDirectory (classFiles , workingDirectory );
156+ this .classDynamicLoader = new ClassDynamicLoader (this .parentClassLoader );
157+
158+ for (File classFile : classFiles ) {
159+ String path = classFile .getAbsolutePath ().replace (workingDirectory .getAbsolutePath (), "" );
160+ String className = path .substring (1 , path .lastIndexOf ("." )).replace (File .separator , "." );
161+
162+ this .classDynamicLoader .addClassFile (className , Files .readAllBytes (classFile .toPath ()));
163+ }
164+
165+ return true ;
166+ } else {
167+ this .compileMessages .clear ();
168+ for (Diagnostic <?> diagnostic : diagnostics .getDiagnostics ()) {
169+ MessageType type = null ;
170+
171+ switch (diagnostic .getKind ()) {
172+ case ERROR :
173+ type = MessageType .ERROR ;
174+ break ;
175+ case MANDATORY_WARNING :
176+ type = MessageType .WARNING ;
177+ break ;
178+ case NOTE :
179+ type = MessageType .NOTE ;
180+ break ;
181+ case OTHER :
182+ type = MessageType .NOTE ;
183+ break ;
184+ case WARNING :
185+ type = MessageType .WARNING ;
186+ break ;
187+ }
188+
189+ this .compileMessages .add (new Message (type , diagnostic .getLineNumber (), diagnostic .getMessage (null )));
190+ }
191+
192+ return false ;
193+ }
194+
195+ } finally {
196+ for (File sourceFile : sourceFiles ) {
197+ sourceFile .delete ();
198+ }
199+ }
200+ }
201+
202+ public ArrayList <Message > getCompileMessages () {
203+ return compileMessages ;
204+ }
205+
206+ public void printAllCompileMessages () {
207+ for (Message message : compileMessages ) {
208+ System .out .println (message .toString ());
209+ }
210+ }
211+ }
0 commit comments