package javajs.async; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.stream.Collectors; import javax.imageio.ImageIO; import javax.swing.Timer; /** * A set of generally useful SwingJS-related methods. Includes: * * alternatives to using getCodeBase() for loading resources, due to issues in * Eclipse setting that incorrectly (but no problem in JavaScript) * * * * @author hansonr * */ public class SwingJSUtils { /** * Set the dimension for the applet prior to j2sApplet's call to * run the applet. Must be used to create a static field: * * * private static Dimension dim = * * * * Then, if it is desired also to have Java also set this, add * * if (dim != null) setSize(dim); * * to the applet's init() method. * * @param w * @param h * @return the Dimension * * @author hansonr */ public static Dimension setDim(int w, int h) { String baseURI = (/** @j2sNative document.body.baseURI || */ null); boolean isTest = (baseURI == null || baseURI.indexOf("_applet.html") >= 0); if (!isTest) return null; /** * @j2sNative * * J2S.thisApplet.__Info.width = w; J2S.thisApplet.__Info.height = h; */ return new Dimension(w, h); } /** * Reliably load a resource of a specific type from the code directory * * adaptable - here we are returning an image or a string * * @param cl the classname of the object to return (Image.class, * String.class) null for InputStream * @param filename * @return * * @author hansonr */ public static Object getResource(Class baseClass, String filename, Class cl) { System.out.println("mpUtils.SwingJSUtils.getResource " + baseClass.getCanonicalName() + " " + filename); InputStream is = baseClass.getResourceAsStream(filename); if (cl == Image.class) { try { return ImageIO.read(is); } catch (IOException e) { e.printStackTrace(); } } else if (cl == String.class) { return new BufferedReader(new InputStreamReader(is)).lines().collect(Collectors.joining("\n")); } return is; } /** * Pre-fetch images during the static entry of the class. This should provide * plenty of clock ticks, since the file transfer is synchronous, and all we are * waiting for is the DOM image object to initialize. * * @param cl * @param images * @param root * @param nImages * @param ext */ public static void loadImagesStatic(Class cl, Image[] images, String root, String ext, int nImages) { for (int i = nImages; --i >= 0;) { // Bild laden und beim MediaTracker registrieren // MediaTracker ladekontrolle = new MediaTracker(this); // BH SwingJS -- adding generally useful method for loading data // avoiding the use of getCodeBase(), which for some reason does not work in // Eclipse. images[i] = (Image) getResource(cl, root + i + "." + ext, Image.class); // /** // * @j2sNative // * $("body").append(images[i]._imgNode); // * // */ // ladekontrolle.addImage(scharf[i],i); // Warten , bis Bild ganz geladen ist // try {ladekontrolle.waitForID(i);} // catch (InterruptedException e) // {} } } /** * Fill an array with images based on a String[] listing * @param cl reference class * @param root optional root path, ending in "/" * @param names source file names * @param images array to fill */ public static void loadImagesStatic(Class cl, String root, String[] names, Image[] images) { for (int i = names.length; --i >= 0;) { images[i] = (Image) getResource(cl, root + names[i], Image.class); } } /** * Eclipse-friendly image getting * * @param c * @param fileName * @return */ public static Image getImage(Component c, String fileName) { return getImage(c.getClass(), fileName); } /** * Eclipse-friendly image getting * * @param c * @param fileName * @return */ public static Image getImage(Class c, String fileName) { return (Image) getResource(c, fileName, Image.class); } /** * Clear the component graphic. BH added this for JavaScript because changing * the browser zoom can change the size of the canvas for unknown reasons. * * @param c */ public static void clearComponent(Component c) { Graphics gc = c.getGraphics(); gc.clearRect(0, 0, c.getWidth(), c.getHeight()); gc.dispose(); } /** * A simple interface to the machine loop, generally of the form * * public boolean stateLoop() { * while (stateHepler.isAlive()) { * switch (stateHelper.getState()) { * case STATE_XXX: * ... * return stateHelper.delayState(100,STATE_YYY); * case STATE_YYY: * ... * stateHelper.setState(STATE_ZZZ); * continue; * case STATE_ZZZ: * ... * return stateHelper.delayAction(100, MY_ID, "myCommand", myListener, STATE_XXX); * * case STATE_DONE: * ... * stateHelper.interrupt(); * return false; * } * return true; * } * return false; * } * * @author hansonr * */ public interface StateMachine { public boolean stateLoop(); } /** * StateHelper is a class that facilitates moving from an asychronous multithreaded model to a state-oriented model of programming * for SwingJS animations and other asynchronous business. * * @author hansonr * */ public static class StateHelper { public static final int UNCHANGED = Integer.MIN_VALUE; private StateMachine machine; private int state; private int level; private boolean interrupted; public StateHelper(StateMachine machine) { this.machine = machine; } public void interrupt() { interrupted = true; } public boolean isInterrupted() { return interrupted; } public boolean isAlive() { return !interrupted; } public void restart() { interrupted = false; } public void setState(int state) { this.state = this.stateNext = state; } public int getState() { return state; } public void setLevel(int level) { this.level = this.levelNext = level; } public int getLevel() { return level; } public void setNextState(int next) { stateNext = next; } public int getNextState() { return stateNext; } public int getNextLevel() { return levelNext; } public void setNextLevel(int next) { levelNext = next; } /** * * NOTE: this method must remain private; it is accessed via p$1 * * @return */ private boolean nextState() { return next(stateNext, levelNext); } /** * Set the state and run machine.stateLoop(). * * @param state something meaningful to the machine * * @return not interrupted * * @author Bob Hanson hansonr@stolaf.edu */ public boolean next(int state) { return next(state, 0); } /** * Set the state and level, and then run machine.stateLoop(). Driven directly or via delayedState or delayedAction * * @param state something meaningful to the machine * @param level something meaningful to the machine * * @return not interrupted * * @author Bob Hanson hansonr@stolaf.edu */ public boolean next(int state, int level) { return nextStatePriv(this, state, level); } private static boolean nextStatePriv(Object oThis, int state, int level) { StateHelper me = (StateHelper) oThis; if (me.interrupted) return false; if (level != UNCHANGED) me.level = level; if (state != UNCHANGED) me.state = state; return me.machine.stateLoop(); } /** * After the given number of milliseseconds, set the new state and run the machines stateLoop with unchanged level * * @param ms the number of milliseconds to delay; 0 to execute synchronously * * @param stateNext the next state to run * @return not interrupted * * @author Bob Hanson hansonr@stolaf.edu */ public boolean delayedState(int ms, int stateNext) { return delayedState(ms, stateNext, level); } private Timer stateTimer; private int stateNext; private int levelNext; /** * After the given number of milliseseconds, set the new state and level, and * run the machines stateLoop * * @param ms the number of milliseconds to delay; 0 to execute * synchronously * * @param stateNext the next state * @param levelNext the next level * @return not interrupted * * @author Bob Hanson hansonr@stolaf.edu */ public boolean delayedState(int ms, int stateNext, int levelNext) { if (interrupted) return false; if (ms == 0) return next(stateNext, levelNext); if (stateNext != UNCHANGED) this.stateNext = stateNext; if (levelNext != UNCHANGED) this.levelNext = levelNext; /** * @j2sNative * var me = this; * setTimeout(function(){ * p$1.nextState.apply(me, []); * },ms); */ { // Java only if (stateTimer == null) { stateTimer = new Timer(ms, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { nextState(); } }); stateTimer.setRepeats(false); stateTimer.start(); } else { stateTimer.restart(); } } return true; } /** * Fire an actionPerformed event after a given number of milliseconds * * @param ms delay milliseconds. if 0, then this action will be called * synchronously * @param id id for this event, possibly ACTION_PERFORMED (1001), but not * necessarily * @param command key for ActionEvent.getCommand() * @param listener ActionListener to be called. * @return not interrupted * * @author Bob Hanson hansonr@stolaf.edu */ public boolean delayedAction(int ms, int id, String command, ActionListener listener) { return delayedAction(ms, id, command, listener, UNCHANGED, UNCHANGED); } /** * Fire an actionPerformed event after a given number of milliseconds * * @param ms delay milliseconds. if 0, then this action will be called * synchronously * @param id id for this event, possibly ACTION_PERFORMED (1001), but not * necessarily * @param command key for ActionEvent.getCommand() * @param listener ActionListener to be called. * * @param state the next state to go to after this listener is called; UNCHANGED to let the listener take care of this. * * @return not interrupted * * @author Bob Hanson hansonr@stolaf.edu */ public boolean delayedAction(int ms, int id, String command, ActionListener listener, int state) { return delayedAction(ms, id, command, listener, state, UNCHANGED); } /** * Fire an actionPerformed event after a given number of milliseconds. Setting BOTH stateNext and levelNext to UNCHANGED (Integer.MIN_VALUE) * allows the listener to handle continuing the loop. * * @param ms delay milliseconds. if 0, then this action will be called * synchronously * @param id id for this event, possibly ACTION_PERFORMED (1001), but not * necessarily * @param command key for ActionEvent.getCommand() * @param listener ActionListener to be called. * @param stateNext state to run after the event is processed by the listener, or UNCHANGED (Integer.MIN_VALUE) to allow listener to handle this. * @param levelNext level to run after the event is processed by the listener, or UNCHANGED (Integer.MIN_VALUE) to allow listener to handle this. * @return not interrupted * * @author Bob Hanson hansonr@stolaf.edu */ public boolean delayedAction(int ms, int id, String command, ActionListener listener, int stateNext, int levelNext) { if (interrupted) return false; ActionEvent event = new ActionEvent(this, id, command); if (ms == 0) { listener.actionPerformed(event); return (stateNext == UNCHANGED && levelNext == UNCHANGED || nextStatePriv(this, stateNext == UNCHANGED ? state : stateNext, levelNext == UNCHANGED ? level : levelNext)); } StateHelper me = this; Timer timer = new Timer(ms, id == ActionEvent.ACTION_PERFORMED ? listener : new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (!interrupted) listener.actionPerformed(event); if (!interrupted && (stateNext != UNCHANGED || levelNext != UNCHANGED)) nextStatePriv(me, stateNext == UNCHANGED ? state : stateNext, levelNext == UNCHANGED ? level : levelNext); } }); timer.setRepeats(false); timer.start(); return true; } public static void delayedRun(int ms, Runnable runnable) { new StateHelper(null).delayedRun(ms, runnable, UNCHANGED, UNCHANGED); } /** * Fire an actionPerformed event after a given number of milliseconds. Setting * BOTH stateNext and levelNext to UNCHANGED (Integer.MIN_VALUE) allows the * listener to handle continuing the loop. * * @param ms delay milliseconds. if 0, then this action will be called * synchronously * @param id id for this event, possibly ACTION_PERFORMED (1001), but not * necessarily * @param command key for ActionEvent.getCommand() * @param listener ActionListener to be called. * @param stateNext state to run after the event is processed by the listener, * or UNCHANGED (Integer.MIN_VALUE) to allow listener to handle * this. * @param levelNext level to run after the event is processed by the listener, * or UNCHANGED (Integer.MIN_VALUE) to allow listener to handle * this. * @return not interrupted * * @author Bob Hanson hansonr@stolaf.edu */ public boolean delayedRun(int ms, Runnable runnable, int stateNext, int levelNext) { if (interrupted) return false; if (ms == 0) { return (stateNext == UNCHANGED && levelNext == UNCHANGED || nextStateIfUnchanged(this, runnable, stateNext == UNCHANGED ? state : stateNext, levelNext == UNCHANGED ? level : levelNext)); } StateHelper me = this; /** * @j2sNative * * setTimeout(function() { * * me.nextStateIfUnchanged$O$O$I$I.apply(me, [me, runnable, stateNext, levelNext]); * * },ms); */ { Timer timer = new Timer(ms, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { nextStateIfUnchanged(me, runnable, stateNext, levelNext); } }); timer.setRepeats(false); timer.start(); } return true; } protected boolean nextStateIfUnchanged(Object oThis, Object runnable, int stateNext, int levelNext) { StateHelper me = (StateHelper)(oThis); if (!me.interrupted) ((Runnable) runnable).run(); if (!me.interrupted && (stateNext != UNCHANGED || levelNext != UNCHANGED)) nextStatePriv(oThis, stateNext == UNCHANGED ? me.state : stateNext, levelNext == UNCHANGED ? me.level : levelNext); return true; } /** * sleep and then execute the next state * @param ms */ public void sleep(int ms) { int next = stateNext; delayedState(ms, next); } } /** * open a "url-like" input stream * @param base * @param fileName * @return */ public static BufferedInputStream openStream(Class base, String fileName) { String s = (String) getResource(base, fileName, String.class); return new BufferedInputStream(new ByteArrayInputStream(s.getBytes())); } public static class Performance { public final static int TIME_RESET = 0; public final static int TIME_MARK = 1; public static final int TIME_SET = 2; public static final int TIME_GET = 3; public static long time, mark, set, duration; /** * typical usage: * * Performance.timeCheck(null, Platform.TIME_MARK); * * ... * * Performance.timeCheck("some message", Platform.TIME_MARK); * * reset...[set/mark]n...get (total time) (time spent between set and mark) * * set...get (total time) (time spent between set and get) * * long t0 = now(0); ........ ; dt = now(t0); (time since t0)e * * @param msg * @param mode */ public static void timeCheck(String msg, int mode) { msg = timeCheckStr(msg, mode); if (msg != null) System.err.println(msg); } public static long now(long t) { return System.currentTimeMillis() - t; } public static String timeCheckStr(String msg, int mode) { long t = System.currentTimeMillis(); switch (mode) { case TIME_RESET: time = mark = t; duration = set = 0; if (msg != null) { return ("Platform: timer reset\t\t\t" + msg); } break; case TIME_SET: if (time == 0) time = t; set = t; break; case TIME_MARK: if (set > 0) { // total time between set/mark points duration += (t - set); } else { if (time == 0) { time = mark = t; } if (msg != null) { long m0 = mark; mark = t; return ("Platform: timer mark\t" + ((t - time) / 1000f) + "\t" + ((t - m0) / 1000f) + "\t" + msg); } mark = t; } break; case TIME_GET: if (msg != null) { if (mark < set) duration = t - set; return ("Platform: timer get\t" + ((t - time) / 1000f) + "\t" + ((duration) / 1000f) + "\t" + msg); } set = 0; break; } return null; } } }