1 package net.sf.j2s.core;
3 import java.io.BufferedReader;
5 import java.io.FileFilter;
6 import java.io.FileInputStream;
7 import java.io.FileNotFoundException;
8 import java.io.FileOutputStream;
9 import java.io.IOException;
10 import java.io.InputStreamReader;
11 import java.nio.file.Files;
12 import java.nio.file.StandardCopyOption;
13 import java.util.ArrayList;
14 import java.util.Date;
15 import java.util.Enumeration;
16 import java.util.HashSet;
17 import java.util.Hashtable;
18 import java.util.List;
20 import java.util.Properties;
22 import org.eclipse.core.resources.IFile;
23 import org.eclipse.core.resources.IProject;
24 import org.eclipse.core.runtime.IProduct;
25 import org.eclipse.jdt.core.IJavaProject;
26 import org.eclipse.jdt.core.JavaCore;
27 import org.eclipse.jdt.core.dom.AST;
28 import org.eclipse.jdt.core.dom.ASTParser;
29 import org.eclipse.jdt.core.dom.CompilationUnit;
32 * The main (and currently only operational) Java2Script compiler.
37 class Java2ScriptCompiler {
39 * The name of the J2S options file, aka as the "Dot-j2s" file.
41 private static final String J2S_OPTIONS_FILE_NAME = ".j2s";
43 // BH: added "true".equals(getProperty(props,
44 // "j2s.compiler.allow.compression")) to ensure compression only occurs when
46 private static final int JSL_LEVEL = AST.JLS8; // deprecation just because Java has moved on
47 private boolean showJ2SSettings = true;
49 // We copy all non .java files from any directory from which we loaded a
50 // java file into the site directory
51 private final HashSet<String> copyResources = new HashSet<String>();
52 private Map<String, String> htMethodsCalled;
53 private List<String> lstMethodsDeclared;
55 private Properties props;
56 private String htmlTemplate = null;
58 private String projectFolder;
60 private String j2sPath;
62 private String logDeclared;
64 private boolean logAllCalls;
66 private String logCalled;
68 private String excludedPaths;
70 private String siteFolder;
72 private boolean testing;
74 private List<String> lstExcludedPaths;
76 private boolean isCleanBuild;
78 boolean isCompilationParticipant;
80 private ASTParser astParser;
82 private IJavaProject project;
84 private boolean isDebugging;
86 static boolean isActive(IJavaProject project) {
88 return new File(project.getProject().getLocation().toOSString(), J2S_OPTIONS_FILE_NAME).exists();
89 } catch (@SuppressWarnings("unused") Exception e) {
94 Java2ScriptCompiler() {
95 // initialized only once using CompilationParticipant; every time using
100 * only for CompilationParticipant
103 void startBuild(boolean isClean) {
104 // at the beginning of a clean build, clear data
105 isCleanBuild = isClean;
108 copyResources.clear();
109 lstMethodsDeclared = null;
110 htMethodsCalled = null;
116 * from Java2ScriptCompilationParticipant.java
118 * get all necessary .j2s params for a build
121 * @param isCompilationParticipant
122 * @return true if this is a j2s project and is enabled
125 boolean initializeProject(IJavaProject project, boolean isCompilationParticipant) {
126 this.project = project;
127 this.isCompilationParticipant = isCompilationParticipant;
128 if (!isActive(project)) {
129 // the file .j2s does not exist in the project directory -- skip this project
132 projectFolder = project.getProject().getLocation().toOSString();
133 props = new Properties();
135 File j2sFile = new File(projectFolder, J2S_OPTIONS_FILE_NAME);
136 props.load(new FileInputStream(j2sFile));
137 String status = getProperty("j2s.compiler.status");
138 if (!"enable".equals(status) && !"enabled".equals(status)) {
139 if (getFileContents(j2sFile).trim().length() == 0) {
140 writeToFile(j2sFile, getDefaultJ2SFile());
146 } catch (FileNotFoundException e1) {
147 e1.printStackTrace();
148 } catch (IOException e1) {
149 e1.printStackTrace();
153 siteFolder = getProperty("j2s.site.directory");
154 if (siteFolder == null)
156 siteFolder = projectFolder + "/" + siteFolder;
157 j2sPath = siteFolder + "/swingjs/j2s";
160 System.out.println("Java2ScriptCompiler writing to " + j2sPath);
161 // method declarations and invocations are only logged
162 // when the designated files are deleted prior to building
164 logDeclared = (isCompilationParticipant && !isCleanBuild ? null : getProperty("j2s.log.methods.declared"));
165 if (logDeclared != null) {
166 if (!(file = new File(projectFolder, logDeclared)).exists()) {
167 lstMethodsDeclared = new ArrayList<String>();
168 System.err.println("logging methods declared to " + file);
170 logDeclared = projectFolder + "/" + logDeclared;
174 logCalled = (isCompilationParticipant && !isCleanBuild ? null : getProperty("j2s.log.methods.called"));
175 if (logCalled != null) {
176 if (!(file = new File(projectFolder, logCalled)).exists()) {
177 htMethodsCalled = new Hashtable<String, String>();
178 System.err.println("logging methods called to " + file);
180 logCalled = projectFolder + "/" + logCalled;
181 logAllCalls = "true".equals(getProperty("j2s.log.all.calls"));
184 excludedPaths = getProperty("j2s.excluded.paths");
186 lstExcludedPaths = null;
188 if (excludedPaths != null) {
189 lstExcludedPaths = new ArrayList<String>();
190 String[] paths = excludedPaths.split(";");
191 for (int i = 0; i < paths.length; i++)
192 if (paths[i].trim().length() > 0)
193 lstExcludedPaths.add(paths[i].trim() + "/");
194 if (lstExcludedPaths.size() == 0)
195 lstExcludedPaths = null;
198 testing = "true".equals(getProperty("j2s.testing"));
200 String prop = getProperty("j2s.compiler.nonqualified.packages");
201 // older version of the name
202 String nonqualifiedPackages = getProperty("j2s.compiler.nonqualified.classes");
203 nonqualifiedPackages = (prop == null ? "" : prop)
204 + (nonqualifiedPackages == null ? "" : (prop == null ? "" : ";") + nonqualifiedPackages);
205 if (nonqualifiedPackages.length() == 0)
206 nonqualifiedPackages = null;
207 // includes @j2sDebug blocks
208 isDebugging = "debug".equals(getProperty("j2s.compiler.mode"));
210 String classReplacements = getProperty("j2s.class.replacements");
212 String htmlTemplateFile = getProperty("j2s.template.html");
213 if (htmlTemplateFile == null)
214 htmlTemplateFile = "template.html";
216 if (htmlTemplate == null) {
217 file = new File(projectFolder, htmlTemplateFile);
218 if (!file.exists()) {
219 String html = getDefaultHTMLTemplate();
220 System.err.println("creating new htmltemplate\n" + html);
221 writeToFile(file, html);
223 htmlTemplate = getFileContents(file);
225 System.err.println("using HTML template " + file);
228 Java2ScriptVisitor.setDebugging(isDebugging);
229 Java2ScriptVisitor.setLogging(lstMethodsDeclared, htMethodsCalled, logAllCalls);
231 Java2ScriptVisitor.NameMapper.setNonQualifiedNamePackages(nonqualifiedPackages);
232 Java2ScriptVisitor.NameMapper.setClassReplacements(classReplacements);
234 astParser = ASTParser.newParser(JSL_LEVEL);
240 * from Java2ScriptCompilationParticipant.java
242 * process the source file into JavaScript using the JDT abstract syntax
243 * tree parser and visitor
247 boolean compileToJavaScript(IFile javaSource) {
248 String fileName = new String(javaSource.getFullPath().removeFirstSegments(1).toPortableString());
249 if (lstExcludedPaths != null) {
250 for (int i = lstExcludedPaths.size(); --i >= 0;)
251 if (fileName.startsWith(lstExcludedPaths.get(i))) {
255 org.eclipse.jdt.core.ICompilationUnit createdUnit = JavaCore.createCompilationUnitFrom(javaSource);
256 astParser.setSource(createdUnit);
257 // note: next call must come before each createAST call
258 astParser.setResolveBindings(true);
259 CompilationUnit root = (CompilationUnit) astParser.createAST(null);
260 // If the Java2ScriptVisitor is ever extended, it is important to set the project.
261 // Java2ScriptVisitor#addClassOrInterface uses getClass().newInstance().setproject(project).
262 Java2ScriptVisitor visitor = new Java2ScriptVisitor().setProject(project, testing);
266 // transpile the code
268 root.accept(visitor);
270 // generate the .js file(s) in the site directory
272 outputJavaScript(visitor, j2sPath);
274 logMethods(logCalled, logDeclared, logAllCalls);
276 // add the HTML files in the site directory
278 addHTML(visitor.getAppList(true), siteFolder, htmlTemplate, true);
279 addHTML(visitor.getAppList(false), siteFolder, htmlTemplate, false);
280 } catch (Throwable e) {
282 e.printStackTrace(System.out);
283 // find the file and delete it.
284 String filePath = j2sPath;
285 String rootName = root.getJavaElement().getElementName();
286 rootName = rootName.substring(0, rootName.lastIndexOf('.'));
287 String packageName = visitor.getMyPackageName();
288 if (packageName != null) {
289 File folder = new File(filePath, packageName.replace('.', File.separatorChar));
290 filePath = folder.getAbsolutePath();
291 File jsFile = new File(filePath, rootName + ".js"); //$NON-NLS-1$
292 if (jsFile.exists()) {
293 System.out.println("Java2ScriptCompiler deleting " + jsFile);
299 String packageName = visitor.getMyPackageName();
300 if (packageName != null) {
301 int pt = packageName.indexOf(".");
303 packageName = packageName.substring(0, pt);
304 if (!copyResources.contains(packageName)) {
305 copyResources.add(packageName);
306 File src = new File(projectFolder + "/src", packageName);
307 File dest = new File(j2sPath, packageName);
308 copySiteResources(src, dest);
314 //// private methods ////
317 private void logMethods(String logCalled, String logDeclared, boolean doAppend) {
318 if (htMethodsCalled != null)
320 File file = new File(logCalled);
321 file.createNewFile();
322 FileOutputStream fos = new FileOutputStream(file, doAppend);
323 for (String key : htMethodsCalled.keySet()) {
324 String val = htMethodsCalled.get(key);
325 fos.write(key.getBytes());
326 if (!val.equals("-")) {
328 fos.write(val.getBytes());
333 } catch (Exception e) {
334 System.err.println("Cannot log to " + logCalled + " " + e.getMessage());
336 if (lstMethodsDeclared != null)
338 File file = new File(logDeclared);
339 file.createNewFile();
340 FileOutputStream fos = new FileOutputStream(file, true);
341 for (int i = 0, n = lstMethodsDeclared.size(); i < n; i++) {
342 fos.write(lstMethodsDeclared.get(i).getBytes());
346 } catch (Exception e) {
347 System.err.println("Cannot log to " + logDeclared + " " + e.getMessage());
351 private String getProperty(String key) {
352 String val = props.getProperty(key);
354 System.err.println(key + " = " + val);
358 private void outputJavaScript(Java2ScriptVisitor visitor, String j2sPath) {
360 // fragments[0] is package]
361 List<String> elements = visitor.getElementList();
363 // BH all compression is deprecated --- use Google Closure Compiler
365 String packageName = visitor.getMyPackageName();
366 for (int i = 0; i < elements.size();) {
367 String elementName = elements.get(i++);
368 String element = elements.get(i++);
369 createJSFile(j2sPath, packageName, elementName, element);
371 showJ2SSettings = false; // just once per compilation run
374 private void createJSFile(String j2sPath, String packageName, String elementName, String js) {
375 if (packageName != null) {
376 File folder = new File(j2sPath, packageName.replace('.', File.separatorChar));
377 j2sPath = folder.getAbsolutePath();
378 if (!folder.exists() || !folder.isDirectory()) {
379 if (!folder.mkdirs()) {
380 throw new RuntimeException("Failed to create folder " + j2sPath); //$NON-NLS-1$
384 File f = new File(j2sPath, elementName + ".js");
386 System.out.println("Java2ScriptCompiler creating " + f);
390 private String getFileContents(File file) {
392 StringBuilder sb = new StringBuilder();
393 FileInputStream is = new FileInputStream(file);
394 BufferedReader reader = new BufferedReader(new InputStreamReader(is));
396 while ((line = reader.readLine()) != null) {
397 sb.append(line).append("\n");
400 return sb.toString();
401 } catch (@SuppressWarnings("unused") IOException e) {
407 private void writeToFile(File file, String data) {
411 FileOutputStream os = new FileOutputStream(file);
412 os.write(data.getBytes("UTF-8"));
414 } catch (IOException e) {
420 * The default .j2s file. Replaces .j2s only if it is found but is empty.
422 * OK, I know this should be a resource.
428 private String getDefaultJ2SFile() {
430 return "#j2s default configuration file created by net.sf.java2script_"
431 + CorePlugin.VERSION + " " + new Date() + "\n\n" +
432 "#enable the Java2Script transpiler -- comment out to disable\n" +
433 "j2s.compiler.status=enable\n" +
436 "# destination directory for all JavaScript\n" +
437 "j2s.site.directory=site\n" +
439 "# uncomment j2s.* lines to process:\n" +
441 "# a semicolon-separated list of package-level file paths to be excluded\n" +
442 "#j2s.excluded.paths=test;testng\n" +
444 "# output file name for logging methods declared - delete the file to regenerate a listing \n" +
445 "#j2s.log.methods.declared=methodsDeclared.csv\n" +
447 "#output file name for logging methods called - delete the file to regenerate a listing\n" +
448 "#j2s.log.methods.called=methodsCalled.csv\n" +
450 "#if set, every instance of methods called will be logged\n" +
451 "#otherwise, only the first call to a method will be logged \n" +
452 "#output will be comma-separated: called method,caller class \n" +
453 "#j2s.log.all.calls=true\n" +
455 "# a semicolon-separated list of packages that contain classes for which the method names\n" +
456 "# in their classes should not be \"qualified\" to indicate their parameter types. \n" +
457 "# This option is useful if you have an API interface in Java that refers to JavaScript \n" +
458 "# methods such as calling window or jQuery functions or the methods in Clazz or J2S. \n" +
459 "# The classes must not have any methods that are overloaded - with the\n" +
460 "# same name but different paramater type, as JavaScript will only see the last one.\n" +
461 "#j2s.compiler.nonqualified.packages=org.jmol.api.js;jspecview.api.js\n" +
463 "# uncomment to add debugging output. Start eclipse with the -consoleLog option to see output.\n" +
464 "#j2s.compiler.mode=debug\n" +
466 "# a semicolon-separated list of from->to listings of package (foo.) or class (foo.bar) \n" +
467 "# replacements to be made. This option allows for having one class or package used in Java\n" +
468 "# and another used in JavaScript. Take care with this. All methods in both packages must\n" +
469 "# have exactly the same parameter signature. We use it in Jalview to provide a minimal\n" +
470 "# JavaScript implementation of a large third-party library while still using that library's\n" +
471 "# jar file in Java.\n" +
472 "#j2s.class.replacements=org.apache.log4j.->jalview.javascript.log4j.\n" +
474 "# uncomment and change if you do not want to use the template.html file created for you\n" +
475 "# in your project directory. A default template file will be created by the transpiler \n" +
476 "# directory if there is none there already.\n" +
477 "#j2s.template.html=template.html\n";
480 * The default template file. The user can specify another in the .j2s file
481 * using template.html=.....
483 * @return default template with _NAME_, _CODE_, and _MAIN_ to fill in.
485 private String getDefaultHTMLTemplate() {
486 return "<!DOCTYPE html>\n" +
489 "<title>SwingJS test _NAME_</title><meta charset=\"utf-8\" />\n" +
490 "<script src=\"swingjs/swingjs2.js\"></script>\n" +
492 "if (!self.SwingJS)alert('swingjs2.js was not found. It needs to be in swingjs folder in the same directory as ' + document.location.href)\n" +
496 " core: \"NONE\",\n" +
499 " readyFunction: null,\n" +
500 " serverURL: 'https://chemapps.stolaf.edu/jmol/jsmol/php/jsmol.php',\n" +
501 " j2sPath: 'swingjs/j2s',\n" +
502 " console:'sysoutdiv',\n" +
503 " allowjavascript: true\n" +
509 "SwingJS.getApplet('testApplet', Info)\n" +
510 "getClassList = function(){J2S._saveFile('_j2sclasslist.txt', Clazz.ClassFilesLoaded.sort().join('\\n'))}\n" +
512 "<div style=\"position:absolute;left:900px;top:30px;width:600px;height:300px;\">\n" +
513 "<div id=\"sysoutdiv\" contentEditable=\"true\" style=\"border:1px solid green;width:100%;height:95%;overflow:auto\"></div>\n" +
514 "This is System.out. <a href=\"javascript:testApplet._clearConsole()\">clear it</a> <br>Add ?j2snocore to URL to see full class list; ?j2sdebug to use uncompressed j2s/core files <br><a href=\"javascript:getClassList()\">get _j2sClassList.txt</a>\n" +
521 * Create a test HTML file for the applet or application. It will go into
529 private void addHTML(ArrayList<String> appList, String siteFolder, String template, boolean isApplet) {
530 if (appList == null || template == null)
532 for (int i = appList.size(); --i >= 0;) {
533 String cl = appList.get(i);
534 String _NAME_ = cl.substring(cl.lastIndexOf(".") + 1);
535 String fname = cl.replaceAll("\\.", "_") + (isApplet ? "_applet" : "") + ".html";
536 cl = "\"" + cl + "\"";
537 String _MAIN_ = (isApplet ? "null" : cl);
538 String _CODE_ = (isApplet ? cl : "null");
539 template = template.replace("_NAME_", _NAME_).replace("_CODE_", _CODE_).replace("_MAIN_", _MAIN_);
540 System.err.println("Java2Script creating " + siteFolder + "/" + fname);
541 writeToFile(new File(siteFolder, fname), template);
545 private FileFilter filter = new FileFilter() {
548 public boolean accept(File pathname) {
549 return pathname.isDirectory() || !pathname.getName().endsWith(".java");
554 private void copySiteResources(File from, File dest) {
555 copyNonclassFiles(from, dest);
558 private void copyNonclassFiles(File dir, File target) {
559 if (dir.equals(target))
561 File[] files = dir.listFiles(filter);
565 if (!target.exists())
566 Files.createDirectories(target.toPath());
567 for (int i = 0; i < files.length; i++) {
571 } else if (f.isDirectory()) {
572 copyNonclassFiles(f, new File(target, f.getName()));
574 Files.copy(f.toPath(), new File(target, f.getName()).toPath(),
575 StandardCopyOption.REPLACE_EXISTING);
576 System.err.println("copied to site: " + f.toPath());
579 } catch (IOException e1) {
580 // TODO Auto-generated catch block
581 System.err.println("error copying " + f + " to " + target);
582 e1.printStackTrace();