001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026:
027: package com.sun.perseus.model;
028:
029: import com.sun.perseus.platform.GZIPSupport;
030:
031: import org.w3c.dom.Document;
032: import org.w3c.dom.Element;
033: import org.w3c.dom.svg.SVGElement;
034: import org.w3c.dom.DOMException;
035:
036: import java.io.ByteArrayOutputStream;
037: import java.io.IOException;
038: import java.io.InputStream;
039:
040: import java.util.Vector;
041:
042: import com.sun.perseus.util.SVGConstants;
043:
044: import com.sun.perseus.builder.ModelBuilder;
045: import com.sun.perseus.builder.SVGTinyModelFactory;
046:
047: import javax.microedition.m2g.SVGImage;
048: import javax.microedition.m2g.ScalableImage;
049: import javax.microedition.m2g.ExternalResourceHandler;
050:
051: import org.xml.sax.SAXParseException;
052:
053: import com.sun.perseus.j2d.ImageLoaderUtil;
054: import com.sun.perseus.j2d.RasterImage;
055:
056: import com.sun.perseus.model.DocumentNode;
057: import com.sun.perseus.model.ElementNode;
058: import com.sun.perseus.model.ModelEvent;
059: import com.sun.perseus.model.ModelNode;
060: import com.sun.perseus.model.SVG;
061: import com.sun.perseus.model.Time;
062: import com.sun.perseus.model.UpdateAdapter;
063:
064: /**
065: *
066: */
067: public class SVGImageImpl extends SVGImage {
068: /**
069: * Size of the byte buffer used to read image input streams handed
070: * by ExternalResourceHandler implementations.
071: */
072: static final int DEFAULT_IMAGE_READ_BUFFER_LENGTH = 64;
073:
074: /**
075: * The image document
076: */
077: DocumentNode documentNode = null;
078:
079: /**
080: * The default width for an empty SVG image
081: */
082: final static public int DEFAULT_WIDTH = 100;
083:
084: /**
085: * The default height for an empty SVG image
086: */
087: final static public int DEFAULT_HEIGHT = 100;
088:
089: /**
090: * The SVGImageLoader
091: */
092: SVGImageLoader svgImageLoader;
093:
094: /**
095: * The current SVGElement with the focus
096: */
097: SVGElement lastElement = null;
098:
099: /**
100: * URI requested by SVGImageLoader's getImageAndWait().
101: */
102: String waitURI = new String();
103:
104: /**
105: * Received null resourceData in requestCompleted(). Used in
106: * getImageAndWait() case.
107: */
108: boolean isBrokenImage = false;
109:
110: /**
111: * Returns the associated <code>Document</code>.
112: *
113: * @return the associated <code>Document</code>.
114: */
115: public Document getDocument() {
116: return (Document) documentNode;
117: }
118:
119: /**
120: * Private constructor. Requires a non null DocumentNode.
121: *
122: * @param documentNode the associated DocumentNode. Should not be null.
123: * @param ExternalResourceHandler the associated handler.
124: */
125: private SVGImageImpl(final DocumentNode documentNode,
126: final ExternalResourceHandler handler) {
127: this .documentNode = documentNode;
128:
129: if (handler != null) {
130: svgImageLoader = new SVGImageLoader(this , handler);
131: documentNode.setImageLoader(svgImageLoader);
132: } else {
133: documentNode.setImageLoader(new DefaultImageLoader());
134: }
135: }
136:
137: // JAVADOC COMMENT ELIDED
138: public static SVGImage createEmptyImage(
139: ExternalResourceHandler handler) {
140: DocumentNode documentNode = new DocumentNode();
141:
142: SVG svg = new SVG(documentNode);
143: svg.setWidth((float) DEFAULT_WIDTH);
144: svg.setHeight((float) DEFAULT_HEIGHT);
145: documentNode.add(svg);
146:
147: documentNode.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
148:
149: SVGImageImpl sii = new SVGImageImpl(documentNode, handler);
150:
151: // Add prototypes for the SVG Tiny elements.
152: Vector prototypes = SVGTinyModelFactory
153: .getPrototypes(documentNode);
154: int n = prototypes.size();
155: for (int i = 0; i < n; i++) {
156: documentNode.addPrototype((ElementNode) prototypes
157: .elementAt(i));
158: }
159:
160: // Initialize the timing engine and sample at time zero.
161: documentNode.initializeTimingEngine();
162: documentNode.sample(new Time(0));
163: documentNode.setLoaded(true);
164:
165: return sii;
166: }
167:
168: /**
169: * This method is used to dispatch a mouse event of the specified
170: * <code>type</code> to the document. The mouse position is given as screen
171: * coordinates <code>x, y</code>. The only mouse event type supported is
172: * "click". Note that when a "click" event is dispatched, a "DOMActivate" is
173: * automatically dispatched by the underlying implementation. If a different
174: * type is specified, a DOMException with error code NOT_SUPPORTED_ERR is
175: * thrown. In the case, where x, y values are outside the viewport area or
176: * no target is available for the x, y coordinates, the event is not
177: * dispatched.
178: *
179: *
180: * @param type the type of mouse event.
181: * @param x the x location of the mouse/pointer in viewport coordinate
182: * system.
183: * @param y the y location of the mouse/pointer in viewport coordinate
184: * system.
185: *
186: * @throws DOMException with error code NOT_SUPPORTED_ERR: if the event
187: * <code>type</code> is not supported.
188: * @throws NullPointerException if <code>type</code> is null.
189: * @throws IllegalArgumentException if the x or y values are negative.
190: *
191: */
192: public void dispatchMouseEvent(String type, int x, int y)
193: throws NullPointerException, IllegalArgumentException,
194: DOMException {
195:
196: if (type == null) {
197: throw new NullPointerException();
198: }
199:
200: if (x < 0 || y < 0) {
201: throw new IllegalArgumentException();
202: }
203:
204: if (!type.equals(SVGConstants.SVG_CLICK_EVENT_TYPE)) {
205: throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
206: "Event type is NOT click.");
207: }
208:
209: // CHECK x,y outside the viewport area
210: if (x > getViewportWidth() || y > getViewportHeight()) {
211: return;
212: }
213:
214: // Get the target hit by the mouse event
215: float[] pt = { x, y };
216: ModelNode target = documentNode.nodeHitAt(pt);
217:
218: if (target == null) {
219: return;
220: }
221:
222: // Dispatch an activate on the element that was clicked on.
223: documentNode.dispatchEvent(documentNode.initEngineEvent(
224: SVGConstants.SVG_DOMACTIVATE_EVENT_TYPE, target));
225:
226: // Now, dispatch the click event.
227: documentNode.dispatchEvent(documentNode.initEngineEvent(
228: SVGConstants.SVG_CLICK_EVENT_TYPE, target));
229: };
230:
231: /**
232: *
233: */
234: public void activate() {
235: if (lastElement == null) {
236: return;
237: }
238:
239: documentNode.dispatchEvent(documentNode.initEngineEvent(
240: SVGConstants.SVG_DOMACTIVATE_EVENT_TYPE,
241: (ModelNode) lastElement));
242: };
243:
244: /**
245: *
246: */
247: public void focusOn(SVGElement element) throws DOMException {
248: if (element == null) {
249: if (lastElement != null) {
250: // remove current focus
251: documentNode
252: .dispatchEvent(documentNode
253: .initEngineEvent(
254: SVGConstants.SVG_DOMFOCUSOUT_EVENT_TYPE,
255: (ModelNode) lastElement));
256: lastElement = null;
257: }
258: return;
259: }
260:
261: DocumentNode ownerDocument = ((ModelNode) element)
262: .getOwnerDocument();
263: if (!ownerDocument.equals(documentNode)) {
264: throw new DOMException(DOMException.WRONG_DOCUMENT_ERR,
265: "Invalid element.");
266: }
267:
268: // If this element is different from the previous element focused,
269: // dispatch a "DOMFocusOut" event to the previous element.
270: if (lastElement != element) {
271: if (lastElement != null) {
272: documentNode
273: .dispatchEvent(documentNode
274: .initEngineEvent(
275: SVGConstants.SVG_DOMFOCUSOUT_EVENT_TYPE,
276: (ModelNode) lastElement));
277: }
278:
279: documentNode.dispatchEvent(documentNode.initEngineEvent(
280: SVGConstants.SVG_DOMFOCUSIN_EVENT_TYPE,
281: (ModelNode) element));
282:
283: lastElement = element;
284: }
285: };
286:
287: /**
288: *
289: */
290: public void incrementTime(final float seconds) {
291: if (documentNode.updateQueue == null) {
292: // We are not running with an update queue, which means we are
293: // not running withing an SVGAnimator. Therefore, we simply
294: // increment time.
295: documentNode.incrementTime(seconds);
296: } else {
297: // The impact of changing the time must be made visible immediately
298: // because the document is being displayed in an
299: // SVGAnimator. Therefore, we force the document to sample and apply
300: // animations so that resulting rendering changes get displayed
301: // immediately.
302:
303: // We are running in an update queue. Check whether or not we are
304: // already in the update thread.
305: if (Thread.currentThread() == documentNode.updateQueue
306: .getThread()) {
307: // We are already in the right thread.
308: documentNode.incrementTime(seconds);
309: documentNode.applyAnimations();
310: } else {
311: // We are in the wrong thread. Move to the right thread.
312: documentNode.safeInvokeAndWait(new Runnable() {
313: public void run() {
314: documentNode.incrementTime(seconds);
315: documentNode.applyAnimations();
316: }
317: });
318: }
319: }
320: }
321:
322: // Inherited from ScalableImage...
323:
324: /**
325: *
326: */
327: public static ScalableImage createImage(InputStream stream,
328: ExternalResourceHandler handler) throws IOException {
329: if (stream == null) {
330: throw new NullPointerException();
331: }
332:
333: DocumentNode documentNode = new DocumentNode();
334:
335: return loadDocument(documentNode, stream, handler);
336: }
337:
338: /**
339: * Implementation helper.
340: *
341: * @param documentNode the <code>DocumentNode</code> object into which the
342: * content of the stream should be loaded.
343: * @param is the <code>InputStream</code> to the content to be loaded.
344: * @param handler the <code>ExternalResourceHandler</code>. May be null.
345: */
346: private static ScalableImage loadDocument(
347: final DocumentNode documentNode, final InputStream is,
348: final ExternalResourceHandler handler) throws IOException {
349: UpdateAdapter updateAdapter = new UpdateAdapter();
350: documentNode.setUpdateListener(updateAdapter);
351:
352: SVGImageImpl sii = new SVGImageImpl(documentNode, handler);
353:
354: ModelBuilder.loadDocument(is, documentNode);
355:
356: if (updateAdapter.hasLoadingFailed()) {
357: if (updateAdapter.getLoadingFailedException() != null) {
358: throw new IOException(updateAdapter
359: .getLoadingFailedException().getMessage());
360: }
361: throw new IOException();
362: }
363:
364: // Now, get image width/height from <svg> element and set it in
365: // DocumentNode
366: Element root = documentNode.getDocumentElement();
367: if (!(root instanceof SVG)) {
368: throw new IOException(Messages.formatMessage(
369: Messages.ERROR_NON_SVG_RESOURCE,
370: new String[] { documentNode.getURIBase() }));
371: }
372:
373: SVG svg = (SVG) root;
374: int width = (int) svg.getWidth();
375: int height = (int) svg.getHeight();
376: documentNode.setSize(width, height);
377:
378: // Now, initialize the timing engine and sample at zero.
379: documentNode.initializeTimingEngine();
380: documentNode.sample(new Time(0));
381: return sii;
382: }
383:
384: /**
385: *
386: */
387: public static ScalableImage createImage(String URL,
388: ExternalResourceHandler handler) throws IOException,
389: SecurityException {
390: if (URL == null) {
391: throw new NullPointerException();
392: }
393:
394: DocumentNode documentNode = new DocumentNode();
395: documentNode.setDocumentURI(URL);
396:
397: InputStream is = null;
398:
399: try {
400: is = GZIPSupport.openHandleGZIP(URL);
401: } catch (IOException ioe) {
402: throw new IllegalArgumentException(ioe.getMessage());
403: }
404:
405: return loadDocument(documentNode, is, handler);
406: }
407:
408: /**
409: * An area where the ScalableImage is rendered is called viewport.
410: * If a part of the viewport lays outside of the target clipping
411: * rectangle it is clipped. The viewport coordinates are given
412: * relative to the target rendering surface.
413: *
414: */
415: public void setViewportWidth(int width) {
416: if (width < 0) {
417: throw new IllegalArgumentException();
418: }
419:
420: documentNode.setSize(width, documentNode.getHeight());
421: }
422:
423: /**
424: *
425: */
426: public void setViewportHeight(int height) {
427: if (height < 0) {
428: throw new IllegalArgumentException();
429: }
430:
431: documentNode.setSize(documentNode.getWidth(), height);
432: }
433:
434: // JAVADOC COMMENT ELIDED
435: public int getViewportWidth() {
436: return documentNode.getWidth();
437: }
438:
439: // JAVADOC COMMENT ELIDED
440: public int getViewportHeight() {
441: return documentNode.getHeight();
442: }
443:
444: /**
445: *
446: */
447: public void requestCompleted(String uri, InputStream resourceData)
448: throws IOException {
449: System.err.println(">>>>> requestCompleted : " + uri + " / "
450: + resourceData);
451: if (uri == null) {
452: throw new NullPointerException();
453: }
454:
455: synchronized (this ) {
456: // set in getImageAndWait()
457: boolean isWaitURI = waitURI.equals(uri);
458:
459: RasterImage img;
460:
461: // null resourceData...
462: if (resourceData == null) {
463: img = svgImageLoader.getBrokenImage();
464: isBrokenImage = true;
465: } else {
466: // we got a fresh, new image...
467: ByteArrayOutputStream bos = new ByteArrayOutputStream(
468: DEFAULT_IMAGE_READ_BUFFER_LENGTH);
469: byte[] ib = new byte[DEFAULT_IMAGE_READ_BUFFER_LENGTH];
470: int byteRead = -1;
471: int totalByteRead = 0;
472: while ((byteRead = resourceData.read(ib, 0, ib.length)) != -1) {
473: bos.write(ib, 0, byteRead);
474: totalByteRead += byteRead;
475: }
476:
477: img = svgImageLoader.loaderUtil.createImage(bos
478: .toByteArray());
479: }
480:
481: // the new image is added to cache...
482: svgImageLoader.addToCache(uri, img);
483:
484: svgImageLoader.setRasterImageConsumerImage(uri, img);
485:
486: // request was initiated by getImageAndWait
487: if (isWaitURI) {
488: notifyAll();
489: }
490: }
491: };
492:
493: synchronized RasterImage waitOnRequestCompleted(final String uri) {
494: waitURI = uri;
495:
496: try {
497: while (isBrokenImage == false
498: && svgImageLoader.getImageFromCache(uri) == null) {
499: wait();
500: }
501: } catch (InterruptedException ie) {
502: return svgImageLoader.getBrokenImage();
503: }
504:
505: waitURI = new String();
506: if (isBrokenImage) {
507: isBrokenImage = false;
508: return svgImageLoader.getBrokenImage();
509: } else {
510: return svgImageLoader.getImageFromCache(uri);
511: }
512: }
513: }
|