001: /*
002: * $RCSfile: RMIImageImpl.java,v $
003: *
004: * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Use is subject to license terms.
007: *
008: * $Revision: 1.1 $
009: * $Date: 2005/02/11 04:56:52 $
010: * $State: Exp $
011: */
012: package com.sun.media.jai.rmi;
013:
014: import java.awt.Rectangle;
015: import java.awt.RenderingHints;
016: import java.awt.geom.Rectangle2D;
017: import java.awt.image.ColorModel;
018: import java.awt.image.SampleModel;
019: import java.awt.image.Raster;
020: import java.awt.image.RenderedImage;
021: import java.awt.image.WritableRaster;
022: import java.awt.image.renderable.RenderContext;
023: import java.io.Serializable;
024: import java.net.InetAddress;
025: import java.rmi.Naming;
026: import java.rmi.Remote;
027: import java.rmi.RemoteException;
028: import java.rmi.RMISecurityManager;
029: import java.rmi.server.UnicastRemoteObject;
030: import java.util.Hashtable;
031: import java.util.Vector;
032: import javax.media.jai.PlanarImage;
033: import javax.media.jai.PropertySource;
034: import javax.media.jai.RenderableOp;
035: import javax.media.jai.RenderedOp;
036: import javax.media.jai.remote.SerializableRenderedImage;
037: import javax.media.jai.remote.RemoteImagingException;
038: import javax.media.jai.util.ImagingException;
039: import javax.media.jai.util.ImagingListener;
040: import com.sun.media.jai.util.ImageUtil;
041:
042: /* A singleton class representing the serializable version of a null
043: property. This required because java.awt.Image.UndefinedProperty
044: is not serializable. */
045: class NullPropertyTag implements Serializable {
046: NullPropertyTag() {
047: }
048: }
049:
050: /**
051: * The server-side implementation of the RMIImage interface. A
052: * RMIImageImpl has a RenderedImage source, acquired via one of three
053: * setSource() methods. The first takes a RenderedImage directly as
054: * its parameter; this image is simply copied over the network using
055: * the normal RMI mechanisms. Note that not every image can be
056: * transferred in this way -- for example, attempting to pass an
057: * OpImage that uses native code or that depends on the availability
058: * of a class not resident on the server as a parameter will cause an
059: * exception to be thrown.
060: *
061: * <p> The second and third ways of setting sources make use of the
062: * RenderedOp and RenderableOp classes to send a high-level
063: * description of an image chain based on operation names. This
064: * chain will be copied over to the server using RMI, where it will be
065: * expanded into an OpImage chain using the server's registry. This
066: * is the preferred method since it requires less data transfer and
067: * offers a better chance of success. It may still fail if the
068: * sources or parameters of any operation in the chain are not
069: * serializable.
070: *
071: * <p> RMI requires all remote methods to declare `throws
072: * RemoteException' in their signatures. It is up to the client to
073: * deal with errors. A simple implementation of error handling may be
074: * found in the RemoteRenderedImage class.
075: *
076: * <p> This class contains a main() method that should be run on the
077: * server after starting the RMI registry. The registry will then
078: * construct new instances of RMIImageImpl on demand.
079: *
080: * @see RMIImage
081: * @see RemoteImage
082: * @see RenderedOp
083: *
084: * @since EA3
085: *
086: */
087: public class RMIImageImpl implements RMIImage {
088: /** Tag to represent a null property. */
089: public static final Object NULL_PROPERTY = new NullPropertyTag();
090:
091: /** Identifier counter for the remote images. */
092: private static long idCounter = 0;
093:
094: /**
095: * The RenderedImage sources hashed by an ID string which must be unique
096: * across all possible clients of this object.
097: */
098: private static Hashtable sources = null;
099:
100: /**
101: * The PropertySources hashed by an ID string which must be unique
102: * across all possible clients of this object.
103: */
104: private static Hashtable propertySources = null;
105:
106: /**
107: * Adds a RenderedImage source to the Hashtable of sources.
108: *
109: * @param id A unique ID for the source.
110: * @param source The source RenderedImage.
111: * @param ps The PropertySource.
112: */
113: private static synchronized void addSource(Long id,
114: RenderedImage source, PropertySource ps) {
115: // Create the Hashtables "just in time".
116: if (sources == null) {
117: sources = new Hashtable();
118: propertySources = new Hashtable();
119: }
120:
121: // Add the source and PropertySource.
122: sources.put(id, source);
123: propertySources.put(id, ps);
124: }
125:
126: /**
127: * Retrieve a PlanarImage source from the Hashtable of sources.
128: *
129: * @param id The unique ID of the source.
130: * @return The source.
131: */
132: private static PlanarImage getSource(Long id)
133: throws RemoteException {
134: Object obj = null;
135: if (sources == null || (obj = sources.get(id)) == null) {
136: throw new RemoteException(JaiI18N
137: .getString("RMIImageImpl2"));
138: }
139:
140: return (PlanarImage) obj;
141: }
142:
143: /**
144: * Retrieve a PropertySource from the Hashtable of PropertySources.
145: *
146: * @param id The unique ID of the source.
147: * @return The PropertySource.
148: */
149: private static PropertySource getPropertySource(Long id)
150: throws RemoteException {
151: Object obj = null;
152: if (propertySources == null
153: || (obj = propertySources.get(id)) == null) {
154: throw new RemoteException(JaiI18N
155: .getString("RMIImageImpl2"));
156: }
157:
158: return (PropertySource) obj;
159: }
160:
161: /**
162: * Constructs a RMIImageImpl with a source to be specified
163: * later.
164: */
165: public RMIImageImpl() throws RemoteException {
166: super ();
167: try {
168: UnicastRemoteObject.exportObject(this );
169: } catch (RemoteException e) {
170: ImagingListener listener = ImageUtil
171: .getImagingListener((RenderingHints) null);
172: String message = JaiI18N.getString("RMIImageImpl0");
173: listener.errorOccurred(message, new RemoteImagingException(
174: message, e), this , false);
175: /*
176: e.printStackTrace();
177: throw new RuntimeException(JaiI18N.getString("RMIImageImpl0") +
178: e.getMessage());
179: */
180: }
181: }
182:
183: /**
184: * Returns the identifier of the remote image. This method should be
185: * called to return an identifier before any other methods are invoked.
186: * The same ID must be used in all subsequent references to the remote
187: * image.
188: */
189: public synchronized Long getRemoteID() throws RemoteException {
190: return new Long(++idCounter);
191: }
192:
193: /**
194: * Sets the source of the image on the server side. This source
195: * should ideally be a lightweight reference to an image available
196: * locally on the server or over a further network link (for
197: * example, an IIPOpImage that contains a URL but not actual image
198: * data).
199: *
200: * <p> Although it is legal to use any RenderedImage, one should be
201: * aware that a deep copy might be made and transmitted to the server.
202: *
203: * @param id An ID for the source which must be unique across all clients.
204: * @param source a RenderedImage source.
205: */
206: public void setSource(Long id, RenderedImage source)
207: throws RemoteException {
208: PlanarImage pi = PlanarImage.wrapRenderedImage(source);
209: addSource(id, pi, pi);
210: }
211:
212: /**
213: * Sets the source to a RenderedOp (i.e., an imaging DAG).
214: * This DAG will be copied over to the server where it will be
215: * transformed into an OpImage chain using the server's local
216: * OperationRegistry and available RenderedImageFactory objects.
217: *
218: * @param id An ID for the source which must be unique across all clients.
219: * @param source a RenderedOp source.
220: */
221: public void setSource(Long id, RenderedOp source)
222: throws RemoteException {
223: addSource(id, source.getRendering(), source);
224: }
225:
226: /**
227: * Sets the source to a RenderableOp defined by a renderable imaging
228: * DAG and a rendering context. The entire RenderableImage
229: * DAG will be copied over to the server.
230: */
231: public void setSource(Long id, RenderableOp source,
232: RenderContextProxy renderContextProxy)
233: throws RemoteException {
234: RenderContext renderContext = renderContextProxy
235: .getRenderContext();
236: RenderedImage r = source.createRendering(renderContext);
237: PlanarImage pi = PlanarImage.wrapRenderedImage(r);
238: addSource(id, pi, pi);
239: }
240:
241: /**
242: * Disposes of any resouces allocated to the client object with
243: * the specified ID.
244: */
245: public void dispose(Long id) throws RemoteException {
246: if (sources != null) {
247: sources.remove(id);
248: propertySources.remove(id);
249: }
250: }
251:
252: /** Gets a property from the property set of this image. If the
253: property is undefined the constant NULL_PROPERTY is returned. */
254: public Object getProperty(Long id, String name)
255: throws RemoteException {
256: PropertySource ps = getPropertySource(id);
257: Object property = ps.getProperty(name);
258: if (property == null
259: || property.equals(java.awt.Image.UndefinedProperty)) {
260: property = NULL_PROPERTY;
261: }
262: return property;
263: }
264:
265: /**
266: * Returns a list of names recognized by getProperty().
267: *
268: * @return an array of Strings representing proeprty names.
269: */
270: public String[] getPropertyNames(Long id) throws RemoteException {
271: PropertySource ps = getPropertySource(id);
272: return ps.getPropertyNames();
273: }
274:
275: /** Returns the minimum X coordinate of the RMIImage. */
276: public int getMinX(Long id) throws RemoteException {
277: return getSource(id).getMinX();
278: }
279:
280: /** Returns the smallest X coordinate to the right of the RMIImage. */
281: public int getMaxX(Long id) throws RemoteException {
282: return getSource(id).getMaxX();
283: }
284:
285: /** Returns the minimum Y coordinate of the RMIImage. */
286: public int getMinY(Long id) throws RemoteException {
287: return getSource(id).getMinY();
288: }
289:
290: /** Returns the smallest Y coordinate below the RMIImage. */
291: public int getMaxY(Long id) throws RemoteException {
292: return getSource(id).getMaxY();
293: }
294:
295: /** Returns the width of the RMIImage. */
296: public int getWidth(Long id) throws RemoteException {
297: return getSource(id).getWidth();
298: }
299:
300: /** Returns the height of the RMIImage. */
301: public int getHeight(Long id) throws RemoteException {
302: return getSource(id).getHeight();
303: }
304:
305: /** Returns the width of a tile in pixels. */
306: public int getTileWidth(Long id) throws RemoteException {
307: return getSource(id).getTileWidth();
308: }
309:
310: /** Returns the height of a tile in pixels. */
311: public int getTileHeight(Long id) throws RemoteException {
312: return getSource(id).getTileHeight();
313: }
314:
315: /**
316: * Returns the X coordinate of the upper-left pixel of tile (0, 0).
317: */
318: public int getTileGridXOffset(Long id) throws RemoteException {
319: return getSource(id).getTileGridXOffset();
320: }
321:
322: /**
323: * Returns the Y coordinate of the upper-left pixel of tile (0, 0).
324: */
325: public int getTileGridYOffset(Long id) throws RemoteException {
326: return getSource(id).getTileGridYOffset();
327: }
328:
329: /** Returns the index of the leftmost column of tiles. */
330: public int getMinTileX(Long id) throws RemoteException {
331: return getSource(id).getMinTileX();
332: }
333:
334: /**
335: * Returns the number of tiles along the tile grid in the horizontal
336: * direction.
337: */
338: public int getNumXTiles(Long id) throws RemoteException {
339: return getSource(id).getNumXTiles();
340: }
341:
342: /** Returns the index of the uppermost row of tiles. */
343: public int getMinTileY(Long id) throws RemoteException {
344: return getSource(id).getMinTileY();
345: }
346:
347: /**
348: * Returns the number of tiles along the tile grid in the vertical
349: * direction.
350: */
351: public int getNumYTiles(Long id) throws RemoteException {
352: return getSource(id).getNumYTiles();
353: }
354:
355: /** Returns the index of the rightmost column of tiles. */
356: public int getMaxTileX(Long id) throws RemoteException {
357: return getSource(id).getMaxTileX();
358: }
359:
360: /** Returns the index of the bottom row of tiles. */
361: public int getMaxTileY(Long id) throws RemoteException {
362: return getSource(id).getMaxTileY();
363: }
364:
365: /** Returns the SampleModel associated with this image. */
366: public SampleModelProxy getSampleModel(Long id)
367: throws RemoteException {
368: return new SampleModelProxy(getSource(id).getSampleModel());
369: }
370:
371: /** Returns the ColorModel associated with this image. */
372: public ColorModelProxy getColorModel(Long id)
373: throws RemoteException {
374: return new ColorModelProxy(getSource(id).getColorModel());
375:
376: }
377:
378: /**
379: * Returns a vector of RenderedImages that are the sources of
380: * image data for this RMIImage. Note that this method
381: * will often return an empty vector.
382: */
383: public Vector getSources(Long id) throws RemoteException {
384: Vector sourceVector = getSource(id).getSources();
385: int size = sourceVector.size();
386: boolean isCloned = false;
387: for (int i = 0; i < size; i++) {
388: RenderedImage img = (RenderedImage) sourceVector.get(i);
389: if (!(img instanceof Serializable)) {
390: if (!isCloned) {
391: sourceVector = (Vector) sourceVector.clone();
392: }
393: sourceVector.set(i, new SerializableRenderedImage(img,
394: false));
395: }
396: }
397: return sourceVector;
398: }
399:
400: /** Returns a Rectangle indicating the image bounds. */
401: public Rectangle getBounds(Long id) throws RemoteException {
402: return getSource(id).getBounds();
403: }
404:
405: /**
406: * Returns tile (x, y). Note that x and y are indices into the
407: * tile array, not pixel locations. Unlike in the true RenderedImage
408: * interface, the Raster that is returned should be considered a copy.
409: *
410: * @param id An ID for the source which must be unique across all clients.
411: * @param tileX the X index of the requested tile in the tile array.
412: * @param tileY the Y index of the requested tile in the tile array.
413: * @return the tile as a Raster.
414: */
415: public RasterProxy getTile(Long id, int tileX, int tileY)
416: throws RemoteException {
417: return new RasterProxy(getSource(id).getTile(tileX, tileY));
418: }
419:
420: /**
421: * Returns the entire image as a single Raster.
422: *
423: * @return a Raster containing a copy of this image's data.
424: */
425: public RasterProxy getData(Long id) throws RemoteException {
426: return new RasterProxy(getSource(id).getData());
427: }
428:
429: /**
430: * Returns an arbitrary rectangular region of the RenderedImage
431: * in a Raster. The rectangle of interest will be clipped against
432: * the image bounds.
433: *
434: * @param id An ID for the source which must be unique across all clients.
435: * @param rect the region of the RenderedImage to be returned.
436: * @return a Raster containing a copy of the desired data.
437: */
438: public RasterProxy getData(Long id, Rectangle bounds)
439: throws RemoteException {
440: RasterProxy rp = null;
441: if (bounds == null) {
442: rp = getData(id);
443: } else {
444: bounds = bounds.intersection(getBounds(id));
445: rp = new RasterProxy(getSource(id).getData(bounds));
446: }
447: return rp;
448: }
449:
450: /**
451: * Returns the same result as getData(Rectangle) would for the
452: * same rectangular region.
453: */
454: public RasterProxy copyData(Long id, Rectangle bounds)
455: throws RemoteException {
456: return getData(id, bounds);
457: }
458:
459: /**
460: * Starts a server on a given port. The RMI registry must be running
461: * on the server host.
462: *
463: * <p> The usage of this class is
464: *
465: * <pre>
466: * java -Djava.rmi.server.codebase=file:$JAI/lib/jai.jar \
467: * -Djava.rmi.server.useCodebaseOnly=false \
468: * -Djava.security.policy=\
469: * file:`pwd`/policy com.sun.media.jai.rmi.RMIImageImpl \
470: * [-host hostName] [-port portNumber]
471: * </pre>
472: *
473: * The default host is the local host and the default port is 1099.
474: *
475: * @param args the port number as a command-line argument.
476: */
477: public static void main(String[] args) {
478: // Set the security manager.
479: if (System.getSecurityManager() == null) {
480: System.setSecurityManager(new RMISecurityManager());
481: }
482:
483: // Set the host name and port number.
484: String host = null;
485: int port = 1099; // default port is 1099
486: for (int i = 0; i < args.length; i++) {
487: if (args[i].equalsIgnoreCase("-host")) {
488: host = args[++i];
489: } else if (args[i].equalsIgnoreCase("-port")) {
490: port = Integer.parseInt(args[++i]);
491: }
492: }
493:
494: // Default to the local host if the host was not specified.
495: if (host == null) {
496: try {
497: host = InetAddress.getLocalHost().getHostAddress();
498: } catch (java.net.UnknownHostException e) {
499: System.err.println(JaiI18N.getString("RMIImageImpl1")
500: + e.getMessage());
501: e.printStackTrace();
502: }
503: }
504:
505: System.out.println(JaiI18N.getString("RMIImageImpl3") + " "
506: + host + ":" + port);
507:
508: try {
509: RMIImageImpl im = new RMIImageImpl();
510: String serverName = new String("rmi://" + host + ":" + port
511: + "/" + RMIImage.RMI_IMAGE_SERVER_NAME);
512: System.out.println(JaiI18N.getString("RMIImageImpl4")
513: + " \"" + serverName + "\".");
514: Naming.rebind(serverName, im);
515: System.out.println(JaiI18N.getString("RMIImageImpl5"));
516: } catch (Exception e) {
517: System.err.println(JaiI18N.getString("RMIImageImpl0")
518: + e.getMessage());
519: e.printStackTrace();
520: }
521: }
522: }
|