From 41ba5838e9920dbc3609dbabe04df91f733c4f85 Mon Sep 17 00:00:00 2001 From: Ben Soares Date: Thu, 17 Oct 2019 11:36:30 +0100 Subject: [PATCH] JAL-3210 changed classes for net.sf.j2s.core --- .../Java2ScriptCompilationParticipant.java | 185 ++++++ utils/jalviewjs/Java2ScriptCompiler.java | 586 ++++++++++++++++++++ 2 files changed, 771 insertions(+) create mode 100644 utils/jalviewjs/Java2ScriptCompilationParticipant.java create mode 100644 utils/jalviewjs/Java2ScriptCompiler.java diff --git a/utils/jalviewjs/Java2ScriptCompilationParticipant.java b/utils/jalviewjs/Java2ScriptCompilationParticipant.java new file mode 100644 index 0000000..9c7aeb9 --- /dev/null +++ b/utils/jalviewjs/Java2ScriptCompilationParticipant.java @@ -0,0 +1,185 @@ +package net.sf.j2s.core; + +import java.util.Arrays; +import java.util.Properties; + +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.compiler.BuildContext; +import org.eclipse.jdt.core.compiler.ReconcileContext; + +/** + * New Java2Script compiler uses org.eclipse.jdt.core.compiler.CompilationParticipant instead of builder + * + * source: https://github.com/eclipse/org.aspectj.shadows/blob/master/org.eclipse.jdt.core/model/org/eclipse/jdt/core/compiler/CompilationParticipant.java + * + * @author hansonr + * + */ +public class Java2ScriptCompilationParticipant extends org.eclipse.jdt.core.compiler.CompilationParticipant { + private BuildContext[] javaFiles; + private boolean isCleanBuild; + + public Java2ScriptCompilationParticipant() { + System.out.println("CompilationParticipant started"); + } + + /** + * Returns whether this participant is active for a given project. + *

+ * Default is to return false. + *

+ *

+ * For efficiency, participants that are not interested in the given project + * should return false for that project. + *

+ * + * @param project + * the project to participate in + * @return whether this participant is active for a given project + */ + public boolean isActive(IJavaProject project) { + boolean isj2s = Java2ScriptCompiler.isActive(project); + System.out.println("isActive " + isj2s + " " + project.getProject().getLocation()); + return isj2s; + } + + /** + * Notifies this participant that a build is about to start and provides it + * the opportunity to create missing source folders for generated source + * files. Additional source folders should be marked as optional so the + * project can be built when the folders do not exist. Only sent to + * participants interested in the project. + *

+ * Default is to return READY_FOR_BUILD. + *

+ * + * @see #buildFinished(IJavaProject project) + * @param project + * the project about to build + * @return READY_FOR_BUILD or NEEDS_FULL_BUILD + */ + public int aboutToBuild(IJavaProject project) { + System.out.println("aboutToBuild " + project.getProject().getLocation()); + return READY_FOR_BUILD; + } + + /** + * Notifies this participant that a clean is about to start and provides it + * the opportunity to delete generated source files. Only sent to + * participants interested in the project. + * + * @param project + * the project about to be cleaned + */ + public void cleanStarting(IJavaProject project) { + System.out.println("cleanStarting " + project.getProject().getLocation()); + isCleanBuild = true; + } + + /** + * Notifies this participant that a compile operation is about to start and + * provides it the opportunity to generate source files based on the source + * files about to be compiled. When isBatchBuild is true, then files + * contains all source files in the project. Only sent to participants + * interested in the current build project. + * + * @param files + * is an array of BuildContext + * @param isBatch + * identifies when the build is a batch build + */ + public void buildStarting(BuildContext[] files, boolean isBatch) { + if (javaFiles != null) { + BuildContext[] concat = Arrays.copyOf(javaFiles, javaFiles.length + files.length); + System.arraycopy(files, 0, concat , javaFiles.length, files.length); + javaFiles = concat; + } else { + javaFiles = files; + } + System.out.println("buildStarting " + files.length + " files, isBatch=" + isBatch); + } + + /** + * Notifies this participant that a build has finished for the project. This + * will be sent, even if buildStarting() was not sent when no source files + * needed to be compiled or the build failed. Only sent to participants + * interested in the project. + * + * @param project + * the project about to build + * @since 3.4 + */ + public void buildFinished(IJavaProject project) { + if (javaFiles != null) { + Java2ScriptCompiler j2sCompiler = new Java2ScriptCompiler(); + j2sCompiler.startBuild(isCleanBuild); + if (!j2sCompiler.initializeProject(project, true)) { + System.out.println(".j2s disabled"); + return; + } + System.out.println("building JavaScript " + project.getProject().getLocation()); + for (int i = 0; i < javaFiles.length; i++) { + System.out.println("[" + (i+1) + "/" + javaFiles.length + "] transpiling " + javaFiles[i]); +// trying to keep the progess monitor running - didn't work +// try { +// Thread.currentThread().sleep(1); +// } catch (InterruptedException e) { +// // ignore +// } + if (!j2sCompiler.compileToJavaScript(javaFiles[i].getFile())) { + System.out.println("Error processing " + javaFiles[i].getFile()); + break; + } + } + javaFiles = null; + System.out.println("build finished " + project.getProject().getLocation()); + } + isCleanBuild = false; + } + + /** + * Returns whether this participant is interested in only Annotations. + *

+ * Default is to return false. + *

+ * + * @return whether this participant is interested in only Annotations. + */ + public boolean isAnnotationProcessor() { + return false; + } + + /** + * Notifies this participant that a compile operation has found source files + * using Annotations. Only sent to participants interested in the current + * build project that answer true to isAnnotationProcessor(). Each + * BuildContext was informed whether its source file currently + * hasAnnotations(). + * + * @param files + * is an array of BuildContext + */ + public void processAnnotations(BuildContext[] files) { + // nothing to do + } + + /** + * Notifies this participant that a reconcile operation is happening. The + * participant can act on this reconcile operation by using the given + * context. Other participant can then see the result of this participation + * on this context. + *

+ * Note that a participant should not modify the buffer of the working copy + * that is being reconciled. + *

+ *

+ * Default is to do nothing. + *

+ * + * @param context + * the reconcile context to act on + */ + public void reconcile(ReconcileContext context) { + // fired whenever a source file is changed -- before it is saved + } +} \ No newline at end of file diff --git a/utils/jalviewjs/Java2ScriptCompiler.java b/utils/jalviewjs/Java2ScriptCompiler.java new file mode 100644 index 0000000..ce31b52 --- /dev/null +++ b/utils/jalviewjs/Java2ScriptCompiler.java @@ -0,0 +1,586 @@ +package net.sf.j2s.core; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IProduct; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.CompilationUnit; + +/** + * The main (and currently only operational) Java2Script compiler. + * + * @author Bob Hanson + * + */ +class Java2ScriptCompiler { + /** + * The name of the J2S options file, aka as the "Dot-j2s" file. + */ + private static final String J2S_OPTIONS_FILE_NAME = ".j2s"; + + // BH: added "true".equals(getProperty(props, + // "j2s.compiler.allow.compression")) to ensure compression only occurs when + // desired + private static final int JSL_LEVEL = AST.JLS8; // deprecation just because Java has moved on + private boolean showJ2SSettings = true; + + // We copy all non .java files from any directory from which we loaded a + // java file into the site directory + private final HashSet copyResources = new HashSet(); + private Map htMethodsCalled; + private List lstMethodsDeclared; + + private Properties props; + private String htmlTemplate = null; + + private String projectFolder; + + private String j2sPath; + + private String logDeclared; + + private boolean logAllCalls; + + private String logCalled; + + private String excludedPaths; + + private String siteFolder; + + private boolean testing; + + private List lstExcludedPaths; + + private boolean isCleanBuild; + + boolean isCompilationParticipant; + + private ASTParser astParser; + + private IJavaProject project; + + private boolean isDebugging; + + static boolean isActive(IJavaProject project) { + try { + return new File(project.getProject().getLocation().toOSString(), J2S_OPTIONS_FILE_NAME).exists(); + } catch (@SuppressWarnings("unused") Exception e) { + return false; + } + } + + Java2ScriptCompiler() { + // initialized only once using CompilationParticipant; every time using + // older Builder idea + } + + /** + * only for CompilationParticipant + * @param isClean + */ + void startBuild(boolean isClean) { + // at the beginning of a clean build, clear data + isCleanBuild = isClean; + htmlTemplate = null; + if (isClean) { + copyResources.clear(); + lstMethodsDeclared = null; + htMethodsCalled = null; + } + + } + + /** + * from Java2ScriptCompilationParticipant.java + * + * get all necessary .j2s params for a build + * + * @param project + * @param isCompilationParticipant + * @return true if this is a j2s project and is enabled + * + */ + boolean initializeProject(IJavaProject project, boolean isCompilationParticipant) { + this.project = project; + this.isCompilationParticipant = isCompilationParticipant; + if (!isActive(project)) { + // the file .j2s does not exist in the project directory -- skip this project + return false; + } + projectFolder = project.getProject().getLocation().toOSString(); + props = new Properties(); + try { + File j2sFile = new File(projectFolder, J2S_OPTIONS_FILE_NAME); + props.load(new FileInputStream(j2sFile)); + String status = getProperty("j2s.compiler.status"); + if (!"enable".equals(status) && !"enabled".equals(status)) { + if (getFileContents(j2sFile).trim().length() == 0) { + writeToFile(j2sFile, getDefaultJ2SFile()); + } else { + // not enabled + return false; + } + } + } catch (FileNotFoundException e1) { + e1.printStackTrace(); + } catch (IOException e1) { + e1.printStackTrace(); + } + + File file; + siteFolder = getProperty("j2s.site.directory"); + if (siteFolder == null) + siteFolder = "site"; + siteFolder = projectFolder + "/" + siteFolder; + j2sPath = siteFolder + "/swingjs/j2s"; + + if (isDebugging) + System.out.println("Java2ScriptCompiler writing to " + j2sPath); + // method declarations and invocations are only logged + // when the designated files are deleted prior to building + + logDeclared = (isCompilationParticipant && !isCleanBuild ? null : getProperty("j2s.log.methods.declared")); + if (logDeclared != null) { + if (!(file = new File(projectFolder, logDeclared)).exists()) { + lstMethodsDeclared = new ArrayList(); + System.err.println("logging methods declared to " + file); + } + logDeclared = projectFolder + "/" + logDeclared; + } + logAllCalls = false; + + logCalled = (isCompilationParticipant && !isCleanBuild ? null : getProperty("j2s.log.methods.called")); + if (logCalled != null) { + if (!(file = new File(projectFolder, logCalled)).exists()) { + htMethodsCalled = new Hashtable(); + System.err.println("logging methods called to " + file); + } + logCalled = projectFolder + "/" + logCalled; + logAllCalls = "true".equals(getProperty("j2s.log.all.calls")); + } + + excludedPaths = getProperty("j2s.excluded.paths"); + + lstExcludedPaths = null; + + if (excludedPaths != null) { + lstExcludedPaths = new ArrayList(); + String[] paths = excludedPaths.split(";"); + for (int i = 0; i < paths.length; i++) + if (paths[i].trim().length() > 0) + lstExcludedPaths.add(paths[i].trim() + "/"); + if (lstExcludedPaths.size() == 0) + lstExcludedPaths = null; + } + + testing = "true".equals(getProperty("j2s.testing")); + + String prop = getProperty("j2s.compiler.nonqualified.packages"); + // older version of the name + String nonqualifiedPackages = getProperty("j2s.compiler.nonqualified.classes"); + nonqualifiedPackages = (prop == null ? "" : prop) + + (nonqualifiedPackages == null ? "" : (prop == null ? "" : ";") + nonqualifiedPackages); + if (nonqualifiedPackages.length() == 0) + nonqualifiedPackages = null; + // includes @j2sDebug blocks + isDebugging = "debug".equals(getProperty("j2s.compiler.mode")); + + String classReplacements = getProperty("j2s.class.replacements"); + + String htmlTemplateFile = getProperty("j2s.template.html"); + if (htmlTemplateFile == null) + htmlTemplateFile = "template.html"; + + if (htmlTemplate == null) { + file = new File(projectFolder, htmlTemplateFile); + if (!file.exists()) { + String html = getDefaultHTMLTemplate(); + System.err.println("creating new htmltemplate\n" + html); + writeToFile(file, html); + } + htmlTemplate = getFileContents(file); + if (showJ2SSettings) + System.err.println("using HTML template " + file); + } + + Java2ScriptVisitor.setDebugging(isDebugging); + Java2ScriptVisitor.setLogging(lstMethodsDeclared, htMethodsCalled, logAllCalls); + + Java2ScriptVisitor.NameMapper.setNonQualifiedNamePackages(nonqualifiedPackages); + Java2ScriptVisitor.NameMapper.setClassReplacements(classReplacements); + + astParser = ASTParser.newParser(JSL_LEVEL); + + return true; + } + + /** + * from Java2ScriptCompilationParticipant.java + * + * process the source file into JavaScript using the JDT abstract syntax + * tree parser and visitor + * + * @param javaSource + */ + boolean compileToJavaScript(IFile javaSource) { + String fileName = new String(javaSource.getFullPath().removeFirstSegments(1).toPortableString()); + if (lstExcludedPaths != null) { + for (int i = lstExcludedPaths.size(); --i >= 0;) + if (fileName.startsWith(lstExcludedPaths.get(i))) { + return true; + } + } + org.eclipse.jdt.core.ICompilationUnit createdUnit = JavaCore.createCompilationUnitFrom(javaSource); + astParser.setSource(createdUnit); + // note: next call must come before each createAST call + astParser.setResolveBindings(true); + CompilationUnit root = (CompilationUnit) astParser.createAST(null); + // If the Java2ScriptVisitor is ever extended, it is important to set the project. + // Java2ScriptVisitor#addClassOrInterface uses getClass().newInstance().setproject(project). + Java2ScriptVisitor visitor = new Java2ScriptVisitor().setProject(project, testing); + + try { + + // transpile the code + + root.accept(visitor); + + // generate the .js file(s) in the site directory + + outputJavaScript(visitor, j2sPath); + + logMethods(logCalled, logDeclared, logAllCalls); + + // add the HTML files in the site directory + + addHTML(visitor.getAppList(true), siteFolder, htmlTemplate, true); + addHTML(visitor.getAppList(false), siteFolder, htmlTemplate, false); + } catch (Throwable e) { + e.printStackTrace(); + e.printStackTrace(System.out); + // find the file and delete it. + String filePath = j2sPath; + String rootName = root.getJavaElement().getElementName(); + rootName = rootName.substring(0, rootName.lastIndexOf('.')); + String packageName = visitor.getMyPackageName(); + if (packageName != null) { + File folder = new File(filePath, packageName.replace('.', File.separatorChar)); + filePath = folder.getAbsolutePath(); + File jsFile = new File(filePath, rootName + ".js"); //$NON-NLS-1$ + if (jsFile.exists()) { + System.out.println("Java2ScriptCompiler deleting " + jsFile); + jsFile.delete(); + } + } + return false; + } + String packageName = visitor.getMyPackageName(); + if (packageName != null) { + int pt = packageName.indexOf("."); + if (pt >= 0) + packageName = packageName.substring(0, pt); + if (!copyResources.contains(packageName)) { + copyResources.add(packageName); + File src = new File(projectFolder + "/src", packageName); + File dest = new File(j2sPath, packageName); + copySiteResources(src, dest); + } + } + return true; + } + + //// private methods //// + + + private void logMethods(String logCalled, String logDeclared, boolean doAppend) { + if (htMethodsCalled != null) + try { + File file = new File(logCalled); + file.createNewFile(); + FileOutputStream fos = new FileOutputStream(file, doAppend); + for (String key : htMethodsCalled.keySet()) { + String val = htMethodsCalled.get(key); + fos.write(key.getBytes()); + if (!val.equals("-")) { + fos.write(','); + fos.write(val.getBytes()); + } + fos.write('\n'); + } + fos.close(); + } catch (Exception e) { + System.err.println("Cannot log to " + logCalled + " " + e.getMessage()); + } + if (lstMethodsDeclared != null) + try { + File file = new File(logDeclared); + file.createNewFile(); + FileOutputStream fos = new FileOutputStream(file, true); + for (int i = 0, n = lstMethodsDeclared.size(); i < n; i++) { + fos.write(lstMethodsDeclared.get(i).getBytes()); + fos.write('\n'); + } + fos.close(); + } catch (Exception e) { + System.err.println("Cannot log to " + logDeclared + " " + e.getMessage()); + } + } + + private String getProperty(String key) { + String val = props.getProperty(key); + if (showJ2SSettings) + System.err.println(key + " = " + val); + return val; + } + + private void outputJavaScript(Java2ScriptVisitor visitor, String j2sPath) { + + // fragments[0] is package] + List elements = visitor.getElementList(); + + // BH all compression is deprecated --- use Google Closure Compiler + + String packageName = visitor.getMyPackageName(); + for (int i = 0; i < elements.size();) { + String elementName = elements.get(i++); + String element = elements.get(i++); + createJSFile(j2sPath, packageName, elementName, element); + } + showJ2SSettings = false; // just once per compilation run + } + + private void createJSFile(String j2sPath, String packageName, String elementName, String js) { + if (packageName != null) { + File folder = new File(j2sPath, packageName.replace('.', File.separatorChar)); + j2sPath = folder.getAbsolutePath(); + if (!folder.exists() || !folder.isDirectory()) { + if (!folder.mkdirs()) { + throw new RuntimeException("Failed to create folder " + j2sPath); //$NON-NLS-1$ + } + } + } + File f = new File(j2sPath, elementName + ".js"); + if (isDebugging) + System.out.println("Java2ScriptCompiler creating " + f); + writeToFile(f, js); + } + + private String getFileContents(File file) { + try { + StringBuilder sb = new StringBuilder(); + FileInputStream is = new FileInputStream(file); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + String line = null; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + reader.close(); + return sb.toString(); + } catch (@SuppressWarnings("unused") IOException e) { + // + } + return null; + } + + private void writeToFile(File file, String data) { + if (data == null) + return; + try { + FileOutputStream os = new FileOutputStream(file); + os.write(data.getBytes("UTF-8")); + os.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * The default .j2s file. Replaces .j2s only if it is found but is empty. + * + * OK, I know this should be a resource. + * + * + * + */ + + private String getDefaultJ2SFile() { + + return "#j2s default configuration file created by net.sf.java2script_" + + CorePlugin.VERSION + " " + new Date() + "\n\n" + + "#enable the Java2Script transpiler -- comment out to disable\n" + + "j2s.compiler.status=enable\n" + + "\n" + + "\n" + + "# destination directory for all JavaScript\n" + + "j2s.site.directory=site\n" + + "\n" + + "# uncomment j2s.* lines to process:\n" + + "\n" + + "# a semicolon-separated list of package-level file paths to be excluded\n" + + "#j2s.excluded.paths=test;testng\n" + + "\n" + + "# output file name for logging methods declared - delete the file to regenerate a listing \n" + + "#j2s.log.methods.declared=methodsDeclared.csv\n" + + "\n" + + "#output file name for logging methods called - delete the file to regenerate a listing\n" + + "#j2s.log.methods.called=methodsCalled.csv\n" + + "\n" + + "#if set, every instance of methods called will be logged\n" + + "#otherwise, only the first call to a method will be logged \n" + + "#output will be comma-separated: called method,caller class \n" + + "#j2s.log.all.calls=true\n" + + "\n" + + "# a semicolon-separated list of packages that contain classes for which the method names\n" + + "# in their classes should not be \"qualified\" to indicate their parameter types. \n" + + "# This option is useful if you have an API interface in Java that refers to JavaScript \n" + + "# methods such as calling window or jQuery functions or the methods in Clazz or J2S. \n" + + "# The classes must not have any methods that are overloaded - with the\n" + + "# same name but different paramater type, as JavaScript will only see the last one.\n" + + "#j2s.compiler.nonqualified.packages=org.jmol.api.js;jspecview.api.js\n" + + "\n" + + "# uncomment to add debugging output. Start eclipse with the -consoleLog option to see output.\n" + + "#j2s.compiler.mode=debug\n" + + "\n" + + "# a semicolon-separated list of from->to listings of package (foo.) or class (foo.bar) \n" + + "# replacements to be made. This option allows for having one class or package used in Java\n" + + "# and another used in JavaScript. Take care with this. All methods in both packages must\n" + + "# have exactly the same parameter signature. We use it in Jalview to provide a minimal\n" + + "# JavaScript implementation of a large third-party library while still using that library's\n" + + "# jar file in Java.\n" + + "#j2s.class.replacements=org.apache.log4j.->jalview.javascript.log4j.\n" + + "\n" + + "# uncomment and change if you do not want to use the template.html file created for you\n" + + "# in your project directory. A default template file will be created by the transpiler \n" + + "# directory if there is none there already.\n" + + "#j2s.template.html=template.html\n"; + } + /** + * The default template file. The user can specify another in the .j2s file + * using template.html=..... + * + * @return default template with _NAME_, _CODE_, and _MAIN_ to fill in. + */ + private String getDefaultHTMLTemplate() { + return "\n" + + "\n" + + "\n" + + "SwingJS test _NAME_\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "
\n" + + "
\n" + + "This is System.out. clear it
Add ?j2snocore to URL to see full class list; ?j2sdebug to use uncompressed j2s/core files
get _j2sClassList.txt\n" + + "
\n" + + "\n" + + "\n"; + } + + /** + * Create a test HTML file for the applet or application. It will go into + * /site. + * + * @param appList + * @param siteFolder + * @param template + * @param isApplet + */ + private void addHTML(ArrayList appList, String siteFolder, String template, boolean isApplet) { + if (appList == null || template == null) + return; + for (int i = appList.size(); --i >= 0;) { + String cl = appList.get(i); + String _NAME_ = cl.substring(cl.lastIndexOf(".") + 1); + String fname = cl.replaceAll("\\.", "_") + (isApplet ? "_applet" : "") + ".html"; + cl = "\"" + cl + "\""; + String _MAIN_ = (isApplet ? "null" : cl); + String _CODE_ = (isApplet ? cl : "null"); + template = template.replace("_NAME_", _NAME_).replace("_CODE_", _CODE_).replace("_MAIN_", _MAIN_); + System.err.println("Java2Script creating " + siteFolder + "/" + fname); + writeToFile(new File(siteFolder, fname), template); + } + } + + private FileFilter filter = new FileFilter() { + + @Override + public boolean accept(File pathname) { + return pathname.isDirectory() || !pathname.getName().endsWith(".java"); + } + + }; + + private void copySiteResources(File from, File dest) { + copyNonclassFiles(from, dest); + } + + private void copyNonclassFiles(File dir, File target) { + if (dir.equals(target)) + return; + File[] files = dir.listFiles(filter); + File f = null; + if (files != null) + try { + if (!target.exists()) + Files.createDirectories(target.toPath()); + for (int i = 0; i < files.length; i++) { + f = files[i]; + if (f == null) { + // + } else if (f.isDirectory()) { + copyNonclassFiles(f, new File(target, f.getName())); + } else { + Files.copy(f.toPath(), new File(target, f.getName()).toPath(), + StandardCopyOption.REPLACE_EXISTING); + System.err.println("copied to site: " + f.toPath()); + } + } + } catch (IOException e1) { + // TODO Auto-generated catch block + System.err.println("error copying " + f + " to " + target); + e1.printStackTrace(); + } + } + +} -- 1.7.10.2