1 /*
2  * $RCSfile: RemoteRenderedOp.java,v $
3  *
4  * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
5  *
6  * Use is subject to license terms.
7  *
8  * $Revision: 1.2 $
9  * $Date: 2006/06/16 22:52:05 $
10  * $State: Exp $
11  */package com.lightcrafts.mediax.jai.remote;
12 
13 import java.awt.Point;
14 import java.awt.Rectangle;
15 import java.awt.RenderingHints;
16 import java.awt.Shape;
17 import java.awt.geom.Area;
18 import java.awt.geom.GeneralPath;
19 import java.awt.image.Raster;
20 import java.awt.image.RenderedImage;
21 import java.awt.image.renderable.ParameterBlock;
22 import java.beans.PropertyChangeEvent;
23 import java.beans.PropertyChangeListener;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.HashSet;
27 import java.util.Locale;
28 import java.util.Set;
29 import java.util.Vector;
30 import java.text.MessageFormat;
31 import com.lightcrafts.mediax.jai.CollectionChangeEvent;
32 import com.lightcrafts.mediax.jai.CollectionOp;
33 import com.lightcrafts.mediax.jai.JAI;
34 import com.lightcrafts.mediax.jai.OperationRegistry;
35 import com.lightcrafts.mediax.jai.PlanarImage;
36 import com.lightcrafts.mediax.jai.PropertyChangeEventJAI;
37 import com.lightcrafts.mediax.jai.PropertySourceChangeEvent;
38 import com.lightcrafts.mediax.jai.RegistryMode;
39 import com.lightcrafts.mediax.jai.RenderedOp;
40 import com.lightcrafts.mediax.jai.RenderingChangeEvent;
41 import com.lightcrafts.mediax.jai.TileCache;
42 import com.lightcrafts.mediax.jai.registry.RemoteRIFRegistry;
43 import com.lightcrafts.mediax.jai.util.ImagingException;
44 import com.lightcrafts.mediax.jai.util.ImagingListener;
45 import com.lightcrafts.media.jai.util.ImageUtil;
46 
47 /**
48  * A node in a remote rendered imaging chain.  This class is a concrete
49  * implementation of the <code>RemoteRenderedImage</code> interface. A
50  * <code>RemoteRenderedOp</code> stores a protocol name (as a
51  * <code>String</code>), a server name (as a <code>String</code>), an
52  * operation name (as a <code>String</code>), a
53  * <code>ParameterBlock</code> containing sources and miscellaneous
54  * parameters, and a <code>RenderingHints</code> containing rendering
55  * hints.  A set of nodes may be joined together via the source
56  * <code>Vector</code>s within their <code>ParameterBlock</code>s to
57  * form a <u>d</u>irected <u>a</u>cyclic <u>g</u>raph (DAG). The topology
58  * i.e., connectivity of the graph may be altered by changing the
59  * <code>ParameterBlock</code>s; the operation name, parameters, and
60  * rendering hints may also be changed.
61  *
62  * <p> Such chains represent and handle operations that are being
63  * performed remotely. They convey the structure of an imaging
64  * chain in a compact representation and can be used to influence the
65  * remote imaging process (through the use of retry interval, retries and
66  * negotiation preferences).
67  *
68  * <p> <code>RemoteRenderedOp</code>s are a client side representation of
69  * the chain of operations taking place on the server.
70  *
71  * <p> The translation between <code>RemoteRenderedOp</code> chains and
72  * <code>RemoteRenderedImage</code> (usually
73  * <code>PlanarImageServerProxy</code>) chains makes use of two levels of
74  * indirection provided by the <code>OperationRegistry</code> and
75  * <code>RemoteRIF</code> facilities.  First, the
76  * local <code>OperationRegistry</code> is used to map the protocol
77  * name into a <code>RemoteRIF</code>.  This <code>RemoteRIF</code> then
78  * constructs one or more <code>RemoteRenderedImage</code>s (usually
79  * <code>PlanarImageServerProxy</code>s) to do the actual work (or
80  * returns a <code>RemoteRenderedImage</code> by other means. The
81  * <code>OperationRegistry</code> maps a protocol name into a
82  * <code>RemoteRIF</code>, since there is one to one correspondence
83  * between a protocol name and a <code>RemoteRIF</code>. This differs from
84  * the case of <code>RenderedOp</code>s, where the
85  * <code>OperationRegistry</code> maps each operation name to a
86  * <code>RenderedImageFactory</code> (RIF), since there is a one to one
87  * correspondence between an operation name and a RIF. The
88  * <code>RemoteRIF</code>s are therefore protocol-specific and not operation
89  * specific, while a RIF is operation specific.
90  *
91  * <p> Once a protocol name has been mapped into a <code>RemoteRIF</code>,
92  * the <code>RemoteRIF.create()</code> method is used to create a rendering.
93  * This rendering is responsible for communicating with the server to
94  * perform the specified operation remotely.
95  *
96  * <p> By virtue of being a subclass of <code>RenderedOp</code>, this class
97  * participates in Java Bean-style events as specified by
98  * <code>RenderedOp</code>. This means that <code>PropertyChangeEmitter</code>
99  * methods may be used to register and unregister
100  * <code>PropertyChangeListener</code>s. <code>RemoteRenderedOp</code>s
101  * are also <code>PropertyChangeListener</code>s so that they may be
102  * registered as listeners of other <code>PropertyChangeEmitter</code>s
103  * or the equivalent. Each <code>RemoteRenderedOp</code> also automatically
104  * receives any <code>RenderingChangeEvent</code>s emitted by any of its
105  * sources which are <code>RenderedOp</code>s.
106  *
107  * <p> <code>RemoteRenderedOp</code>s add the server name and the protocol
108  * name to the critical attributes, the editing (changing) of which,
109  * coupled with a difference in the old and new rendering over some
110  * non-empty region, may cause a <code>RenderingChangeEvent</code> to
111  * be emitted. As with <code>RenderedOp</code>, editing of a critical
112  * attribute of a <code>RemoteRenderedOp</code> will cause a
113  * <code>PropertyChangeEventJAI</code> detailing the change to be fired
114  * to all registered <code>PropertyChangeListener</code>s.
115  * <code>RemoteRenderedOp</code> registers itself as a
116  * <code>PropertyChangeListener</code> for all critical attributes, and
117  * thus receives all <code>PropertyChangeEventJAI</code> events generated
118  * by itself. This is done in order to allow the event handling code
119  * to generate a new rendering and reuse any tiles that might be valid
120  * after the critical argument change.
121  *
122  * <p> When a <code>RemoteRenderedOp</code> node receives a
123  * <code>PropertyChangeEventJAI</code> from itself, the region of
124  * the current rendering which is invalidated is computed using
125  * <code>RemoteDescriptor.getInvalidRegion()</code>. When a
126  * <code>RemoteRenderedOp</code> node receives a
127  * <code>RenderingChangeEvent</code> from one of its sources, the region of
128  * the current rendering which is invalidated is computed using
129  * the <code>mapSourceRect()</code> method of the current rendering and
130  * the invalid region of the source (retrieved using
131  * <code>RenderingChangeEvent.getInvalidRegion()</code>)
132  * If the complement of the invalid region contains any tiles of the
133  * current rendering, a new rendering of the node will be generated using
134  * the new source node and its rendering generated using that version of
135  * <code>RemoteRIF.create</code>() that updates the rendering of the node
136  * according to the specified <code>PropertyChangeEventJAI</code>. The
137  * identified tiles will be retained from the old rendering insofar as
138  * possible.  This might involve for example adding tiles to a
139  * <code>TileCache</code> under the ownership of the new rendering.
140  * A <code>RenderingChangeEvent</code> will then be fired to all
141  * <code>PropertyChangeListener</code>s of the node, and to any node sinks
142  * that are <code>PropertyChangeListener</code>s. The
143  * <code>newRendering</code> parameter of the event constructor
144  * (which may be retrieved via the <code>getNewValue()</code> method of
145  * the event) will be set to either the new rendering of the node or to
146  * <code>null</code> if it was not possible to retain any tiles of the
147  * previous rendering.
148  *
149  * @see RenderedOp
150  * @see RemoteRenderedImage
151  *
152  * @since JAI 1.1
153  */
154 public class RemoteRenderedOp extends RenderedOp
155     implements RemoteRenderedImage {
156 
157     /** The name of the protocol this class provides an implementation for. */
158     protected String protocolName;
159 
160     /** The name of the server. */
161     protected String serverName;
162 
163     // The NegotiableCapabilitySet representing the negotiated values.
164     private NegotiableCapabilitySet negotiated;
165 
166     /**
167      * The RenderingHints when the node was last rendered, i.e., when
168      * "theImage" was set to its current value.
169      */
170     private transient RenderingHints oldHints;
171 
172     /** Node event names. */
173     private static Set nodeEventNames = null;
174 
175     static {
176 	nodeEventNames = new HashSet();
177         nodeEventNames.add("protocolname");
178         nodeEventNames.add("servername");
179         nodeEventNames.add("protocolandservername");
180         nodeEventNames.add("operationname");
181         nodeEventNames.add("operationregistry");
182         nodeEventNames.add("parameterblock");
183         nodeEventNames.add("sources");
184         nodeEventNames.add("parameters");
185         nodeEventNames.add("renderinghints");
186     }
187 
188     /**
189      * Constructs a <code>RemoteRenderedOp</code> that will be used to
190      * instantiate a particular rendered operation to be performed remotely
191      * using the default operation registry, the name of the remote imaging
192      * protocol, the name of the server to perform the operation on, an
193      * operation name, a <code>ParameterBlock</code>, and a set of
194      * rendering hints.  All input parameters are saved by reference.
195      *
196      * <p> An <code>IllegalArgumentException</code> may
197      * be thrown by the protocol specific classes at a later point, if
198      * null is provided as the serverName argument and null is not
199      * considered a valid server name by the specified protocol.
200      *
201      * <p> The <code>RenderingHints</code> may contain negotiation
202      * preferences specified under the <code>KEY_NEGOTIATION_PREFERENCES</code>
203      * key.
204      *
205      * @param protocolName The protocol name as a String.
206      * @param serverName   The server name as a String.
207      * @param opName       The operation name.
208      * @param pb           The sources and parameters. If <code>null</code>,
209      *                     it is assumed that this node has no sources and
210      *                     parameters.
211      * @param hints        The rendering hints.  If <code>null</code>, it is
212      *                     assumed that no hints are associated with the
213      *                     rendering.
214      *
215      * @throws IllegalArgumentException if <code>protocolName</code> is
216      * <code>null</code>.
217      * @throws IllegalArgumentException if <code>opName</code> is
218      * <code>null</code>.
219      */
RemoteRenderedOp(String protocolName, String serverName, String opName, ParameterBlock pb, RenderingHints hints)220     public RemoteRenderedOp(String protocolName,
221 			    String serverName,
222 			    String opName,
223 			    ParameterBlock pb,
224 			    RenderingHints hints) {
225 	this(null, protocolName, serverName, opName, pb, hints);
226     }
227 
228     /**
229      * Constructs a <code>RemoteRenderedOp</code> that will be used to
230      * instantiate a particular rendered operation to be performed remotely
231      * using the specified operation registry, the name of the remote imaging
232      * protocol, the name of the server to perform the operation on, an
233      * operation name, a <code>ParameterBlock</code>, and a set of
234      * rendering hints.  All input parameters are saved by reference.
235      *
236      * <p> An <code>IllegalArgumentException</code> may
237      * be thrown by the protocol specific classes at a later point, if
238      * null is provided as the serverName argument and null is not
239      * considered a valid server name by the specified protocol.
240      *
241      * <p> The <code>RenderingHints</code> may contain negotiation
242      * preferences specified under the <code>KEY_NEGOTIATION_PREFERENCES</code>
243      * key.
244      *
245      * @param registry     The <code>OperationRegistry</code> to be used for
246      *                     instantiation.  if <code>null</code>, the default
247      *                     registry is used.
248      * @param protocolName The protocol name as a String.
249      * @param serverName   The server name as a String.
250      * @param opName       The operation name.
251      * @param pb           The sources and parameters. If <code>null</code>,
252      *                     it is assumed that this node has no sources and
253      *                     parameters.
254      * @param hints        The rendering hints.  If <code>null</code>, it is
255      *                     assumed that no hints are associated with the
256      *                     rendering.
257      *
258      * @throws IllegalArgumentException if <code>protocolName</code> is
259      * <code>null</code>.
260      * @throws IllegalArgumentException if <code>opName</code> is
261      * <code>null</code>.
262      */
RemoteRenderedOp(OperationRegistry registry, String protocolName, String serverName, String opName, ParameterBlock pb, RenderingHints hints)263     public RemoteRenderedOp(OperationRegistry registry,
264 			    String protocolName,
265 			    String serverName,
266 			    String opName,
267 			    ParameterBlock pb,
268 			    RenderingHints hints) {
269 
270 	// This will throw IAE for opName if null
271 	super(registry, opName, pb, hints);
272 
273 	if (protocolName == null)
274 	    throw new IllegalArgumentException(JaiI18N.getString("Generic1"));
275 
276 	this.protocolName = protocolName;
277 	this.serverName = serverName;
278 
279 	// Add the node as a PropertyChangeListener of itself for
280         // the critical attributes of the node. Superclass RenderedOp
281 	// takes care of all critical attributes except the following.
282 	// Case is ignored in the property names but infix caps are
283 	// used here anyway.
284 	addPropertyChangeListener("ServerName", this);
285         addPropertyChangeListener("ProtocolName", this);
286         addPropertyChangeListener("ProtocolAndServerName", this);
287     }
288 
289     /**
290      * Returns the <code>String</code> that identifies the server.
291      */
getServerName()292     public String getServerName() {
293 	return serverName;
294     }
295 
296     /**
297      * Sets a <code>String</code> identifying the server.
298      *
299      * <p> If the supplied name does not equal the current server name, a
300      * <code>PropertyChangeEventJAI</code> named "ServerName"
301      * will be fired and a <code>RenderingChangeEvent</code> may be
302      * fired if the node has already been rendered. The oldValue
303      * field in the <code>PropertyChangeEventJAI</code> will contain
304      * the old server name <code>String</code> and the newValue
305      * field will contain the new server name <code>String</code>.
306      *
307      * @param serverName A <code>String</code> identifying the server.
308      * @throws IllegalArgumentException if serverName is null.
309      */
setServerName(String serverName)310     public void setServerName(String serverName) {
311 
312 	if (serverName == null)
313 	    throw new IllegalArgumentException(JaiI18N.getString("Generic2"));
314 
315 	if (serverName.equalsIgnoreCase(this.serverName)) return;
316 
317 	String oldServerName = this.serverName;
318 	this.serverName = serverName;
319 	fireEvent("ServerName", oldServerName, serverName);
320         nodeSupport.resetPropertyEnvironment(false);
321     }
322 
323     /**
324      * Returns the <code>String</code> that identifies the remote imaging
325      * protocol.
326      */
getProtocolName()327     public String getProtocolName() {
328 	return protocolName;
329     }
330 
331     /**
332      * Sets a <code>String</code> identifying the remote imaging protocol.
333      * This method causes this <code>RemoteRenderedOp</code> to use
334      * the new protocol name with the server name set on this node
335      * previously. If the server is not compliant with the new
336      * protocol name, the <code>setProtocolAndServerNames()</code>
337      * method should be used to set a new protocol name and a compliant
338      * new server name at the same time.
339      *
340      * <p> If the supplied name does not equal the current protocol name, a
341      * <code>PropertyChangeEventJAI</code> named "ProtocolName"
342      * will be fired and a <code>RenderingChangeEvent</code> may be
343      * fired if the node has already been rendered. The oldValue
344      * field in the <code>PropertyChangeEventJAI</code> will contain
345      * the old protocol name <code>String</code> and the newValue
346      * field will contain the new protocol name <code>String</code>.
347      *
348      * @param protocolName A <code>String</code> identifying the server.
349      * @throws IllegalArgumentException if protocolName is null.
350      */
setProtocolName(String protocolName)351     public void setProtocolName(String protocolName) {
352 
353 	if (protocolName == null)
354 	    throw new IllegalArgumentException(JaiI18N.getString("Generic1"));
355 
356 	if (protocolName.equalsIgnoreCase(this.protocolName)) return;
357 
358 	String oldProtocolName = this.protocolName;
359 	this.protocolName = protocolName;
360 	fireEvent("ProtocolName", oldProtocolName, protocolName);
361         nodeSupport.resetPropertyEnvironment(false);
362     }
363 
364     /**
365      * Sets the protocol name and the server name of this
366      * <code>RemoteRenderedOp</code> to the specified arguments..
367      *
368      * <p> If both the supplied protocol name and the supplied server
369      * name values do not equal the current values, a
370      * <code>PropertyChangeEventJAI</code> named "ProtocolAndServerName"
371      * will be fired. The oldValue field in the
372      * <code>PropertyChangeEventJAI</code> will contain a two element
373      * array of <code>String</code>s, the old protocol name being the
374      * first element and the old server name being the second. Similarly
375      * the newValue field of the <code>PropertyChangeEventJAI</code> will
376      * contain a two element array of <code>String</code>s, the new protocol
377      * name being the first element and the new server name being the
378      * second. If only the supplied protocol name does not equal
379      * the current protocol name, a <code>PropertyChangeEventJAI</code>
380      * named "ProtocolName" will be fired. If only the supplied server
381      * name does not equal the current server name, a
382      * <code>PropertyChangeEventJAI</code> named "ServerName"
383      * will be fired.
384      *
385      * @param protocolName A <code>String</code> identifying the protocol.
386      * @param serverName A <code>String</code> identifying the server.
387      * @throws IllegalArgumentException if protocolName is null.
388      * @throws IllegalArgumentException if serverName is null.
389      */
setProtocolAndServerNames(String protocolName, String serverName)390     public void setProtocolAndServerNames(String protocolName,
391 					  String serverName) {
392 
393 	if (serverName == null)
394 	    throw new IllegalArgumentException(JaiI18N.getString("Generic2"));
395 
396 	if (protocolName == null)
397 	    throw new IllegalArgumentException(JaiI18N.getString("Generic1"));
398 
399 	boolean protocolNotChanged =
400 	    protocolName.equalsIgnoreCase(this.protocolName);
401 	boolean serverNotChanged =
402 	    serverName.equalsIgnoreCase(this.serverName);
403 
404 	if (protocolNotChanged) {
405 	    if (serverNotChanged)
406 		// Neither changed
407 		return;
408 	    else {
409 		// Only serverName changed
410 		setServerName(serverName);
411 		return;
412 	    }
413 	} else {
414 	    if (serverNotChanged) {
415 		// Only protocolName changed
416 		setProtocolName(protocolName);
417 		return;
418 	    }
419 	}
420 
421 	String oldProtocolName = this.protocolName;
422 	String oldServerName = this.serverName;
423 	this.protocolName = protocolName;
424 	this.serverName = serverName;
425 
426 	// Both changed
427 	fireEvent("ProtocolAndServerName",
428 		  new String[] {oldProtocolName, oldServerName},
429 		  new String[] {protocolName, serverName});
430         nodeSupport.resetPropertyEnvironment(false);
431     }
432 
433     /**
434      * Returns the name of the <code>RegistryMode</code> corresponding to
435      * this <code>RemoteRenderedOp</code>.  This method overrides the
436      * implementation in <code>RenderedOp</code> to always returns the
437      * <code>String</code> "remoteRendered".
438      */
getRegistryModeName()439     public String getRegistryModeName() {
440         return RegistryMode.getMode("remoteRendered").getName();
441     }
442 
443     /**
444      * Overrides the <code>RenderedOp</code> method to allow the operation
445      * to be performed remotely.
446      */
createInstance(boolean isNodeRendered)447     protected synchronized PlanarImage createInstance(boolean isNodeRendered) {
448 
449         ParameterBlock pb = new ParameterBlock();
450         pb.setParameters(getParameters());
451 
452         int numSources = getNumSources();
453 
454         for (int i = 0; i < numSources; i++) {
455 
456             Object source = getNodeSource(i);
457             Object ai = null;
458 	    if (source instanceof RenderedOp) {
459 
460                 RenderedOp src = (RenderedOp)source;
461 		ai = isNodeRendered ?
462                     src.getRendering() :
463                     src.createInstance();
464 
465             } else if ((source instanceof RenderedImage) ||
466                        (source instanceof Collection)) {
467 
468                 ai = source;
469 	    } else if (source instanceof CollectionOp) {
470                 ai = ((CollectionOp)source).getCollection();
471             } else {
472                 /* Source is some other type. Pass on (for now). */
473                 ai = source;
474             }
475             pb.addSource(ai);
476         }
477 
478 	RemoteRenderedImage instance =
479 	    RemoteRIFRegistry.create(nodeSupport.getRegistry(),
480 				     protocolName,
481 				     serverName,
482 				     nodeSupport.getOperationName(),
483 				     pb,
484 				     nodeSupport.getRenderingHints());
485 
486         // Throw an exception if the rendering is null.
487         if (instance == null) {
488             throw new ImagingException(JaiI18N.getString("RemoteRenderedOp2"));
489         }
490 
491 	// Save the state of the node.
492 	RenderingHints rh = nodeSupport.getRenderingHints();
493         oldHints = rh == null ? null : (RenderingHints)rh.clone();
494 
495 	// Ensure that the rendering is a PlanarImage.
496         return PlanarImage.wrapRenderedImage(instance);
497     }
498 
499     /* ----- PropertyChangeListener method. ----- */
500 
501     /**
502      * Implementation of <code>PropertyChangeListener</code>.
503      *
504      * <p> When invoked with an event which is an instance of
505      * <code>RenderingChangeEvent</code> the node will respond by
506      * re-rendering itself while retaining any tiles possible.
507      */
508     // XXX Update javadoc both here and at class level.
propertyChange(PropertyChangeEvent evt)509     public synchronized void propertyChange(PropertyChangeEvent evt) {
510 
511         //
512         // React if and only if the node has been rendered and
513         // A: a non-PropertySourceChangeEvent PropertyChangeEventJAI
514         //    was received from this node, or
515         // B: a RenderingChangeEvent was received from a source node.
516         //
517 
518         // Cache event and node sources.
519         Object evtSrc = evt.getSource();
520         Vector nodeSources = nodeSupport.getParameterBlock().getSources();
521 
522         // Get the name of the bean property and convert it to lower
523         // case now for efficiency later.
524         String propName = evt.getPropertyName().toLowerCase(Locale.ENGLISH);
525 
526         if (theImage != null &&
527            ((evt instanceof PropertyChangeEventJAI &&
528              evtSrc == this &&
529              !(evt instanceof PropertySourceChangeEvent) &&
530              nodeEventNames.contains(propName)) ||
531             ((evt instanceof RenderingChangeEvent ||
532               evt instanceof CollectionChangeEvent ||
533               (evt instanceof PropertyChangeEventJAI &&
534                evtSrc instanceof RenderedImage &&
535                propName.equals("invalidregion"))) &&
536              nodeSources.contains(evtSrc)))) {
537 
538             // Save the previous rendering.
539             PlanarImage theOldImage = theImage;
540 
541             // Initialize the event flag.
542             boolean shouldFireEvent = false;
543 
544             // Set default invalid region to null (the entire image).
545             Shape invalidRegion = null;
546 
547             if (evtSrc == this &&
548                (propName.equals("operationregistry") ||
549 		propName.equals("protocolname") ||
550 		propName.equals("protocolandservername"))) {
551 
552                 // invalidate the entire rendering.
553                 shouldFireEvent = true;
554                 theImage = null;
555 
556             } else if (evt instanceof RenderingChangeEvent ||
557                       (evtSrc instanceof RenderedImage &&
558                        propName.equals("invalidregion"))) {
559 
560                 // Set the event flag.
561                 shouldFireEvent = true;
562                 Shape srcInvalidRegion = null;
563 
564                 if (evt instanceof RenderingChangeEvent) {
565 
566                     // RenderingChangeEvent presumably from a source
567 		    // RenderedOp.
568                     RenderingChangeEvent rcEvent = (RenderingChangeEvent)evt;
569 
570                     // Get the invalidated region of the source.
571                     srcInvalidRegion = rcEvent.getInvalidRegion();
572 
573                     // If entire source is invalid replace with source bounds.
574                     if (srcInvalidRegion == null) {
575                         srcInvalidRegion =
576                             ((PlanarImage)rcEvent.getOldValue()).getBounds();
577                     }
578                 } else {
579 
580                     // Get the invalidated region of the source.
581                     srcInvalidRegion = (Shape)evt.getNewValue();
582 
583                     // If entire source is invalid replace with source bounds.
584                     if (srcInvalidRegion == null) {
585                         RenderedImage rSrc = (RenderedImage)evtSrc;
586                         srcInvalidRegion =
587                             new Rectangle(rSrc.getMinX(), rSrc.getMinY(),
588                                           rSrc.getWidth(), rSrc.getHeight());
589                     }
590                 }
591 
592                 // Only process further if the rendering is a
593 		// PlanarImageServerProxy.
594                 if (!(theImage instanceof PlanarImageServerProxy)) {
595 
596                     // Clear the current rendering.
597                     theImage = null;
598 
599                 } else {
600 
601                     // Save the previous rendering as a PlanarImageServerProxy.
602                     PlanarImageServerProxy oldPISP =
603 			(PlanarImageServerProxy)theImage;
604 
605                     // Cache source invalid bounds.
606                     Rectangle srcInvalidBounds = srcInvalidRegion.getBounds();
607 
608                     // If bounds are empty, replace srcInvalidRegion with
609                     // the complement of the image bounds within the
610                     // bounds of all tiles.
611                     if (srcInvalidBounds.isEmpty()) {
612                         int x = oldPISP.tileXToX(oldPISP.getMinTileX());
613                         int y = oldPISP.tileYToY(oldPISP.getMinTileY());
614                         int w =
615 			    oldPISP.getNumXTiles() * oldPISP.getTileWidth();
616                         int h =
617 			    oldPISP.getNumYTiles() * oldPISP.getTileHeight();
618                         Rectangle tileBounds = new Rectangle(x, y, w, h);
619                         Rectangle imageBounds = oldPISP.getBounds();
620                         if (!tileBounds.equals(imageBounds)) {
621                             Area tmpArea = new Area(tileBounds);
622                             tmpArea.subtract(new Area(imageBounds));
623                             srcInvalidRegion = tmpArea;
624                             srcInvalidBounds = srcInvalidRegion.getBounds();
625                         }
626                     }
627 
628                     // ----- Determine invalid destination region. -----
629 
630                     boolean saveAllTiles = false;
631                     ArrayList validTiles = null;
632                     if (srcInvalidBounds.isEmpty()) {
633                         invalidRegion = srcInvalidRegion;
634                         saveAllTiles = true;
635 
636                     } else {
637 
638                         // Get index of source which changed.
639                         int idx = nodeSources.indexOf(evtSrc);
640 
641                         // Determine bounds of invalid destination region.
642                         Rectangle dstRegionBounds =
643                             oldPISP.mapSourceRect(srcInvalidBounds, idx);
644 
645                         if (dstRegionBounds == null) {
646                             dstRegionBounds = oldPISP.getBounds();
647                         }
648 
649                         // Determine invalid destination region.
650                         Point[] indices = getTileIndices(dstRegionBounds);
651                         int numIndices = indices != null ? indices.length : 0;
652                         GeneralPath gp = null;
653 
654                         for(int i = 0; i < numIndices; i++) {
655                             if (i % 1000 == 0 && gp != null)
656                                 gp = new GeneralPath(new Area(gp));
657 
658                             Rectangle dstRect =
659                                 getTileRect(indices[i].x, indices[i].y);
660                             Rectangle srcRect =
661                                 oldPISP.mapDestRect(dstRect, idx);
662                             if(srcRect == null) {
663                                 gp = null;
664                                 break;
665                             }
666                             if(srcInvalidRegion.intersects(srcRect)) {
667                                 if(gp == null) {
668                                     gp = new GeneralPath(dstRect);
669                                 } else {
670                                     gp.append(dstRect, false);
671                                 }
672                             } else {
673                                 if(validTiles == null) {
674                                     validTiles = new ArrayList();
675                                 }
676                                 validTiles.add(indices[i]);
677                             }
678                         }
679 
680                         invalidRegion = (gp == null) ? null : new Area(gp);
681                     }
682 
683                     // Retrieve the old TileCache.
684                     TileCache oldCache = oldPISP.getTileCache();
685 		    theImage = null;
686 
687                     // Only perform further processing if there is a cache
688                     // and there are tiles to save.
689                     if (oldCache != null &&
690 			(saveAllTiles || validTiles != null)) {
691 
692 			// Create new rendering
693 			newEventRendering(protocolName,
694 					  oldPISP,
695 					  (PropertyChangeEventJAI)evt);
696 
697                         // Only perform further processing if the new
698                         // rendering is an OpImage with a non-null TileCache.
699                         if (theImage instanceof PlanarImageServerProxy &&
700                            ((PlanarImageServerProxy)theImage).getTileCache() !=
701 			   null) {
702                             PlanarImageServerProxy newPISP =
703 				(PlanarImageServerProxy)theImage;
704                             TileCache newCache = newPISP.getTileCache();
705 
706                             Object tileCacheMetric =
707                                 newPISP.getTileCacheMetric();
708 
709                             if (saveAllTiles) {
710                                 Raster[] tiles = oldCache.getTiles(oldPISP);
711                                 int numTiles = tiles == null ?
712 				    0 : tiles.length;
713                                 for(int i = 0; i < numTiles; i++) {
714                                     Raster tile = tiles[i];
715                                     int tx = newPISP.XToTileX(tile.getMinX());
716                                     int ty = newPISP.YToTileY(tile.getMinY());
717                                     newCache.add(newPISP,
718                                                  tx, ty, tile,
719 					         tileCacheMetric);
720                                 }
721                             } else { // save some, but not all, tiles
722                                 int numValidTiles = validTiles.size();
723                                 for(int i = 0; i < numValidTiles; i++) {
724                                     Point tileIndex = (Point)validTiles.get(i);
725                                     Raster tile =
726                                         oldCache.getTile(oldPISP,
727                                                          tileIndex.x,
728                                                          tileIndex.y);
729                                     if (tile != null) {
730                                         newCache.add(newPISP,
731                                                      tileIndex.x,
732                                                      tileIndex.y,
733                                                      tile,
734 						     tileCacheMetric);
735                                     }
736                                 }
737                             }
738                         }
739                     }
740                 }
741             } else { // not op name or registry change nor RenderingChangeEvent
742                 ParameterBlock oldPB = null;
743                 ParameterBlock newPB = null;
744 		String oldServerName = serverName;
745 		String newServerName = serverName;
746 
747                 boolean checkInvalidRegion = false;
748 
749 		if (propName.equals("operationname")) {
750 
751 		    if (theImage instanceof PlanarImageServerProxy) {
752 			newEventRendering(protocolName,
753 					  (PlanarImageServerProxy)theImage,
754 					  (PropertyChangeEventJAI)evt);
755 		    } else {
756 			theImage = null;
757 			createRendering();
758 		    }
759 
760 		    // Do not set checkInvalidRegion to true, since there
761 		    // are no tiles to save for this case.
762 
763 		    shouldFireEvent = true;
764 
765 		    // XXX Do we need to do any evaluation of any
766 		    // DeferredData parameters.
767 
768 		} else if (propName.equals("parameterblock")) {
769                     oldPB = (ParameterBlock)evt.getOldValue();
770                     newPB = (ParameterBlock)evt.getNewValue();
771                     checkInvalidRegion = true;
772                 } else if (propName.equals("sources")) {
773                     // Replace source(s)
774                     Vector params =
775 			nodeSupport.getParameterBlock().getParameters();
776                     oldPB = new ParameterBlock((Vector)evt.getOldValue(),
777                                                params);
778                     newPB = new ParameterBlock((Vector)evt.getNewValue(),
779                                                params);
780                     checkInvalidRegion = true;
781                 } else if (propName.equals("parameters")) {
782                     // Replace parameter(s)
783                     oldPB = new ParameterBlock(nodeSources,
784                                                (Vector)evt.getOldValue());
785                     newPB = new ParameterBlock(nodeSources,
786                                                (Vector)evt.getNewValue());
787                     checkInvalidRegion = true;
788                 } else if (propName.equals("renderinghints")) {
789                     oldPB = newPB = nodeSupport.getParameterBlock();
790                     checkInvalidRegion = true;
791                 } else if (propName.equals("servername")) {
792 		    oldPB = newPB = nodeSupport.getParameterBlock();
793 		    oldServerName = (String)evt.getOldValue();
794 		    newServerName = (String)evt.getNewValue();
795 		    checkInvalidRegion = true;
796 		} else if (evt instanceof CollectionChangeEvent) {
797 		    // Event from a CollectionOp source.
798                     // Replace appropriate source.
799                     int collectionIndex = nodeSources.indexOf(evtSrc);
800                     Vector oldSources = (Vector)nodeSources.clone();
801                     Vector newSources = (Vector)nodeSources.clone();
802                     oldSources.set(collectionIndex, evt.getOldValue());
803                     newSources.set(collectionIndex, evt.getNewValue());
804 
805                     Vector params =
806                         nodeSupport.getParameterBlock().getParameters();
807 
808                     oldPB = new ParameterBlock(oldSources, params);
809                     newPB = new ParameterBlock(newSources, params);
810 
811                     checkInvalidRegion = true;
812                 }
813 
814                 if (checkInvalidRegion) {
815                     // Set event flag.
816                     shouldFireEvent = true;
817 
818                     // Get the associated RemoteDescriptor.
819                     OperationRegistry registry = nodeSupport.getRegistry();
820                     RemoteDescriptor odesc = (RemoteDescriptor)
821                         registry.getDescriptor(RemoteDescriptor.class,
822                                                protocolName);
823 
824 		    // XXX
825                     // Evaluate any DeferredData parameters.
826                     oldPB = ImageUtil.evaluateParameters(oldPB);
827                     newPB = ImageUtil.evaluateParameters(newPB);
828 
829                     // Determine the invalid region.
830                     invalidRegion = (Shape)
831                         odesc.getInvalidRegion("rendered",
832 					       oldServerName,
833                                                oldPB,
834                                                oldHints,
835 					       newServerName,
836                                                newPB,
837                                                nodeSupport.getRenderingHints(),
838                                                this);
839 
840                     if (invalidRegion == null ||
841                        !(theImage instanceof PlanarImageServerProxy)) {
842                         // Can't save any tiles; clear the rendering.
843                         theImage = null;
844 
845                     } else {
846 
847                         // Create a new rendering.
848                         PlanarImageServerProxy oldRendering =
849 			    (PlanarImageServerProxy)theImage;
850 
851 			newEventRendering(protocolName, oldRendering,
852 					  (PropertyChangeEventJAI)evt);
853 
854                         // If the new rendering is also a
855 			// PlanarImageServerProxy, save some tiles.
856                         if (theImage instanceof PlanarImageServerProxy &&
857 			    oldRendering.getTileCache() != null &&
858 			    ((PlanarImageServerProxy)theImage).getTileCache()
859 			    != null) {
860                             PlanarImageServerProxy newRendering =
861 				(PlanarImageServerProxy)theImage;
862 
863                             TileCache oldCache = oldRendering.getTileCache();
864                             TileCache newCache = newRendering.getTileCache();
865 
866                             Object tileCacheMetric =
867                                 newRendering.getTileCacheMetric();
868 
869                             // If bounds are empty, replace invalidRegion with
870                             // the complement of the image bounds within the
871                             // bounds of all tiles.
872                             if (invalidRegion.getBounds().isEmpty()) {
873                                 int x = oldRendering.tileXToX(
874                                             oldRendering.getMinTileX());
875                                 int y = oldRendering.tileYToY(
876                                             oldRendering.getMinTileY());
877                                 int w = oldRendering.getNumXTiles() *
878                                     oldRendering.getTileWidth();
879                                 int h = oldRendering.getNumYTiles() *
880                                     oldRendering.getTileHeight();
881                                 Rectangle tileBounds =
882 				    new Rectangle(x, y, w, h);
883                                 Rectangle imageBounds =
884                                     oldRendering.getBounds();
885                                 if (!tileBounds.equals(imageBounds)) {
886                                     Area tmpArea = new Area(tileBounds);
887                                     tmpArea.subtract(new Area(imageBounds));
888                                     invalidRegion = tmpArea;
889                                 }
890                             }
891 
892                             if (invalidRegion.getBounds().isEmpty()) {
893 
894                                 // Save all tiles.
895                                 Raster[] tiles =
896                                     oldCache.getTiles(oldRendering);
897                                 int numTiles = tiles == null ?
898                                     0 : tiles.length;
899                                 for(int i = 0; i < numTiles; i++) {
900                                     Raster tile = tiles[i];
901                                     int tx =
902                                         newRendering.XToTileX(tile.getMinX());
903                                     int ty =
904                                         newRendering.YToTileY(tile.getMinY());
905                                     newCache.add(newRendering,
906                                                  tx, ty, tile,
907 						 tileCacheMetric);
908                                 }
909                             } else {
910                                 // Copy tiles not in invalid region from old
911                                 // TileCache to new TileCache.
912                                 Raster[] tiles =
913                                     oldCache.getTiles(oldRendering);
914                                 int numTiles = tiles == null ?
915                                     0 : tiles.length;
916                                 for(int i = 0; i < numTiles; i++) {
917                                     Raster tile = tiles[i];
918                                     Rectangle bounds = tile.getBounds();
919                                     if (!invalidRegion.intersects(bounds)) {
920                                         newCache.add(
921 					      newRendering,
922 					      newRendering.XToTileX(bounds.x),
923 					      newRendering.YToTileY(bounds.y),
924 					      tile,
925 					      tileCacheMetric);
926                                     }
927                                 }
928                             }
929                         }
930                     }
931                 }
932             }
933 
934             // Re-render the node. This will only occur if theImage
935             // has been set to null above.
936 	    if (theOldImage instanceof PlanarImageServerProxy &&
937 		theImage == null) {
938 		newEventRendering(protocolName,
939 				  (PlanarImageServerProxy)theOldImage,
940 				  (PropertyChangeEventJAI)evt);
941 	    } else {
942 		createRendering();
943 	    }
944 
945             // Fire an event if the flag was set.
946             if (shouldFireEvent) {
947 
948                 // Clear the synthetic and cached properties and reset the
949                 // property source.
950 		resetProperties(true);
951 
952                 // Create the event object.
953                 RenderingChangeEvent rcEvent =
954                     new RenderingChangeEvent(this, theOldImage, theImage,
955                                              invalidRegion);
956 
957                 // Fire to all registered listeners.
958                 eventManager.firePropertyChange(rcEvent);
959 
960 		// Fire an event to all PropertyChangeListener sinks.
961 		Vector sinks = getSinks();
962 		if (sinks != null) {
963 		    int numSinks = sinks.size();
964 		    for (int i = 0; i < numSinks; i++) {
965 			Object sink = sinks.get(i);
966 			if (sink instanceof PropertyChangeListener) {
967 			    ((PropertyChangeListener)sink).propertyChange(rcEvent);
968 			}
969 		    }
970 		}
971 	    }
972 	}
973     }
974 
975     /**
976      * Creates a new rendering in response to the provided event, and
977      * assigns the new rendering to "theImage" variable.
978      */
newEventRendering(String protocolName, PlanarImageServerProxy oldPISP, PropertyChangeEventJAI event)979     private void newEventRendering(String protocolName,
980 				   PlanarImageServerProxy oldPISP,
981 				   PropertyChangeEventJAI event) {
982 	RemoteRIF rrif = (RemoteRIF)nodeSupport.getRegistry().
983 	    getFactory("remoterendered", protocolName);
984 	theImage = (PlanarImage)rrif.create(oldPISP, this, event);
985     }
986 
987     /**
988      * Fire an events to all registered listeners.
989      */
fireEvent(String propName, Object oldVal, Object newVal)990     private void fireEvent(String propName, Object oldVal, Object newVal) {
991         if (eventManager != null) {
992             Object eventSource = eventManager.getPropertyChangeEventSource();
993             PropertyChangeEventJAI evt =
994                 new PropertyChangeEventJAI(eventSource,
995                                            propName, oldVal, newVal);
996             eventManager.firePropertyChange(evt);
997         }
998     }
999 
1000     /**
1001      * Returns the amount of time between retries in milliseconds.
1002      *
1003      * <p> If this <code>RemoteRenderedOp</code> has been rendered, then its
1004      * <code>getRetryInterval()</code> method will be called to return
1005      * the current retry interval. If no rendering has been created, and
1006      * a value was set using the <code>setRetryInterval()</code> method), that
1007      * value will be returned, else the default retry interval as defined by
1008      * <code>RemoteJAI.DEFAULT_RETRY_INTERVAL</code> is returned.
1009      */
getRetryInterval()1010     public int getRetryInterval() {
1011 	if (theImage != null) {
1012 	    return ((RemoteRenderedImage)theImage).getRetryInterval();
1013 	} else {
1014 	    RenderingHints rh = nodeSupport.getRenderingHints();
1015 	    if (rh == null) {
1016 		return RemoteJAI.DEFAULT_RETRY_INTERVAL;
1017 	    } else {
1018 		Integer i = (Integer)rh.get(JAI.KEY_RETRY_INTERVAL);
1019 		if (i == null)
1020 		    return RemoteJAI.DEFAULT_RETRY_INTERVAL;
1021 		else
1022 		    return i.intValue();
1023 	    }
1024 	}
1025     }
1026 
1027     /**
1028      * Sets the amount of time between retries in milliseconds. If this
1029      * <code>RemoteRenderedOp</code> has already been rendered, the
1030      * <code>setRetryInterval()</code> method is called on the rendering
1031      * to inform it of the new retry interval. The rendering can choose to
1032      * ignore this new setting, in which case <code>getRetryInterval()</code>
1033      * will still return the old value, or the rendering can honor these
1034      * settings, in which case <code>getRetryInterval()</code> will return
1035      * the new settings. If this <code>RemoteRenderedOp</code> has not been
1036      * rendered, the new retry interval specified will be stored.
1037      * These new stored retry interval will be passed as
1038      * part of the <code>RenderingHints</code> using the
1039      * <code>KEY_RETRY_INTERVAL</code> key, to the new rendering
1040      * when it is created.
1041      *
1042      * @param retryInterval The amount of time (in milliseconds) to wait
1043      *                      between retries.
1044      * @throws IllegalArgumentException if retryInterval is negative.
1045      */
setRetryInterval(int retryInterval)1046     public void setRetryInterval(int retryInterval) {
1047 
1048 	if (retryInterval < 0)
1049 	    throw new IllegalArgumentException(JaiI18N.getString("Generic3"));
1050 
1051 	if (theImage != null) {
1052 	    ((RemoteRenderedImage)theImage).setRetryInterval(retryInterval);
1053 	}
1054 
1055 	RenderingHints rh = nodeSupport.getRenderingHints();
1056 	if (rh == null) {
1057 	    nodeSupport.setRenderingHints(new RenderingHints(null));
1058 	    rh = nodeSupport.getRenderingHints();
1059 	}
1060 
1061 	rh.put(JAI.KEY_RETRY_INTERVAL, new Integer(retryInterval));
1062     }
1063 
1064     /**
1065      * Returns the number of retries.
1066      *
1067      * <p> If this <code>RemoteRenderedOp</code> has been rendered, then its
1068      * <code>getNumRetries()</code> method will be called to return
1069      * the current number of retries. If no rendering has been created, and
1070      * a value was set using the <code>setNumRetries()</code> method), that
1071      * value will be returned, else the default retry interval as defined by
1072      * <code>RemoteJAI.DEFAULT_NUM_RETRIES</code> is returned.
1073      */
getNumRetries()1074     public int getNumRetries() {
1075 	if (theImage != null) {
1076 	    return ((RemoteRenderedImage)theImage).getNumRetries();
1077 	} else {
1078 	    RenderingHints rh = nodeSupport.getRenderingHints();
1079 	    if (rh == null) {
1080 		return RemoteJAI.DEFAULT_NUM_RETRIES;
1081 	    } else {
1082 		Integer i = (Integer)rh.get(JAI.KEY_NUM_RETRIES);
1083 		if (i == null)
1084 		    return RemoteJAI.DEFAULT_NUM_RETRIES;
1085 		else
1086 		    return i.intValue();
1087 	    }
1088 	}
1089     }
1090 
1091     /**
1092      * Sets the number of retries. If this <code>RemoteRenderedOp</code>
1093      * has already been rendered, the <code>setNumRetries()</code> method
1094      * is called on the rendering to inform it of the new number of retries.
1095      * The rendering can choose to ignore these new settings, in which case
1096      * <code>getNunRetries()</code> will still return the old values, or
1097      * the rendering can honor these new settings in which
1098      * case <code>getNumRetries()</code> will return the new value.
1099      * If this <code>RemoteRenderedOp</code> has not been rendered,
1100      * the new setting specified will be stored.
1101      * These new settings which have been stored will be passed as
1102      * part of the <code>RenderingHints</code> using the
1103      * <code>KEY_NUM_RETRIES</code> key, to the new rendering
1104      * when it is created.
1105      *
1106      * @param numRetries The number of times an operation should be retried
1107      *                   in case of a network error.
1108      * @throws IllegalArgumentException if numRetries is negative.
1109      */
setNumRetries(int numRetries)1110     public void setNumRetries(int numRetries) {
1111 
1112 	if (numRetries < 0)
1113 	    throw new IllegalArgumentException(
1114 				      JaiI18N.getString("Generic4"));
1115 
1116 	if (theImage != null) {
1117 	    ((RemoteRenderedImage)theImage).setNumRetries(numRetries);
1118 	}
1119 
1120 	RenderingHints rh = nodeSupport.getRenderingHints();
1121 	if (rh == null) {
1122 	    nodeSupport.setRenderingHints(new RenderingHints(null));
1123 	    rh = nodeSupport.getRenderingHints();
1124 	}
1125 
1126 	rh.put(JAI.KEY_NUM_RETRIES, new Integer(numRetries));
1127     }
1128 
1129     /**
1130      * Sets the preferences to be used in the client-server
1131      * communication. These preferences are utilized in the negotiation
1132      * process. Note that preferences for more than one category can be
1133      * specified using this method. Also each preference can be a list
1134      * of values in decreasing order of preference, each value specified
1135      * as a <code>NegotiableCapability</code>. The
1136      * <code>NegotiableCapability</code> first (for a particular category)
1137      * in this list is given highest priority in the negotiation process
1138      * (for that category).
1139      *
1140      * <p> It may be noted that this method allows for multiple negotiation
1141      * cycles by allowing negotiation preferences to be set
1142      * multiple times. If this <code>RemoteRenderedOp</code> has already
1143      * been rendered, the <code>setNegotiationPreferences()</code> method
1144      * is called on the rendering to inform it of the new preferences. The
1145      * rendering can choose to ignore these new preferences, in which case
1146      * <code>getNegotiatedValues()</code> will still return the results of
1147      * the old negotiation, or the rendering can re-perform the negotiation,
1148      * (using the <code>RemoteJAI.negotiate</code>, for example) in which
1149      * case <code>getNegotiatedValues()</code> will return the new
1150      * negotiated values. If this <code>RemoteRenderedOp</code> has not been
1151      * rendered, the new preferences specified will be stored, a negotiation
1152      * with these new preferences will be initiated and the results stored.
1153      * These new preferences which have been stored will be passed as
1154      * part of the <code>RenderingHints</code> using the
1155      * <code>KEY_NEGOTIATION_PREFERENCES</code> key, to the new rendering
1156      * when it is created.
1157      *
1158      * @param preferences The preferences to be used in the negotiation
1159      * process.
1160      */
setNegotiationPreferences(NegotiableCapabilitySet preferences)1161     public void setNegotiationPreferences(NegotiableCapabilitySet preferences)
1162     {
1163 	if (theImage != null) {
1164 	    ((RemoteRenderedImage)theImage).setNegotiationPreferences(
1165 								 preferences);
1166 	}
1167 
1168 	RenderingHints rh = nodeSupport.getRenderingHints();
1169 
1170 	if (preferences != null) {
1171 	    if (rh == null) {
1172 		nodeSupport.setRenderingHints(new RenderingHints(null));
1173 		rh = nodeSupport.getRenderingHints();
1174 	    }
1175 
1176 	    rh.put(JAI.KEY_NEGOTIATION_PREFERENCES, preferences);
1177 	} else {
1178 	    // Remove any previous values set for negotiation preferences
1179 	    if (rh != null) {
1180 		rh.remove(JAI.KEY_NEGOTIATION_PREFERENCES);
1181 	    }
1182 	}
1183 
1184 	negotiated = negotiate(preferences);
1185     }
1186 
1187     /**
1188      * Returns the current negotiation preferences or null, if none were
1189      * set previously.
1190      */
getNegotiationPreferences()1191     public NegotiableCapabilitySet getNegotiationPreferences() {
1192 
1193 	RenderingHints rh = nodeSupport.getRenderingHints();
1194 	return rh == null ? null : (NegotiableCapabilitySet)rh.get(
1195 					     JAI.KEY_NEGOTIATION_PREFERENCES);
1196     }
1197 
1198     // do the negotiation
negotiate(NegotiableCapabilitySet prefs)1199     private NegotiableCapabilitySet negotiate(NegotiableCapabilitySet prefs) {
1200 
1201 	OperationRegistry registry = nodeSupport.getRegistry();
1202 
1203 	NegotiableCapabilitySet serverCap = null;
1204 
1205 	// Get the RemoteDescriptor for protocolName
1206 	RemoteDescriptor descriptor = (RemoteDescriptor)
1207 	    registry.getDescriptor(RemoteDescriptor.class, protocolName);
1208 
1209 	if (descriptor == null) {
1210 	    Object[] msgArg0 = {new String(protocolName)};
1211 	    MessageFormat formatter = new MessageFormat("");
1212 	    formatter.setLocale(Locale.getDefault());
1213 	    formatter.applyPattern(JaiI18N.getString("RemoteJAI16"));
1214 	    throw new ImagingException(formatter.format(msgArg0));
1215 	}
1216 
1217 	int count=0;
1218 	int numRetries = getNumRetries();
1219 	int retryInterval = getRetryInterval();
1220 
1221 	Exception rieSave = null;
1222 	while (count++ < numRetries) {
1223 	    try {
1224 		serverCap = descriptor.getServerCapabilities(serverName);
1225 		break;
1226 	    } catch (RemoteImagingException rie) {
1227 		// Print that an Exception occured
1228 		System.err.println(JaiI18N.getString("RemoteJAI24"));
1229 		rieSave = rie;
1230 		// Sleep for retryInterval milliseconds
1231 		try {
1232 		    Thread.sleep(retryInterval);
1233 		} catch (InterruptedException ie) {
1234 //		    throw new RuntimeException(ie.toString());
1235                     sendExceptionToListener(JaiI18N.getString("Generic5"),
1236                                             new ImagingException(JaiI18N.getString("Generic5"), ie));
1237 		}
1238 	    }
1239 	}
1240 
1241 	if (serverCap == null && count > numRetries) {
1242             sendExceptionToListener(JaiI18N.getString("RemoteJAI18"), rieSave);
1243 //	    throw new RemoteImagingException(JaiI18N.getString("RemoteJAI18")+"\n"+rieSave.getMessage());
1244 	}
1245 
1246 	RemoteRIF rrif = (RemoteRIF)registry.getFactory("remoteRendered",
1247 							protocolName);
1248 
1249 	return RemoteJAI.negotiate(prefs,
1250 				   serverCap,
1251 				   rrif.getClientCapabilities());
1252     }
1253 
1254     /**
1255      * Returns the results of the negotiation between the client and server
1256      * capabilities according to the preferences set via the
1257      * <code>setNegotiationPreferences()</code> method. This will return
1258      * null if no negotiation preferences were set, and no negotiation
1259      * was performed, or if the negotiation failed.
1260      *
1261      * <p> If this <code>RemoteRenderedOp</code> has been rendered, then its
1262      * <code>getNegotiatedValues()</code> method will be called to return
1263      * the current negotiated values. If no rendering has been created, then
1264      * the internally stored negotiated value (calculated when the new
1265      * preferences were set using the <code>setNegotiationPreferences()</code>
1266      * method) will be returned.
1267      */
getNegotiatedValues()1268     public NegotiableCapabilitySet getNegotiatedValues()
1269 	throws RemoteImagingException {
1270 	if (theImage != null) {
1271 	    return ((RemoteRenderedImage)theImage).getNegotiatedValues();
1272 	} else {
1273 	    return negotiated;
1274 	}
1275     }
1276 
1277     /**
1278      * Returns the results of the negotiation between the client and server
1279      * capabilities for the given category according to the preferences
1280      * set via the <code>setNegotiationPreferences()</code> method. This
1281      * will return null if no negotiation preferences were set, and no
1282      * negotiation was performed, or if the negotiation failed.
1283      *
1284      * <p> If this <code>RemoteRenderedOp</code> has been rendered, then its
1285      * <code>getNegotiatedValues()</code> method will be called to return
1286      * the current negotiated values. If no rendering has been created, then
1287      * the internally stored negotiated value (calculated when the new
1288      * preferences were set using the <code>setNegotiationPreferences()</code>
1289      * method) will be returned.
1290      *
1291      * @param category The category to return the negotiated results for.
1292      */
getNegotiatedValue(String category)1293     public NegotiableCapability getNegotiatedValue(String category)
1294 	throws RemoteImagingException {
1295 	if (theImage != null) {
1296 	    return ((RemoteRenderedImage)theImage).getNegotiatedValue(
1297 								    category);
1298 	} else {
1299 	    return negotiated == null ? null :
1300 		negotiated.getNegotiatedValue(category);
1301 	}
1302     }
1303 
1304     /**
1305      * Informs the server of the negotiated values that are the result of
1306      * a successful negotiation. If this <code>RemoteRenderedOp</code> has
1307      * been rendered, then the rendering's
1308      * <code>setServerNegotiatedValues</code> method will be called to
1309      * inform the server of the negotiated results. If no rendering has
1310      * been created, this method will do nothing.
1311      *
1312      * @param negotiatedValues    The result of the negotiation.
1313      */
setServerNegotiatedValues(NegotiableCapabilitySet negotiatedValues)1314     public void setServerNegotiatedValues(NegotiableCapabilitySet
1315 					  negotiatedValues)
1316 	throws RemoteImagingException {
1317 
1318 	if (theImage != null) {
1319 	    ((RemoteRenderedImage)theImage).setServerNegotiatedValues(
1320 							    negotiatedValues);
1321 	}
1322     }
1323 
sendExceptionToListener(String message, Exception e)1324     void sendExceptionToListener(String message, Exception e) {
1325         ImagingListener listener =
1326             (ImagingListener)getRenderingHints().get(JAI.KEY_IMAGING_LISTENER);
1327 
1328         listener.errorOccurred(message, e, this, false);
1329     }
1330 }
1331