0001: /*
0002: #IFNDEF ALT_LICENSE
0003: ThinWire(R) RIA Ajax Framework
0004: Copyright (C) 2003-2007 Custom Credit Systems
0005:
0006: This library is free software; you can redistribute it and/or modify it under
0007: the terms of the GNU Lesser General Public License as published by the Free
0008: Software Foundation; either version 2.1 of the License, or (at your option) any
0009: later version.
0010:
0011: This library is distributed in the hope that it will be useful, but WITHOUT ANY
0012: WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0013: PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
0014:
0015: You should have received a copy of the GNU Lesser General Public License along
0016: with this library; if not, write to the Free Software Foundation, Inc., 59
0017: Temple Place, Suite 330, Boston, MA 02111-1307 USA
0018:
0019: Users who would rather have a commercial license, warranty or support should
0020: contact the following company who invented, built and supports the technology:
0021:
0022: Custom Credit Systems, Richardson, TX 75081, USA.
0023: email: info@thinwire.com ph: +1 (888) 644-6405
0024: http://www.thinwire.com
0025: #ENDIF
0026: [ v1.2_RC2 ]
0027: */
0028: package thinwire.render.web;
0029:
0030: import java.io.ByteArrayOutputStream;
0031: import java.io.CharArrayWriter;
0032: import java.io.IOException;
0033: import java.io.Reader;
0034: import java.io.PrintWriter;
0035: import java.text.DecimalFormat;
0036: import java.util.ArrayList;
0037: import java.util.Date;
0038: import java.util.HashMap;
0039: import java.util.HashSet;
0040: import java.util.LinkedList;
0041: import java.util.List;
0042: import java.util.Map;
0043: import java.util.Set;
0044: import java.util.TreeSet;
0045: import java.util.WeakHashMap;
0046: import java.util.logging.Level;
0047: import java.util.logging.Logger;
0048: import java.util.regex.Pattern;
0049: import java.util.zip.GZIPOutputStream;
0050:
0051: import thinwire.render.RenderStateEvent;
0052: import thinwire.render.RenderStateListener;
0053: import thinwire.ui.*;
0054: import thinwire.ui.FileChooser.FileInfo;
0055: import thinwire.ui.layout.SplitLayout;
0056: import thinwire.ui.style.*;
0057: import thinwire.util.Grid;
0058: import thinwire.util.ImageInfo;
0059:
0060: /**
0061: * @author Joshua J. Gertzen
0062: */
0063: public final class WebApplication extends Application {
0064: private static final String CLASS_NAME = WebApplication.class
0065: .getName();
0066: private static final String PACKAGE_NAME = WebApplication.class
0067: .getPackage().getName();
0068: private static final String EOL = System
0069: .getProperty("line.separator");
0070: private static final Logger log = Logger.getLogger(CLASS_NAME);
0071: private static final Level LEVEL = Level.FINER;
0072:
0073: private static String[] BUILT_IN_RESOURCES = {
0074: "Main.js",
0075: "Class.js",
0076: "HttpRequest.js",
0077: "Component.js",
0078: "Animation.js",
0079: "BaseBrowserLink.js",
0080: "BaseCheckRadio.js",
0081: "BaseContainer.js",
0082: "BaseRange.js",
0083: "BaseText.js",
0084: "BorderImage.js",
0085: "Button.js",
0086: "CheckBox.js",
0087: "Container.js",
0088: "DateBox.js",
0089: "Dialog.js",
0090: "Divider.js",
0091: "DragHandler.js",
0092: "DragAndDropHandler.js",
0093: "DropDown.js",
0094: "EventManager.js",
0095: "Frame.js",
0096: "GridBox.js",
0097: "Hyperlink.js",
0098: "Image.js",
0099: "KeyboardManager.js",
0100: "Label.js",
0101: "Menu.js",
0102: "ProgressBar.js",
0103: "RadioButton.js",
0104: "Slider.js",
0105: "TabFolder.js",
0106: "TabSheet.js",
0107: "TextArea.js",
0108: "TextField.js",
0109: "Tree.js",
0110: "WebBrowser.js",
0111: "class:///" + SplitLayout.class.getName()
0112: + "/resources/SplitLayout.js", "FileChooser.js",
0113: "Startup.js", "FileUploadPage.html", };
0114:
0115: static final byte[] MAIN_PAGE;
0116:
0117: static {
0118: String classURL = "class:///" + CLASS_NAME + "/resources/";
0119:
0120: for (String res : BUILT_IN_RESOURCES) {
0121: if (!res.endsWith(".js")) {
0122: if (!res.startsWith("class:///"))
0123: res = classURL + res;
0124: RemoteFileMap.INSTANCE.add(res, null, Application
0125: .getResourceBytes(res));
0126: }
0127: }
0128:
0129: try {
0130: String twLib = loadJSLibrary(classURL);
0131: //Store the MainPage.html after replacing the JS lib name
0132: MAIN_PAGE = new String(WebApplication
0133: .getResourceBytes(classURL + "MainPage.html"))
0134: .replaceAll("[$][{]ThinWire[.]js[}]", twLib)
0135: .getBytes();
0136: } catch (Exception e) {
0137: if (!(e instanceof RuntimeException))
0138: e = new RuntimeException(e);
0139: throw (RuntimeException) e;
0140: }
0141: }
0142:
0143: private static String loadJSLibrary(String resURL) {
0144: try {
0145: //Write out the library JS
0146: String twPrefix = Application.getPlatformVersionInfo().get(
0147: "productVersion");
0148: log.info("Loading ThinWire(R) RIA Ajax Framework v"
0149: + twPrefix);
0150: twPrefix = "ThinWire_v" + twPrefix;
0151: ByteArrayOutputStream baos = new ByteArrayOutputStream();
0152: GZIPOutputStream os = new GZIPOutputStream(baos);
0153:
0154: for (String res : BUILT_IN_RESOURCES) {
0155: if (res.endsWith(".js")) {
0156: if (!res.startsWith("class:///"))
0157: res = resURL + res;
0158: WebApplication.writeResourceToStream(res, os);
0159: }
0160: }
0161:
0162: os.close();
0163: return RemoteFileMap.INSTANCE.add(null, twPrefix + ".js",
0164: baos.toByteArray());
0165: } catch (Exception e) {
0166: if (!(e instanceof RuntimeException))
0167: e = new RuntimeException(e);
0168: throw (RuntimeException) e;
0169: }
0170: }
0171:
0172: public static Application current() {
0173: Thread t = Thread.currentThread();
0174: return t instanceof EventProcessor ? ((EventProcessor) t).app
0175: : null;
0176: }
0177:
0178: static class Timer {
0179: Runnable task;
0180: long timeout;
0181: boolean repeat;
0182: }
0183:
0184: private static enum State {
0185: INIT, STARTUP, RUNNING, REPAINT, SHUTDOWN, TERMINATED
0186: }
0187:
0188: private String baseFolder;
0189: private String styleSheet;
0190: private int nextCompId;
0191: private State state;
0192: private Map<String, Class<ComponentRenderer>> nameToRenderer;
0193: private Map<Window, WindowRenderer> windowToRenderer;
0194: private Map<Integer, WebComponentListener> webComponentListeners;
0195: private Set<String> clientSideIncludes;
0196: private Map<Component, Object> renderStateListeners;
0197: private EventProcessor proc;
0198:
0199: List<Runnable> timers;
0200: Map<String, Timer> timerMap;
0201: WebComponentEvent startupEvent;
0202: Map<Style, String> styleToStyleClass = new HashMap<Style, String>();
0203: FileInfo[] fileList = new FileInfo[1];
0204:
0205: //Stress Test Variables.
0206: UserActionListener userActionListener;
0207: boolean playBackOn = false;
0208: long playBackStart = -1;
0209: private long playBackDuration = -1;
0210: private long recordDuration = -1;
0211: boolean playBackEventReceived = false;
0212:
0213: //end Stress Test.
0214:
0215: WebApplication(String baseFolder, String mainClass,
0216: String styleSheet, String[] args) throws IOException {
0217: this .baseFolder = baseFolder;
0218: this .styleSheet = styleSheet;
0219: nameToRenderer = new HashMap<String, Class<ComponentRenderer>>();
0220: windowToRenderer = new HashMap<Window, WindowRenderer>();
0221: timerMap = new HashMap<String, Timer>();
0222: timers = new LinkedList<Runnable>();
0223: webComponentListeners = new HashMap<Integer, WebComponentListener>();
0224:
0225: setWebComponentListener(ApplicationEventListener.ID,
0226: new ApplicationEventListener(this ));
0227: startupEvent = ApplicationEventListener.newStartEvent(
0228: mainClass, args);
0229: state = State.INIT;
0230: }
0231:
0232: void signalShutdown() {
0233: if (state == State.SHUTDOWN || state == State.TERMINATED)
0234: return;
0235: if (log.isLoggable(LEVEL))
0236: log.log(LEVEL, Thread.currentThread().getName()
0237: + ": initiating application instance shutdown");
0238: state = State.SHUTDOWN;
0239: }
0240:
0241: void repaint() {
0242: state = State.REPAINT;
0243: }
0244:
0245: void shutdown() {
0246: if (state == State.TERMINATED)
0247: return;
0248: if (state != State.SHUTDOWN)
0249: signalShutdown();
0250:
0251: try {
0252: proc = EventProcessorPool.INSTANCE.getProcessor(this );
0253: WebComponentEvent wce = ApplicationEventListener
0254: .newShutdownEvent();
0255:
0256: try {
0257: proc.handleRequest(wce, new CharArrayWriter());
0258:
0259: while (proc.isInUse()) {
0260: if (log.isLoggable(LEVEL))
0261: log
0262: .log(
0263: LEVEL,
0264: Thread.currentThread()
0265: .getName()
0266: + ": processor returned, probably from flush(), sending null event");
0267: proc.handleRequest((WebComponentEvent) null,
0268: new CharArrayWriter());
0269: }
0270:
0271: if (log.isLoggable(LEVEL))
0272: log.log(LEVEL, Thread.currentThread().getName()
0273: + ": shutdown has completed");
0274: } catch (IOException e) {
0275: log
0276: .log(
0277: Level.SEVERE,
0278: "handleRequest generated IOException during shutdown",
0279: e);
0280: }
0281: } finally {
0282: if (!proc.isInUse()) {
0283: if (log.isLoggable(LEVEL))
0284: log.log(LEVEL, Thread.currentThread().getName()
0285: + ": returning to pool after shutdown");
0286: EventProcessorPool.INSTANCE.returnToPool(proc);
0287: } else {
0288: //This state should only occur if during the shutdown process another 'waitForWindow' Dialog is
0289: //presented for some reason.
0290: EventProcessorPool.INSTANCE.removeFromPool(proc);
0291: if (log.isLoggable(Level.WARNING))
0292: log.log(Level.WARNING, Thread.currentThread()
0293: .getName()
0294: + ": thread still active!");
0295: }
0296:
0297: proc = null;
0298: if (nameToRenderer != null)
0299: nameToRenderer.clear();
0300: if (windowToRenderer != null)
0301: windowToRenderer.clear();
0302: if (webComponentListeners != null)
0303: webComponentListeners.clear();
0304: if (clientSideIncludes != null)
0305: clientSideIncludes.clear();
0306: if (renderStateListeners != null)
0307: renderStateListeners.clear();
0308: if (timerMap != null)
0309: timerMap.clear();
0310: if (styleToStyleClass != null)
0311: styleToStyleClass.clear();
0312: if (userActionListener != null)
0313: userActionListener.stop();
0314:
0315: nameToRenderer = null;
0316: windowToRenderer = null;
0317: webComponentListeners = null;
0318: clientSideIncludes = null;
0319: renderStateListeners = null;
0320: timerMap = null;
0321: styleToStyleClass = null;
0322: fileList = null;
0323: userActionListener = null;
0324:
0325: //app.clientSideFunctionCall(SHUTDOWN_INSTANCE,
0326: // "The application instance has shutdown. Press F5 to restart the application or close the browser to end your session.");
0327:
0328: state = State.TERMINATED;
0329: }
0330: }
0331:
0332: void processActionEvents(Reader r, PrintWriter w)
0333: throws IOException {
0334: if (proc != null)
0335: throw new IllegalStateException(
0336: "There is already an EventProcessor allocated to this application!");
0337:
0338: try {
0339: proc = EventProcessorPool.INSTANCE.getProcessor(this );
0340: proc.handleRequest(r, w);
0341:
0342: if (state != State.RUNNING) {
0343: if (state == State.INIT) {
0344: proc.handleRequest(ApplicationEventListener
0345: .newInitEvent(), w);
0346: state = State.STARTUP;
0347: } else if (state == State.SHUTDOWN) {
0348: shutdown();
0349: } else if (state == State.STARTUP
0350: && (getFrame().getWidth() > 0 || getFrame()
0351: .getHeight() > 0)) {
0352: WebComponentEvent startupEvent = this .startupEvent;
0353: this .startupEvent = null;
0354: proc.handleRequest(startupEvent, w);
0355: state = State.RUNNING;
0356: } else if (state == State.REPAINT) {
0357: proc.handleRequest(ApplicationEventListener
0358: .newRepaintEvent(), w);
0359: state = State.RUNNING;
0360: }
0361: }
0362: } finally {
0363: if (proc != null) {
0364: EventProcessorPool.INSTANCE.returnToPool(proc);
0365: proc = null;
0366: }
0367: }
0368: }
0369:
0370: void sendDefaultComponentStyles(WindowRenderer wr) {
0371: StringBuilder sb = new StringBuilder();
0372: sb.append("{");
0373:
0374: for (Map.Entry<Class<? extends Component>, Style> e : getDefaultStyles()
0375: .entrySet()) {
0376: Class<? extends Component> clazz = e.getKey();
0377:
0378: if (clazz != null) {
0379: String styleClass = ComponentRenderer
0380: .getSimpleClassName(clazz);
0381: Style style = e.getValue();
0382: styleToStyleClass.put(style, styleClass);
0383: sb.append(styleClass).append(":");
0384: getStyleValues(wr, sb, style, null);
0385: sb.append(',');
0386: }
0387: }
0388:
0389: sb.setCharAt(sb.length() - 1, '}');
0390: clientSideMethodCall("tw_Component", "setDefaultStyles", sb);
0391: }
0392:
0393: void sendStyleInitInfo() {
0394: try {
0395: loadStyleSheet(styleSheet);
0396: StringBuilder sb = new StringBuilder();
0397: sb.append('{');
0398:
0399: for (Map.Entry<String, Color> e : getSystemColors()
0400: .entrySet()) {
0401: sb.append(e.getKey()).append(":\"").append(
0402: e.getValue().toHexString()).append("\",");
0403: }
0404:
0405: sb.setCharAt(sb.length() - 1, '}');
0406: clientSideMethodCall("tw_Component", "setSystemColors", sb);
0407:
0408: sb.setLength(0);
0409: sb.append('{');
0410:
0411: for (Map.Entry<String, String> e : getSystemImages()
0412: .entrySet()) {
0413: String value = getSystemFile(e.getValue());
0414: value = RemoteFileMap.INSTANCE.add(value, null,
0415: Application.getResourceBytes(value));
0416: sb.append(e.getKey()).append(":\"").append("%SYSROOT%")
0417: .append(value).append("\",");
0418: }
0419:
0420: sb.setCharAt(sb.length() - 1, '}');
0421: clientSideMethodCall("tw_Component", "setSystemImages", sb);
0422: } catch (Exception e) {
0423: if (e instanceof RuntimeException)
0424: throw (RuntimeException) e;
0425: throw new RuntimeException(e);
0426: }
0427: }
0428:
0429: Color getSystemColor(String name) {
0430: return getSystemColors().get(name);
0431: }
0432:
0433: StringBuilder getStyleValue(ComponentRenderer cr, StringBuilder sb,
0434: String propertyName, Object value) {
0435: if (propertyName.equals(Border.PROPERTY_BORDER_SIZE)) {
0436: sb.append(RichTextParser.STYLE_BORDER_WIDTH).append(":\"")
0437: .append(value).append("px");
0438: } else if (propertyName.equals(Font.PROPERTY_FONT_SIZE)) {
0439: sb.append(RichTextParser.STYLE_FONT_SIZE).append(":\"")
0440: .append(value).append("pt");
0441: } else if (propertyName.equals(Border.PROPERTY_BORDER_IMAGE)) {
0442: sb.append(RichTextParser.STYLE_BORDER_IMAGE).append(":\"");
0443: ImageInfo ii = (ImageInfo) value;
0444: String name = ii.getName();
0445:
0446: if (name.length() > 0) {
0447: sb.append(cr.getQualifiedURL(name));
0448: sb.append(',').append(ii.getWidth()).append(',')
0449: .append(ii.getHeight());
0450: }
0451: } else {
0452: if (propertyName.equals(Border.PROPERTY_BORDER_TYPE)) {
0453: sb.append(RichTextParser.STYLE_BORDER_STYLE);
0454: } else if (propertyName.equals(Font.PROPERTY_FONT_FAMILY)) {
0455: sb.append(RichTextParser.STYLE_FONT_FAMILY);
0456: } else if (propertyName
0457: .equals(Background.PROPERTY_BACKGROUND_POSITION)) {
0458: sb.append(RichTextParser.STYLE_BACKGROUND_POSITION);
0459: } else if (propertyName.equals(Font.PROPERTY_FONT_ITALIC)) {
0460: sb.append(RichTextParser.STYLE_FONT_STYLE);
0461: value = value == Boolean.TRUE ? "italic" : "normal";
0462: } else if (propertyName.equals(Font.PROPERTY_FONT_BOLD)) {
0463: sb.append(RichTextParser.STYLE_FONT_WEIGHT);
0464: value = value == Boolean.TRUE ? "bold" : "normal";
0465: } else if (propertyName
0466: .equals(Background.PROPERTY_BACKGROUND_IMAGE)) {
0467: sb.append(RichTextParser.STYLE_BACKGROUND_IMAGE);
0468: value = cr.getQualifiedURL((String) value);
0469: } else if (value instanceof Color) {
0470: if (propertyName.equals(Font.PROPERTY_FONT_COLOR)) {
0471: sb.append(RichTextParser.STYLE_COLOR);
0472: } else if (propertyName
0473: .equals(Background.PROPERTY_BACKGROUND_COLOR)) {
0474: sb.append(RichTextParser.STYLE_BACKGROUND_COLOR);
0475: } else if (propertyName
0476: .equals(Border.PROPERTY_BORDER_COLOR)) {
0477: sb.append(RichTextParser.STYLE_BORDER_COLOR);
0478: } else {
0479: throw new IllegalArgumentException(
0480: "unknown style property '" + propertyName
0481: + "' with value '" + value + "'");
0482: }
0483:
0484: Color color = (Color) value;
0485: if (color.isSystemColor())
0486: color = getSystemColors().get(color.toString());
0487: value = color.toHexString();
0488: } else if (propertyName
0489: .equals(Font.PROPERTY_FONT_UNDERLINE)
0490: || propertyName.equals(Font.PROPERTY_FONT_STRIKE)) {
0491: sb.append(RichTextParser.STYLE_TEXT_DECORATION);
0492: Boolean[] bool = (Boolean[]) value;
0493:
0494: if (bool[0] == Boolean.TRUE && bool[1] == Boolean.TRUE) {
0495: value = "underline line-through";
0496: } else if (bool[0] == Boolean.TRUE) {
0497: value = "underline";
0498: } else if (bool[1] == Boolean.TRUE) {
0499: value = "line-through";
0500: } else {
0501: value = "none";
0502: }
0503: } else if (propertyName
0504: .equals(Background.PROPERTY_BACKGROUND_REPEAT)) {
0505: sb.append(RichTextParser.STYLE_BACKGROUND_REPEAT);
0506:
0507: switch ((Background.Repeat) value) {
0508: case BOTH:
0509: value = "repeat";
0510: break;
0511: case X:
0512: value = "repeat-x";
0513: break;
0514: case Y:
0515: value = "repeat-y";
0516: break;
0517: default:
0518: value = "no-repeat";
0519: break;
0520: }
0521: } else {
0522: throw new IllegalArgumentException(
0523: "unknown style property '" + propertyName
0524: + "' with value '" + value + "'");
0525: }
0526:
0527: sb.append(":\"").append(value);
0528: }
0529:
0530: sb.append("\",");
0531: return sb;
0532: }
0533:
0534: StringBuilder getStyleValues(ComponentRenderer cr,
0535: StringBuilder sb, Style s, Style ds) {
0536: Background background = s.getBackground();
0537: Color backgroundColor = background.getColor();
0538: String backgroundImage = background.getImage();
0539: Background.Repeat backgroundRepeat = background.getRepeat();
0540: Background.Position backgroundPosition = background
0541: .getPosition();
0542:
0543: Border border = s.getBorder();
0544: Border.Type borderType = border.getType();
0545: Color borderColor = border.getColor();
0546: Integer borderSize = border.getSize();
0547: String borderImage = border.getImage();
0548:
0549: Font font = s.getFont();
0550: Font.Family fontFamily = font.getFamily();
0551: Double fontSize = font.getSize();
0552: Color fontColor = font.getColor();
0553: Boolean fontBold = font.isBold();
0554: Boolean fontItalic = font.isItalic();
0555: Boolean fontUnderline = font.isUnderline();
0556: Boolean fontStrike = font.isStrike();
0557:
0558: if (ds != null) {
0559: background = ds.getBackground();
0560: if (backgroundColor.equals(background.getColor()))
0561: backgroundColor = null;
0562: if (backgroundImage.equals(background.getImage()))
0563: backgroundImage = null;
0564: if (backgroundRepeat.equals(background.getRepeat()))
0565: backgroundRepeat = null;
0566: if (backgroundPosition.equals(background.getPosition()))
0567: backgroundPosition = null;
0568:
0569: border = ds.getBorder();
0570: if (borderType.equals(border.getType()))
0571: borderType = null;
0572: if (borderColor.equals(border.getColor()))
0573: borderColor = null;
0574: if (borderSize.equals(border.getSize()))
0575: borderSize = null;
0576: if (borderImage.equals(border.getImage()))
0577: borderImage = null;
0578:
0579: font = ds.getFont();
0580: if (fontFamily.equals(font.getFamily()))
0581: fontFamily = null;
0582: if (fontSize.equals(font.getSize()))
0583: fontSize = null;
0584: if (fontColor.equals(font.getColor()))
0585: fontColor = null;
0586: if (fontBold.equals(font.isBold()))
0587: fontBold = null;
0588: if (fontItalic.equals(font.isItalic()))
0589: fontItalic = null;
0590: if (fontUnderline.equals(font.isUnderline()))
0591: fontUnderline = null;
0592: if (fontStrike.equals(font.isStrike()))
0593: fontStrike = null;
0594: }
0595:
0596: sb.append("{");
0597:
0598: if (backgroundColor != null)
0599: getStyleValue(cr, sb, Background.PROPERTY_BACKGROUND_COLOR,
0600: backgroundColor);
0601: if (backgroundImage != null)
0602: getStyleValue(cr, sb, Background.PROPERTY_BACKGROUND_IMAGE,
0603: backgroundImage);
0604: if (backgroundRepeat != null)
0605: getStyleValue(cr, sb,
0606: Background.PROPERTY_BACKGROUND_REPEAT,
0607: backgroundRepeat);
0608: if (backgroundPosition != null)
0609: getStyleValue(cr, sb,
0610: Background.PROPERTY_BACKGROUND_POSITION,
0611: backgroundPosition);
0612:
0613: if (borderType != null && borderType != Border.Type.IMAGE) {
0614: if (borderType == Border.Type.NONE) {
0615: borderType = Border.Type.SOLID;
0616: borderColor = backgroundColor;
0617: }
0618:
0619: getStyleValue(cr, sb, "borderType", borderType);
0620: }
0621:
0622: if (borderImage != null)
0623: getStyleValue(cr, sb, Border.PROPERTY_BORDER_IMAGE, s
0624: .getBorder().getImageInfo());
0625: if (borderColor != null)
0626: getStyleValue(cr, sb, Border.PROPERTY_BORDER_COLOR,
0627: borderColor);
0628: if (borderSize != null)
0629: getStyleValue(cr, sb, Border.PROPERTY_BORDER_SIZE,
0630: borderSize);
0631: if (fontFamily != null)
0632: getStyleValue(cr, sb, Font.PROPERTY_FONT_FAMILY, fontFamily);
0633: if (fontSize != null)
0634: getStyleValue(cr, sb, Font.PROPERTY_FONT_SIZE, fontSize);
0635: if (fontColor != null)
0636: getStyleValue(cr, sb, Font.PROPERTY_FONT_COLOR, fontColor);
0637: if (fontBold != null)
0638: getStyleValue(cr, sb, Font.PROPERTY_FONT_BOLD, fontBold);
0639: if (fontItalic != null)
0640: getStyleValue(cr, sb, Font.PROPERTY_FONT_ITALIC, fontItalic);
0641: if (fontUnderline != null || fontStrike != null)
0642: getStyleValue(cr, sb, Font.PROPERTY_FONT_UNDERLINE,
0643: new Boolean[] { fontUnderline, fontStrike });
0644:
0645: if (sb.length() > 1) {
0646: sb.setCharAt(sb.length() - 1, '}');
0647: } else {
0648: sb.setLength(0);
0649: }
0650:
0651: return sb;
0652: }
0653:
0654: Integer getNextComponentId() {
0655: nextCompId = nextCompId == Integer.MAX_VALUE ? 1
0656: : nextCompId + 1;
0657: return new Integer(nextCompId);
0658: }
0659:
0660: ComponentRenderer getRenderer(Component comp) {
0661: Class compClass = comp.getClass();
0662: String className = compClass.getName();
0663: Class<ComponentRenderer> renderClazz = nameToRenderer
0664: .get(className);
0665:
0666: if (renderClazz == null) {
0667: String compClassName = className;
0668: List<Class> lst = new ArrayList<Class>();
0669: lst.add(compClass);
0670:
0671: do {
0672: compClass = lst.remove(0);
0673: className = compClass.getName();
0674: String qualClassName = PACKAGE_NAME
0675: + '.'
0676: + className.substring(className
0677: .lastIndexOf('.') + 1) + "Renderer";
0678:
0679: try {
0680: renderClazz = (Class) Class.forName(qualClassName);
0681: nameToRenderer.put(compClassName, renderClazz);
0682: break;
0683: } catch (ClassNotFoundException e) {
0684: //We'll continue trying until no classes in the hierarchy are left.
0685: }
0686:
0687: Class sc = compClass.getSuperclass();
0688: if (Component.class.isAssignableFrom(sc))
0689: lst.add(sc);
0690:
0691: for (Class i : compClass.getInterfaces()) {
0692: if (Component.class.isAssignableFrom(i))
0693: lst.add(i);
0694: }
0695: } while (lst.size() > 0);
0696: }
0697:
0698: if (renderClazz != null) {
0699: try {
0700: return (ComponentRenderer) renderClazz.newInstance();
0701: } catch (IllegalAccessException e) {
0702: throw new RuntimeException(
0703: "illegal access while trying to access "
0704: + renderClazz.getName(), e);
0705: } catch (InstantiationException e) {
0706: throw new RuntimeException(
0707: renderClazz.getName()
0708: + " could not be instantiated, using default renderer instead",
0709: e);
0710: }
0711: }
0712:
0713: throw new RuntimeException("Renderer for component class '"
0714: + comp.getClass() + "' not found");
0715: }
0716:
0717: public void clientSideIncludeFile(String localName) {
0718: if (clientSideIncludes == null) {
0719: clientSideIncludes = new HashSet<String>(3);
0720: } else if (clientSideIncludes.contains(localName)) {
0721: return;
0722: }
0723:
0724: if (localName == null || localName.trim().length() == 0)
0725: throw new IllegalArgumentException(
0726: "localName == null || localName.trim().length() == 0");
0727: if (!localName.startsWith("class:///"))
0728: localName = this .getRelativeFile(localName)
0729: .getAbsolutePath();
0730: String remoteName = RemoteFileMap.INSTANCE.add(localName);
0731: clientSideFunctionCallWaitForReturn("tw_include", remoteName);
0732: clientSideIncludes.add(localName);
0733: }
0734:
0735: public void clientSideFunctionCall(String functionName,
0736: Object... args) {
0737: clientSideCallImpl(false, null, functionName, args);
0738: }
0739:
0740: public String clientSideFunctionCallWaitForReturn(
0741: String functionName, Object... args) {
0742: return clientSideCallImpl(true, null, functionName, args);
0743: }
0744:
0745: public void clientSideMethodCall(String objectName,
0746: String methodName, Object... args) {
0747: clientSideCallImpl(false, objectName, methodName, args);
0748: }
0749:
0750: public String clientSideMethodCallWaitForReturn(String objectName,
0751: String methodName, Object... args) {
0752: return clientSideCallImpl(true, objectName, methodName, args);
0753: }
0754:
0755: public void clientSideMethodCall(Integer componentId,
0756: String methodName, Object... args) {
0757: clientSideCallImpl(false, componentId, methodName, args);
0758: }
0759:
0760: public String clientSideMethodCallWaitForReturn(
0761: Integer componentId, String methodName, Object... args) {
0762: return clientSideCallImpl(true, componentId, methodName, args);
0763: }
0764:
0765: private String clientSideCallImpl(boolean sync, Object objectId,
0766: String name, Object[] args) {
0767: if (proc == null)
0768: throw new IllegalStateException(
0769: "No event processor allocated to this application. This is likely caused by making UI calls from a non-UI thread");
0770: return proc.postUpdateEvent(sync, objectId, name, args);
0771: }
0772:
0773: public void addRenderStateListener(Component comp,
0774: RenderStateListener r) {
0775: Integer id = getComponentId(comp);
0776:
0777: if (id == null) {
0778: if (renderStateListeners == null)
0779: renderStateListeners = new WeakHashMap<Component, Object>();
0780: Object o = renderStateListeners.get(comp);
0781:
0782: if (o instanceof RenderStateListener) {
0783: if (o != r) {
0784: Set<RenderStateListener> l = new HashSet<RenderStateListener>(
0785: 3);
0786: l.add((RenderStateListener) o);
0787: l.add(r);
0788: renderStateListeners.put(comp, l);
0789: }
0790: } else if (o instanceof Set) {
0791: ((Set) o).add(r);
0792: } else {
0793: renderStateListeners.put(comp, r);
0794: }
0795: } else {
0796: r.renderStateChange(new RenderStateEvent(comp, id));
0797: }
0798: }
0799:
0800: public void removeRenderStateListener(Component comp,
0801: RenderStateListener r) {
0802: if (renderStateListeners != null) {
0803: Object o = renderStateListeners.get(comp);
0804:
0805: if (o instanceof RenderStateListener) {
0806: if (o == r)
0807: renderStateListeners.remove(comp);
0808: } else if (o instanceof List) {
0809: ((List) o).remove(r);
0810: }
0811: }
0812: }
0813:
0814: void flushRenderCallbacks(Component comp, Integer id) {
0815: if (renderStateListeners == null)
0816: return;
0817: Object o = renderStateListeners.get(comp);
0818: if (o == null)
0819: return;
0820:
0821: RenderStateEvent ev = new RenderStateEvent(comp, id);
0822:
0823: if (o instanceof RenderStateListener) {
0824: ((RenderStateListener) o).renderStateChange(ev);
0825: } else {
0826: for (RenderStateListener r : ((Set<RenderStateListener>) o)) {
0827: r.renderStateChange(ev);
0828: }
0829: }
0830: }
0831:
0832: public Integer getComponentId(Component comp) {
0833: Object w = comp;
0834:
0835: while (w != null && !(w instanceof Window)) {
0836: if (w instanceof Grid.Row) {
0837: w = ((Grid.Row) w).getParent();
0838: } else {
0839: w = ((Component) w).getParent();
0840: }
0841: }
0842:
0843: if (w != null) {
0844: WindowRenderer wr = windowToRenderer.get(w);
0845: return wr == null ? null : wr.getComponentId(comp);
0846: } else {
0847: return null;
0848: }
0849: }
0850:
0851: public Component getComponentFromId(Integer id) {
0852: return ((ComponentRenderer) getWebComponentListener(id)).comp;
0853: }
0854:
0855: void setWebComponentListener(Integer compId,
0856: WebComponentListener listener) {
0857: synchronized (webComponentListeners) {
0858: if (listener == null)
0859: webComponentListeners.remove(compId);
0860: else
0861: webComponentListeners.put(compId, listener);
0862: }
0863: }
0864:
0865: WebComponentListener getWebComponentListener(Integer compId) {
0866: synchronized (webComponentListeners) {
0867: return webComponentListeners.get(compId);
0868: }
0869: }
0870:
0871: public String getBaseFolder() {
0872: return baseFolder;
0873: }
0874:
0875: protected void captureThread() {
0876: if (proc == null)
0877: throw new IllegalStateException(
0878: "No event processor allocated to this application. This is likely caused by making UI calls from a non-UI thread");
0879: proc.captureThread();
0880: }
0881:
0882: protected void releaseThread() {
0883: if (proc == null)
0884: throw new IllegalStateException(
0885: "No event processor allocated to this application. This is likely caused by making UI calls from a non-UI thread");
0886: proc.releaseThread();
0887: }
0888:
0889: protected void showWindow(Window w) {
0890: WindowRenderer wr = (WindowRenderer) windowToRenderer.get(w);
0891: if (wr != null)
0892: throw new IllegalStateException(
0893: "A window cannot be set to visible while it is already visible");
0894: windowToRenderer.put(w, wr = (WindowRenderer) getRenderer(w));
0895: wr.ai = this ;
0896:
0897: if (wr instanceof DialogRenderer) {
0898: wr.render(wr, w, windowToRenderer.get(getFrame()));
0899:
0900: //Force events to be sent to client because dialog show's must be immediate!
0901: proc.flush();
0902: } else {
0903: sendDefaultComponentStyles(wr);
0904: wr.render(wr, w, null);
0905: }
0906: }
0907:
0908: protected void hideWindow(Window w) {
0909: WindowRenderer wr = (WindowRenderer) windowToRenderer.remove(w);
0910: if (wr == null)
0911: throw new IllegalStateException(
0912: "Cannot close a window that has not been set to visible");
0913: if (log.isLoggable(LEVEL))
0914: log.log(LEVEL, Thread.currentThread().getName()
0915: + ": closing window with id:" + wr.id);
0916: wr.destroy();
0917:
0918: //Force events to be sent to client because dialog hide's must be immediate!
0919: if (wr instanceof DialogRenderer)
0920: proc.flush();
0921: }
0922:
0923: WindowRenderer getWindowRenderer(Window w) {
0924: return (WindowRenderer) windowToRenderer.get(w);
0925: }
0926:
0927: protected FileInfo getFileInfo() {
0928: synchronized (fileList) {
0929: while (fileList[0] == null) {
0930: try {
0931: fileList.wait();
0932: } catch (Exception e) {
0933: throw new RuntimeException(e);
0934: }
0935: }
0936: FileChooser.FileInfo fileInfo = fileList[0];
0937: fileList[0] = null;
0938: return fileInfo;
0939: }
0940: }
0941:
0942: protected String getQualifiedURL(String location) {
0943: return windowToRenderer.get(getFrame()).getQualifiedURL(
0944: location);
0945: }
0946:
0947: protected void removeFileFromMap(String location) {
0948: windowToRenderer.get(getFrame()).removeFileFromMap(location);
0949: }
0950:
0951: public void addTimerTask(Runnable task, long timeout) {
0952: addTimerTask(task, timeout, false);
0953: }
0954:
0955: public void addTimerTask(Runnable task, long timeout, boolean repeat) {
0956:
0957: if (timeout == 0 && !repeat) {
0958: if (!timers.contains(task))
0959: timers.add(task);
0960: } else {
0961: String timerId = String.valueOf(System
0962: .identityHashCode(task));
0963:
0964: if (timerMap.containsKey(timerId)) {
0965: resetTimerTask(task);
0966: } else {
0967: Timer timer = new Timer();
0968: timer.task = task;
0969: timer.timeout = timeout;
0970: timer.repeat = repeat;
0971: timerMap.put(timerId, timer);
0972: clientSideFunctionCall("tw_addTimerTask", timerId,
0973: timeout);
0974: }
0975: }
0976: }
0977:
0978: public void resetTimerTask(Runnable task) {
0979: if (!timers.contains(task)) {
0980: String timerId = String.valueOf(System
0981: .identityHashCode(task));
0982: Timer timer = timerMap.get(timerId);
0983: if (timer != null)
0984: clientSideFunctionCall("tw_addTimerTask", timerId,
0985: timer.timeout);
0986: }
0987: }
0988:
0989: public void removeTimerTask(Runnable task) {
0990: if (!timers.remove(task)) {
0991: String timerId = String.valueOf(System
0992: .identityHashCode(task));
0993: clientSideFunctionCall("tw_removeTimerTask", timerId);
0994: timerMap.remove(timerId);
0995: }
0996: }
0997:
0998: protected Object setPackagePrivateMember(String memberName,
0999: Component comp, Object value) {
1000: return super .setPackagePrivateMember(memberName, comp, value);
1001: }
1002:
1003: public void setUserActionListener(UserActionListener listener) {
1004: this .userActionListener = listener;
1005: }
1006:
1007: void notifyUserActionReceived(WebComponentEvent evt) {
1008: UserActionEvent uae = new UserActionEvent(evt);
1009: this .userActionListener.actionReceived(uae);
1010: }
1011:
1012: protected void finalize() {
1013: if (log.isLoggable(LEVEL))
1014: log.log(LEVEL, Thread.currentThread().getName()
1015: + ": finalizing app");
1016: }
1017:
1018: public void setPlayBackOn(boolean playBackOn) {
1019: this .playBackOn = playBackOn;
1020: if (!this .playBackOn) {
1021: this .endPlayBack();
1022: }
1023: }
1024:
1025: private void endPlayBack() {
1026: log.entering("ThinWireApplication", "endPlayBack");
1027: this .playBackDuration = new Date().getTime()
1028: - this .playBackStart;
1029: StringBuilder sb = new StringBuilder(EOL + EOL);
1030: sb.append(Thread.currentThread().getName()
1031: + " Playback Statistics" + EOL);
1032: sb
1033: .append("-----------------------------------------------------"
1034: + EOL);
1035: sb.append("Duration of recording session: "
1036: + this .recordDuration + EOL);
1037: sb.append(" Duration of playback session: "
1038: + this .playBackDuration + EOL);
1039: DecimalFormat df = new DecimalFormat();
1040: df.setMinimumFractionDigits(2);
1041: df.setMinimumIntegerDigits(1);
1042: double drecord = new Long(this .recordDuration).doubleValue();
1043: double dplay = new Long(this .playBackDuration).doubleValue();
1044: double pctChange = (((dplay / drecord) - 1) * 100);
1045: sb.append(" % change: "
1046: + df.format(pctChange) + EOL + EOL);
1047: log.info(sb.toString());
1048: log.exiting("ThinWireApplication", "endPlayBack");
1049: }
1050:
1051: public void setRecordDuration(long recordDuration) {
1052: this.recordDuration = recordDuration;
1053: }
1054: }
|