FileDocCategorySizeDatePackage
SimpleGraphBuilder.javaAPI DocJMF 2.1.1e25364Mon May 12 12:20:50 BST 2003com.sun.media

SimpleGraphBuilder

public class SimpleGraphBuilder extends Object
This is the Graph builder to generate the data flow graph for rendering an input format. It contains 3 parts: 1) Routines to search for all the supported output formats; 2) Routines to build a default flow graph -- buildGraph; A default graph is such that no customised option is specified on the TrackControl. It operates on a breath-first search algorithm until the final target is reached as defined by the findTarget() method. Intermediate search paths are stored as GraphNode's in the "candidates" vector.

Fields Summary
protected int
STAGES
protected Hashtable
plugIns
protected GraphNode[]
targetPlugins
protected Vector
targetPluginNames
protected int
targetType
int
indent
protected static GraphInspector
inspector
Constructors Summary
Methods Summary
booleanbuildGraph(com.sun.media.BasicTrackControl tc)
Take a TrackControl and build the graph for it.


	    Log.comment("Input: " + tc.getOriginalFormat());

	    Vector candidates = new Vector();
	    GraphNode node = new GraphNode(null, (PlugIn)null, 
					tc.getOriginalFormat(), null, 0);
	    indent = 1;
	    Log.setIndent(indent);

	    // Define the final targets.
	    if (!setDefaultTargets(tc.getOriginalFormat()))
		return false;

	    candidates.addElement(node);

	    GraphNode failed;

	    while ((node = buildGraph(candidates)) != null) {

		// Found a potential graph.  Check if we can build a
		// track from it. 
		if ((failed = buildTrackFromGraph(tc, node)) == null) {
		    // we are done.
		    indent = 0;
		    Log.setIndent(indent);
		    return true;
		}

		// If we can't build a track from it, it's because there's
		// a node in the graph that cannot be opened.  We'll have
		// to reap it from the candidates and the registry. 
		removeFailure(candidates, failed, tc.getOriginalFormat() );
	    }

	    indent = 0;
	    Log.setIndent(indent);
	    return false;
    
com.sun.media.GraphNodebuildGraph(javax.media.Format input)
Build a flow graph based on the given input format.


	    Log.comment("Input: " + input);

	    Vector candidates = new Vector();
	    GraphNode node = new GraphNode(null, (PlugIn)null, 
					input, null, 0);
	    indent = 1;
	    Log.setIndent(indent);

	    // Define the final targets.
	    if (!setDefaultTargets(input))
		return null;

	    candidates.addElement(node);

	    GraphNode failed;

	    while ((node = buildGraph(candidates)) != null) {

		// Found a potential graph.  Verify it if all the
		// nodes can be used.
		if ((failed = verifyGraph(node)) == null) {
		    // we are done.
		    indent = 0;
		    Log.setIndent(indent);
		    return node;
		}

		// If we can't build a track from it, it's because there's
		// a node in the graph that cannot be opened.  We'll have
		// to reap it from the candidates and the registry. 
		removeFailure(candidates, failed, input);
	    }

	    indent = 0;
	    Log.setIndent(indent);

	    return node;
    
com.sun.media.GraphNodebuildGraph(java.util.Vector candidates)
Given the intermediate search candidates, build a graph until it reaches a target.

	    GraphNode node;
	    while ((node = doBuildGraph(candidates)) == null) {
		if (candidates.isEmpty())
		    break;
	    }
	    return node;
    
protected com.sun.media.GraphNodebuildTrackFromGraph(com.sun.media.BasicTrackControl tc, com.sun.media.GraphNode node)
When the graph build finds a viable graph to build, this callback will be invoked to see if the graph can actually be built. Subclass should implement this.

	return null;
    
public static javax.media.PlugIncreatePlugIn(java.lang.String name, int type)

	Class cls;
	Object obj;

	try {
	    // cls = Class.forName(name);
	    cls = BasicPlugIn.getClassForName(name);
	    obj = cls.newInstance();
	} catch (Exception e) {
	    //Log.write("Cannot instantiate: " + name);
	    return null;
	} catch (Error e) {
	    return null;
	}

	if (verifyClass(obj, type))
	    return (PlugIn)obj;
	//Log.write(name + " is not of type " + cls);
	return null;
    
com.sun.media.GraphNodedoBuildGraph(java.util.Vector candidates)
This is the "worker" method that does all the dirty work.

	    if (candidates.isEmpty())
		return null;

	    GraphNode node = (GraphNode)candidates.firstElement();
	    candidates.removeElementAt(0);

	    if (node.input == null && 
		(node.plugin == null || !(node.plugin instanceof Codec))) {
		// shouldn't happen!
		Log.error("Internal error: doBuildGraph");
		return null;
	    }

	    int oldIndent = indent;

	    Log.setIndent(node.level + 1);

	    //Log.write("level: " + node.level);
	    if (node.plugin != null) {

		// It may not seem necessary to do this since the
		// previous round has already verified the input.
		// But since the same plugin could have a different
		// input called on it on previous rounds, it needs to
		// be resetted to the designated input.  This has
		// caused a bug in failing setOutputFormat for some
		// codecs. 
		if (verifyInput(node.plugin, node.input) == null)
		    return null;
	    }

	    /*
		Log.write("Try plugin: " + node.plugin.getClass());
	    else
	        Log.write("Given input: " + node.input);
	    */

	    // Stop when the target is reached as defined by the findTarget
	    // method.
	    GraphNode n;
	    if ((n = findTarget(node)) != null) {
		// We are done!
		/*
		if (n.plugin != null)
		   Log.write("Found target: " + n.plugin);
		else
		   Log.write("Found target: " + n.cname);
		*/
		indent = oldIndent;
		Log.setIndent(indent);
		return n;
	    }

	    // Don't go deeper than allowed.
	    if (node.level >= STAGES) {
		indent = oldIndent;
		Log.setIndent(indent);
		return null;
	    }

	    Format input, outs[];
	    boolean mp3Pkt = false;	// 2.1.1b hack	-ivg

	    if (node.plugin != null) {
		if (node.output != null) {
		    outs = new Format[1];
		    outs[0] = node.output;
		} else {
		    outs = node.getSupportedOutputs(node.input);
		    if (outs == null || outs.length == 0) {
			//Log.write("Weird!  The given plugin does not support any output.");
			indent = oldIndent;
			Log.setIndent(indent);
			return null;
		    }
		}
		input = node.input;

		// 2.1.1b hack	-ivg
		if (node.plugin instanceof 
			com.sun.media.codec.audio.mpa.Packetizer)
		    mp3Pkt = true;

	    } else {
		outs = new Format[1];
		outs[0] = node.input;
		input = null;
	    }

	    GraphNode gn;
	    Format fmt, ins[];
	    boolean foundSomething = false;
	    for (int i = 0; i < outs.length; i++) {

		// Ignore outputs that are the same as the input.
		if (!node.custom && input != null && input.equals(outs[i]))
		    continue;

		// Verify the output format.
		if (node.plugin != null) {

		    if (verifyOutput(node.plugin, outs[i]) == null) {
			//Log.write("Verify output failed: " + node.plugin);
			//Log.write("  with: " + outs[i]); 
			if (inspector != null && inspector.detailMode())
			    inspector.verifyOutputFailed(node.plugin, outs[i]);
			continue;
		    }

		    if (inspector != null &&
			!inspector.verify((Codec)node.plugin, node.input, outs[i]))
			continue;
		}

		//Log.write("find codec for input: " + outs[i]);

		Vector cnames = PlugInManager.getPlugInList(outs[i], 
					null, PlugInManager.CODEC);
		if (cnames == null || cnames.size() == 0)
		    continue;

		for (int j = 0; j < cnames.size(); j++) {

		    // Instantiate and verify the codec.
		    if ((gn = getPlugInNode((String)cnames.elementAt(j), 
					PlugInManager.CODEC, plugIns)) == null)
			continue;

		    // 2.1.1b hack	-ivg
		    if (mp3Pkt && gn.plugin instanceof
				com.sun.media.codec.audio.mpa.DePacketizer)
			continue;

		    // Check to see if the particular input/plugin combination
		    // has already been attempted.  If so, we don't need to
		    // do it again.
		    if (gn.checkAttempted(outs[i]))
			continue;

	            //Log.write("Try codec: " + cnames.elementAt(j));

		    ins = gn.getSupportedInputs();
		    if ((fmt = matches(outs[i], ins, null, gn.plugin)) == null) {
			//Log.write("Verify input failed: " + outs[i]);
			//Log.write("    : " + gn.plugin);
			if (inspector != null && inspector.detailMode())
			    inspector.verifyInputFailed(gn.plugin, outs[i]);
			continue;
		    }

		    if (inspector != null && inspector.detailMode()) {
			if (!inspector.verify((Codec)gn.plugin, fmt, null))
			    continue;
		    }

		    n = new GraphNode(gn, fmt, node, node.level+1);
		    candidates.addElement(n);
		    foundSomething = true;
		}
	    }

	    /*
	    if (!foundSomething) {
		if (node.plugin == null)
		    Log.write("  no codec supports the given input.");
		else
		    Log.write("  no codec supports the outputs from this plugin.");
	    }
	    */

	    indent = oldIndent;
	    Log.setIndent(indent);
	    return null;
    
public static javax.media.CodecfindCodec(javax.media.Format in, javax.media.Format out, javax.media.Format[] selectedIn, javax.media.Format[] selectedOut)
Find a codec that can handle the given input and output. The output argument can be null if no specific output format is required.

	Vector cnames = PlugInManager.getPlugInList(in, out, 
					PlugInManager.CODEC);
	if (cnames == null) {
	    // Well no codec supports that input. :(
	    return null;
	}

	Codec c = null;
	Format fmts[], matched;
	for (int i = 0; i < cnames.size(); i++) {
	    if ((c = (Codec)createPlugIn((String)cnames.elementAt(i),
					PlugInManager.CODEC)) == null)
		continue;
	    fmts = c.getSupportedInputFormats();
	    if ((matched = matches(in, fmts, null, c)) == null)
		continue;
	    if (selectedIn != null && selectedIn.length > 0)
		selectedIn[0] = matched;
	    fmts = c.getSupportedOutputFormats(matched);
	    if (fmts == null || fmts.length == 0) {
		// Weird!
		continue;
	    }
	    boolean success = false;
	    for (int j = 0; j < fmts.length; j++) {
		// Try out the supported output formats in turn.
		if (out != null) {
		    if (!out.matches(fmts[j]) ||
			(matched = out.intersects(fmts[j])) == null)
			continue;
		} else
		    matched = fmts[j];
		if (c.setOutputFormat(matched) != null) {
		    success = true;
		    break;
		}
	    }
	    if (success) {
		try {
		    c.open();
		} catch (ResourceUnavailableException e) {
		}
		if (selectedOut != null && selectedOut.length > 0)
		    selectedOut[0] = matched;
		// Alright, we are done!
		return c;
	    }
	}

	return null;
    
public static javax.media.RendererfindRenderer(javax.media.Format in)
Find a renderer that can handle the given input and output. The output argument can be null if no specific output format is required.

	Vector names = PlugInManager.getPlugInList(in, null, 
					PlugInManager.RENDERER);
	if (names == null) {
	    // Well no renderer supports that input. :(
	    return null;
	}

	Renderer r = null;
	Format fmts[], matched;
	for (int i = 0; i < names.size(); i++) {
	    if ((r = (Renderer)createPlugIn((String)names.elementAt(i),
					PlugInManager.RENDERER)) == null)
		continue;
	    fmts = r.getSupportedInputFormats();
	    if ((matched = matches(in, fmts, null, r)) == null)
		continue;

	    try {
		r.open();
	    } catch (ResourceUnavailableException e) {
	    }

	    // Alright, we are done!
	    return r;
	}

	return null;
    
public static java.util.VectorfindRenderingChain(javax.media.Format in, java.util.Vector formats)
Return a chain of codecs and renderer to render to input format. Unlike findCodec and findRenderer, it uses the same graph building algorithm that the media engine uses to determine the best rendering path for a particular input format. The return value is a vector of plugins of all the codecs and the renderer. The plugin list is in reverse order starting from the renderer. The list of the corresponding input formats for each codec is also returned as an argument to the function.

	SimpleGraphBuilder gb = new SimpleGraphBuilder();
	GraphNode n;

	if ((n = gb.buildGraph(in)) == null)
	    return null;

	Vector list = new Vector(10);

	while (n != null && n.plugin != null) {
	    list.addElement(n.plugin);
	    if (formats != null)
		formats.addElement(n.input);
	    n = n.prev;
	}

	return list;
    
com.sun.media.GraphNodefindTarget(com.sun.media.GraphNode node)
This defines when the search ends. The "targets" array defines the nodes that are to be the "end points" (leaf nodes) of the graph. With the default graph builder, the targets array contains the list of sinks that can potentially support the input format.


	    Format outs[];

	    // Expand the outputs of the next node.
	    if (node.plugin == null) {
		outs = new Format[1];
		outs[0] = node.input;
	    } else {
		if (node.output != null) {
		    outs = new Format[1];
		    outs[0] = node.output;
		} else {
		    outs = node.getSupportedOutputs(node.input);
		    if (outs == null || outs.length == 0) {
			//Log.write("Weird!  The given plugin does not support any output."); 
			return null;
		    }
		}
	    }

	    GraphNode n;
	    
	    // Check for the list of predefined targets.
	    if (targetPlugins != null && (n = verifyTargetPlugins(node, outs)) != null)
		return n;

	    return null;
    
public static com.sun.media.GraphNodegetPlugInNode(java.lang.String name, int type, java.util.Hashtable plugIns)
Given a codec class name, instantiate the codec and query it dynamically to see if it supports the given input and output formats.

	GraphNode gn = null;
	Object obj = null;
	boolean add = false;

	// Check the hash registry to see if we've already instantiated that
	// object.  If not, we'll instantiate it.
	if (plugIns == null || (gn = (GraphNode)plugIns.get(name)) == null) {

	    PlugIn p = createPlugIn(name, type);

	    gn = new GraphNode(name, p, null, null, 0);
	    if (plugIns != null)
		plugIns.put(name, gn);

	    if (p == null) {
		// If we failed to create it this time, we won't try it again.
		// We'll mark it as failed.
		gn.failed = true;
		return null;
	    } else
		return gn;
	}

	// If it has been marked as failed before, we won't attempt 
	// to use it again.
	if (gn.failed)
	    return null;

	if (verifyClass(gn.plugin, type))
	    return gn;

	return null;
    
public static javax.media.Formatmatches(javax.media.Format[] outs, javax.media.Format[] ins, javax.media.PlugIn up, javax.media.PlugIn down)
Choose a format among the two input arrays that matches and verify that if the given upstream and downstream plugins accept the matched format as output (for the upstream) or as input (for the downstream). Either of the plugin arguments can be null. In which case the verification step will be skipped accordingly.

param
outs the supported output formats from the upstream node.
param
ints the supported input formats from the downstream node.
param
up the upstream node.
param
down the downstream node.
return
a matching format.

	Format fmt;
	if (outs == null) return null;
	for (int i = 0; i < outs.length; i++) {
	    if ((fmt = matches(outs[i], ins, up, down)) != null)
		return fmt;
	}
	return null;
    
public static javax.media.Formatmatches(javax.media.Format out, javax.media.Format[] ins, javax.media.PlugIn up, javax.media.PlugIn down)
Choose a format among the two input arrays that matches and verify that if the given upstream and downstream plugins accept the matched format as output (for the upstream) or as input (for the downstream). Either of the plugin arguments can be null. In which case the verification step will be skipped accordingly.

return
a matching format.

	if (out == null || ins == null) return null;
	for (int i = 0; i < ins.length; i++) {
	    if (ins[i] != null &&
		ins[i].getClass().isAssignableFrom(out.getClass()) &&
	        out.matches(ins[i]) ) {
		Format fmt = out.intersects(ins[i]);

		if (fmt == null)
		    // weird!
		    continue;

		// Check if the downstream accepts the given input.
		if (down != null && (fmt = verifyInput(down, fmt)) == null)
		    continue;

		// Check if the upstream accepts the given as output.
		Format refined = fmt;
		if (up != null && (refined = verifyOutput(up, fmt)) == null)
		    continue;

		// If the returned output format from the upstream is
		// different from the original input to the upstream,
		// we'll have to check that new format on the downstream
		// to make sure.
		if (down != null && refined != fmt &&
		    verifyInput(down, refined) == null)
		    continue;

		return refined;
	    }
	}
	return null;
    
public static javax.media.Formatmatches(javax.media.Format[] outs, javax.media.Format in, javax.media.PlugIn up, javax.media.PlugIn down)

	Format ins[] = new Format[1];
	ins[0] = in;
	return matches(outs, ins, up, down);
    
voidremoveFailure(java.util.Vector candidates, com.sun.media.GraphNode failed, javax.media.Format input)
Given a node that has failed, this function will eliminate it from the candiates list and mark it in the registry as as failed node so it won't be attempted again. This method is upated by hsy on 10/10/2000. If we just remove the failed node from candidates and mark it in the registy, we can't guarantee we could roll back to the exact/desired node/state to re-start searching, since this node might have already been removed from candidates. The simple/straightforward fix is to clear up candidates and plugIns and start allover again.


	if (failed.plugin == null)
	    return;
	
	// This is the new implementation updated by hsy on 10/10/2000
	// Here we re-start building the graph allover again. Clear
	// candidates and put the initial node into it.
	Log.comment("Failed to open plugin " + failed.plugin + ". Will re-build the graph allover again");
	candidates.removeAllElements();
	GraphNode hsyn =  new GraphNode(null, (PlugIn)null, 
					input, null, 0);
	indent = 1;
	Log.setIndent(indent);
	
	candidates.addElement(hsyn);
		
	// clear up the hashtable plugIns too, only let it keep
	// all the failed nodes, so that we are not going to
	// attemp these nodes again.
	failed.failed = true;
	plugIns.put(failed.plugin.getClass().getName(), failed);
	
	Enumeration e = plugIns.keys();
	while(e.hasMoreElements()) {
	    String ss = (String)e.nextElement();
	    GraphNode nn = (GraphNode)plugIns.get(ss);
	    if ( !nn.failed)
		plugIns.remove(ss);
	}
	
	
	/**** This is the old implementation.
	     GraphNode n;
	     Enumeration enum = candidates.elements();
	     while (enum.hasMoreElements()) {
	     n = (GraphNode)enum.nextElement();
	     if (n.plugin == failed.plugin)
	     candidates.removeElement(n);
	     }
	     
	     if ((n = (GraphNode)plugIns.get(failed.plugin.getClass().getName())) != null)
	     n.failed = true;
	     
	******/
    
public voidreset()
Reset local cache and reuse the same instance for graph building.

	Enumeration enum = plugIns.elements();
	GraphNode n;
	while (enum.hasMoreElements()) {
	    n = (GraphNode)enum.nextElement();
	    n.resetAttempted();
	}
    
booleansetDefaultTargetRenderer(javax.media.Format in)

	    // Define the final targets which uses renderers.
	    if (in instanceof AudioFormat) {
		targetPluginNames = PlugInManager.getPlugInList(
					new AudioFormat(null,
						AudioFormat.NOT_SPECIFIED,
						AudioFormat.NOT_SPECIFIED,
						AudioFormat.NOT_SPECIFIED,
						AudioFormat.NOT_SPECIFIED,
						AudioFormat.NOT_SPECIFIED,
						AudioFormat.NOT_SPECIFIED,
						AudioFormat.NOT_SPECIFIED,
						null),
					null, PlugInManager.RENDERER);
	    } else if (in instanceof VideoFormat) {
		targetPluginNames = PlugInManager.getPlugInList(
					new VideoFormat(null, 
						null,
						VideoFormat.NOT_SPECIFIED,
						null,
						VideoFormat.NOT_SPECIFIED // frameRate ???
						),
					null, PlugInManager.RENDERER);
	    } else {
		targetPluginNames = PlugInManager.getPlugInList(null, 
					null, PlugInManager.RENDERER);
	    }

	    // No target available.
	    if (targetPluginNames == null || targetPluginNames.size() == 0) {
		//Log.write("The graph builder does not recognize the input format at all:");
		//Log.write(in.toString());
		return false;
	    }

	    targetPlugins = new GraphNode[targetPluginNames.size()];
	    targetType = PlugInManager.RENDERER;

	    return true;
    
booleansetDefaultTargets(javax.media.Format in)
Set the default targets, which are the renderers.

	    return setDefaultTargetRenderer(in);
    
public static voidsetGraphInspector(com.sun.media.GraphInspector insp)


         
	inspector = insp;
    
public static booleanverifyClass(java.lang.Object obj, int type)

	Class cls;

	switch (type) {
	case PlugInManager.CODEC:
	    cls = Codec.class; break;
	case PlugInManager.RENDERER:
	    cls = Renderer.class; break;
	case PlugInManager.MULTIPLEXER:
	    cls = Multiplexer.class; break;
	default:
	    cls = PlugIn.class;
	}
	    
	if (cls.isInstance(obj))
	    return true;
	else
	    return false;
    
protected com.sun.media.GraphNodeverifyGraph(com.sun.media.GraphNode node)
Given a protential graph, verify it.


	    Format prevFormat = null;
	    Vector used = new Vector(5);

	    if (node.plugin == null) {
		// There's nothing to build.
		// i.e. the output from the source (demux) works just fine.
		// Probably just need to be multiplexed.
		return null;
	    }

	    Log.setIndent(indent++);

	    // Build the graph from the last node.
	    while (node != null && node.plugin != null) {

		if (used.contains(node.plugin)) {
		    // That plugin has already been used in the same path,
		    // we'll need to instantiate another one of its kind.
		    PlugIn p;
		    if (node.cname == null || 
			(p = createPlugIn(node.cname, -1)) == null) {
			Log.write("Failed to instantiate " + node.cname);
			return node;
		    }
		    node.plugin = p;
		} else {
		    used.addElement(node.plugin);
		}

		if ((node.type == -1 || node.type == PlugInManager.RENDERER) &&
		     node.plugin instanceof Renderer) {
		    ((Renderer)node.plugin).setInputFormat(node.input);
		} else if ((node.type == -1 || node.type == PlugInManager.CODEC) &&
		     node.plugin instanceof Codec) {
		    ((Codec)node.plugin).setInputFormat(node.input);
		    if (prevFormat != null)
			((Codec)node.plugin).setOutputFormat(prevFormat);
		    else if (node.output != null)
			((Codec)node.plugin).setOutputFormat(node.output);
		}

		// For renderers, we wait till prefetching to
		// open the device.

		if (!((node.type == -1 || node.type == PlugInManager.RENDERER) &&
		      node.plugin instanceof Renderer)) {
		    try {
			node.plugin.open();
		    } catch (Exception e) {
			Log.warning("Failed to open: " + node.plugin);
			node.failed = true;
			return node;
		    }
		}

		prevFormat = node.input;
		node = node.prev;
	    }

	    Log.setIndent(indent--);

	    return null;
    
public static javax.media.FormatverifyInput(javax.media.PlugIn p, javax.media.Format in)
Check if the given plugin supports the given input.

	if (p instanceof Codec)
	    return ((Codec)p).setInputFormat(in);
	if (p instanceof Renderer)
	    return ((Renderer)p).setInputFormat(in);
	return null;
    
public static javax.media.FormatverifyOutput(javax.media.PlugIn p, javax.media.Format out)
Check if the given plugin supports the given output.

	if (p instanceof Codec)
	    return ((Codec)p).setOutputFormat(out);
	return null;
    
com.sun.media.GraphNodeverifyTargetPlugins(com.sun.media.GraphNode node, javax.media.Format[] outs)
Check for a match in the list of predefined targets.


	    GraphNode gn;
	    Format fmt;

	    for (int i = 0; i < targetPlugins.length; i++) {
		if ((gn = targetPlugins[i]) == null) {
		    String name = (String)targetPluginNames.elementAt(i);
		    if (name == null)
			continue;

		    // Initial screening before instantiating the objects.
		    Format base[] = PlugInManager.getSupportedInputFormats(
							name, targetType);
		    if (matches(outs, base, null, null) == null)
			continue;

		    // Passing initial test, we'll want to instantiate it
		    // to get more info from it.
		    if ((gn = getPlugInNode(name, targetType, 
				plugIns)) == null) {
			targetPluginNames.setElementAt(null, i);
			continue;
		    }

		    targetPlugins[i] = gn;
		}

		if ((fmt = matches(outs, gn.getSupportedInputs(),
					node.plugin, gn.plugin)) != null) {
		    // found the target.

		    if (inspector != null) {

			if (node.plugin != null &&
			    !inspector.verify((Codec)node.plugin, node.input, fmt))
			    continue;
			if ((gn.type == -1 || 
			     gn.type == PlugInManager.CODEC) &&
			    gn.plugin instanceof Codec) {
			    if (!inspector.verify((Codec)gn.plugin, fmt, null))
				continue;
			} else if ((gn.type == -1 || 
			            gn.type == PlugInManager.RENDERER) &&
				    gn.plugin instanceof Renderer) {
			    if (!inspector.verify((Renderer)gn.plugin, fmt))
				continue;
			}
		    }

		    return new GraphNode(gn, fmt, node, node.level+1);
		}
	    }

	    return null;