JAL-3210 changed classes for net.sf.j2s.core
authorBen Soares <bsoares@dundee.ac.uk>
Thu, 17 Oct 2019 10:36:30 +0000 (11:36 +0100)
committerBen Soares <bsoares@dundee.ac.uk>
Thu, 17 Oct 2019 10:36:30 +0000 (11:36 +0100)
utils/jalviewjs/Java2ScriptCompilationParticipant.java [new file with mode: 0644]
utils/jalviewjs/Java2ScriptCompiler.java [new file with mode: 0644]

diff --git a/utils/jalviewjs/Java2ScriptCompilationParticipant.java b/utils/jalviewjs/Java2ScriptCompilationParticipant.java
new file mode 100644 (file)
index 0000000..9c7aeb9
--- /dev/null
@@ -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.
+        * <p>
+        * Default is to return <code>false</code>.
+        * </p>
+        * <p>
+        * For efficiency, participants that are not interested in the given project
+        * should return <code>false</code> for that project.
+        * </p>
+        * 
+        * @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.
+        * <p>
+        * Default is to return <code>READY_FOR_BUILD</code>.
+        * </p>
+        * 
+        * @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.
+        * <p>
+        * Default is to return <code>false</code>.
+        * </p>
+        * 
+        * @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.
+        * <p>
+        * Note that a participant should not modify the buffer of the working copy
+        * that is being reconciled.
+        * </p>
+        * <p>
+        * Default is to do nothing.
+        * </p>
+        * 
+        * @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 (file)
index 0000000..ce31b52
--- /dev/null
@@ -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<String> copyResources = new HashSet<String>();
+       private Map<String, String> htMethodsCalled;
+       private List<String> 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<String> 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<String>();
+                               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<String, String>();
+                               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>();
+                       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<String> 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 "<!DOCTYPE html>\n" + 
+                               "<html>\n" + 
+                               "<head>\n" + 
+                               "<title>SwingJS test _NAME_</title><meta charset=\"utf-8\" />\n" + 
+                               "<script src=\"swingjs/swingjs2.js\"></script>\n" + 
+                               "<script>\n" + 
+                               "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" + 
+                               "Info = {\n" + 
+                               "  code: _CODE_,\n" + 
+                               "  main: _MAIN_,\n" + 
+                               "  core: \"NONE\",\n" + 
+                               "       width: 850,\n" + 
+                               "       height: 550,\n" + 
+                               "  readyFunction: null,\n" + 
+                               "       serverURL: 'https://chemapps.stolaf.edu/jmol/jsmol/php/jsmol.php',\n" + 
+                               "       j2sPath: 'swingjs/j2s',\n" + 
+                               "       console:'sysoutdiv',\n" + 
+                               "       allowjavascript: true\n" + 
+                               "}\n" + 
+                               "</script>\n" + 
+                               "</head>\n" + 
+                               "<body>\n" + 
+                               "<script>\n" + 
+                               "SwingJS.getApplet('testApplet', Info)\n" + 
+                               "getClassList = function(){J2S._saveFile('_j2sclasslist.txt', Clazz.ClassFilesLoaded.sort().join('\\n'))}\n" + 
+                               "</script>\n" + 
+                               "<div style=\"position:absolute;left:900px;top:30px;width:600px;height:300px;\">\n" + 
+                               "<div id=\"sysoutdiv\" contentEditable=\"true\" style=\"border:1px solid green;width:100%;height:95%;overflow:auto\"></div>\n" + 
+                               "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" + 
+                               "</div>\n" + 
+                               "</body>\n" + 
+                               "</html>\n";
+       }
+
+       /**
+        * Create a test HTML file for the applet or application. It will go into
+        * <project>/site.
+        * 
+        * @param appList
+        * @param siteFolder
+        * @param template
+        * @param isApplet
+        */
+       private void addHTML(ArrayList<String> 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();
+                       }
+       }
+
+}