Back to index

lightning-sunbird  0.9+nobinonly
nsStreamConverterService.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
00002  *
00003  * ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is mozilla.org code.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 1998
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK *****
00038  *
00039  *
00040  * This Original Code has been modified by IBM Corporation.
00041  * Modifications made by IBM described herein are
00042  * Copyright (c) International Business Machines
00043  * Corporation, 2000
00044  *
00045  * Modifications to Mozilla code or documentation
00046  * identified per MPL Section 3.3
00047  *
00048  * Date         Modified by     Description of modification
00049  * 03/27/2000   IBM Corp.       Added PR_CALLBACK for Optlink
00050  *                               use in OS2
00051  */
00052 
00053 #include "nsStreamConverterService.h"
00054 #include "nsIServiceManager.h"
00055 #include "nsIComponentManager.h"
00056 #include "nsString.h"
00057 #include "nsReadableUtils.h"
00058 #include "nsIAtom.h"
00059 #include "nsDeque.h"
00060 #include "nsIInputStream.h"
00061 #include "nsIOutputStream.h"
00062 #include "nsIStreamConverter.h"
00063 #include "nsICategoryManager.h"
00064 #include "nsXPCOM.h"
00065 #include "nsISupportsPrimitives.h"
00066 #include "nsXPIDLString.h"
00067 
00069 // nsISupports methods
00070 NS_IMPL_THREADSAFE_ISUPPORTS1(nsStreamConverterService, nsIStreamConverterService)
00071 
00072 
00073 
00074 // nsIStreamConverterService methods
00075 
00076 
00077 // nsStreamConverterService methods
00078 nsStreamConverterService::nsStreamConverterService() : mAdjacencyList(nsnull) {
00079 }
00080 
00081 nsStreamConverterService::~nsStreamConverterService() {
00082     NS_ASSERTION(mAdjacencyList, "init wasn't called, or the retval was ignored");
00083     delete mAdjacencyList;
00084 }
00085 
00086 // Delete all the entries in the adjacency list
00087 static PRBool PR_CALLBACK DeleteAdjacencyEntry(nsHashKey *aKey, void *aData, void* closure) {
00088     SCTableData *entry = (SCTableData*)aData;
00089     NS_ASSERTION(entry->key && entry->data.edges, "malformed adjacency list entry");
00090     delete entry->key;
00091     delete entry->data.edges;
00092     delete entry;
00093     return PR_TRUE;   
00094 }
00095 
00096 nsresult
00097 nsStreamConverterService::Init() {
00098     mAdjacencyList = new nsObjectHashtable(nsnull, nsnull,
00099                                            DeleteAdjacencyEntry, nsnull);
00100     if (!mAdjacencyList) return NS_ERROR_OUT_OF_MEMORY;
00101     return NS_OK;
00102 }
00103 
00104 // Builds the graph represented as an adjacency list (and built up in 
00105 // memory using an nsObjectHashtable and nsISupportsArray combination).
00106 //
00107 // :BuildGraph() consults the category manager for all stream converter 
00108 // CONTRACTIDS then fills the adjacency list with edges.
00109 // An edge in this case is comprised of a FROM and TO MIME type combination.
00110 // 
00111 // CONTRACTID format:
00112 // @mozilla.org/streamconv;1?from=text/html&to=text/plain
00113 // XXX curently we only handle a single from and to combo, we should repeat the 
00114 // XXX registration process for any series of from-to combos.
00115 // XXX can use nsTokenizer for this.
00116 //
00117 
00118 nsresult
00119 nsStreamConverterService::BuildGraph() {
00120 
00121     nsresult rv;
00122 
00123     nsCOMPtr<nsICategoryManager> catmgr(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
00124     if (NS_FAILED(rv)) return rv;
00125 
00126     nsCOMPtr<nsISimpleEnumerator> entries;
00127     rv = catmgr->EnumerateCategory(NS_ISTREAMCONVERTER_KEY, getter_AddRefs(entries));
00128     if (NS_FAILED(rv)) return rv;
00129 
00130     // go through each entry to build the graph
00131     nsCOMPtr<nsISupportsCString> entry;
00132     rv = entries->GetNext(getter_AddRefs(entry));
00133     while (NS_SUCCEEDED(rv)) {
00134 
00135         // get the entry string
00136         nsCAutoString entryString;
00137         rv = entry->GetData(entryString);
00138         if (NS_FAILED(rv)) return rv;
00139         
00140         // cobble the entry string w/ the converter key to produce a full contractID.
00141         nsCAutoString contractID(NS_ISTREAMCONVERTER_KEY);
00142         contractID.Append(entryString);
00143 
00144         // now we've got the CONTRACTID, let's parse it up.
00145         rv = AddAdjacency(contractID.get());
00146         if (NS_FAILED(rv)) return rv;
00147 
00148         rv = entries->GetNext(getter_AddRefs(entry));
00149     }
00150 
00151     return NS_OK;
00152 }
00153 
00154 
00155 // XXX currently you can not add the same adjacency (i.e. you can't have multiple
00156 // XXX stream converters registering to handle the same from-to combination. It's
00157 // XXX not programatically prohibited, it's just that results are un-predictable
00158 // XXX right now.
00159 nsresult
00160 nsStreamConverterService::AddAdjacency(const char *aContractID) {
00161     nsresult rv;
00162     // first parse out the FROM and TO MIME-types.
00163 
00164     nsCAutoString fromStr, toStr;
00165     rv = ParseFromTo(aContractID, fromStr, toStr);
00166     if (NS_FAILED(rv)) return rv;
00167 
00168     // Each MIME-type is a vertex in the graph, so first lets make sure
00169     // each MIME-type is represented as a key in our hashtable.
00170 
00171     nsCStringKey fromKey(fromStr);
00172     SCTableData *fromEdges = (SCTableData*)mAdjacencyList->Get(&fromKey);
00173     if (!fromEdges) {
00174         // There is no fromStr vertex, create one.
00175 
00176         nsCStringKey *newFromKey = new nsCStringKey(ToNewCString(fromStr), fromStr.Length(), nsCStringKey::OWN);
00177         if (!newFromKey) return NS_ERROR_OUT_OF_MEMORY;
00178 
00179         SCTableData *data = new SCTableData(newFromKey);
00180         if (!data) {
00181             delete newFromKey;
00182             return NS_ERROR_OUT_OF_MEMORY;
00183         }
00184 
00185         nsCOMArray<nsIAtom>* edgeArray = new nsCOMArray<nsIAtom>;
00186         if (!edgeArray) {
00187             delete newFromKey;
00188             data->key = nsnull;
00189             delete data;
00190             return NS_ERROR_OUT_OF_MEMORY;
00191         }
00192         data->data.edges = edgeArray;
00193 
00194         mAdjacencyList->Put(newFromKey, data);
00195         fromEdges = data;
00196     }
00197 
00198     nsCStringKey toKey(toStr);
00199     if (!mAdjacencyList->Get(&toKey)) {
00200         // There is no toStr vertex, create one.
00201         nsCStringKey *newToKey = new nsCStringKey(ToNewCString(toStr), toStr.Length(), nsCStringKey::OWN);
00202         if (!newToKey) return NS_ERROR_OUT_OF_MEMORY;
00203 
00204         SCTableData *data = new SCTableData(newToKey);
00205         if (!data) {
00206             delete newToKey;
00207             return NS_ERROR_OUT_OF_MEMORY;
00208         }
00209 
00210         nsCOMArray<nsIAtom>* edgeArray = new nsCOMArray<nsIAtom>;
00211         if (!edgeArray) {
00212             delete newToKey;
00213             data->key = nsnull;
00214             delete data;
00215             return NS_ERROR_OUT_OF_MEMORY;
00216         }
00217         data->data.edges = edgeArray;
00218         mAdjacencyList->Put(newToKey, data);
00219     }
00220     
00221     // Now we know the FROM and TO types are represented as keys in the hashtable.
00222     // Let's "connect" the verticies, making an edge.
00223 
00224     nsCOMPtr<nsIAtom> vertex = do_GetAtom(toStr.get()); 
00225     if (!vertex) return NS_ERROR_OUT_OF_MEMORY;
00226 
00227     NS_ASSERTION(fromEdges, "something wrong in adjacency list construction");
00228     if (!fromEdges)
00229         return NS_ERROR_FAILURE;
00230 
00231     nsCOMArray<nsIAtom> *adjacencyList = fromEdges->data.edges;
00232     return adjacencyList->AppendObject(vertex) ? NS_OK : NS_ERROR_FAILURE;
00233 }
00234 
00235 nsresult
00236 nsStreamConverterService::ParseFromTo(const char *aContractID, nsCString &aFromRes, nsCString &aToRes) {
00237 
00238     nsCAutoString ContractIDStr(aContractID);
00239 
00240     PRInt32 fromLoc = ContractIDStr.Find("from=");
00241     PRInt32 toLoc   = ContractIDStr.Find("to=");
00242     if (-1 == fromLoc || -1 == toLoc ) return NS_ERROR_FAILURE;
00243 
00244     fromLoc = fromLoc + 5;
00245     toLoc = toLoc + 3;
00246 
00247     nsCAutoString fromStr, toStr;
00248 
00249     ContractIDStr.Mid(fromStr, fromLoc, toLoc - 4 - fromLoc);
00250     ContractIDStr.Mid(toStr, toLoc, ContractIDStr.Length() - toLoc);
00251 
00252     aFromRes.Assign(fromStr);
00253     aToRes.Assign(toStr);
00254 
00255     return NS_OK;
00256 }
00257 
00258 // nsObjectHashtable enumerator functions.
00259 
00260 // Initializes the BFS state table.
00261 static PRBool PR_CALLBACK InitBFSTable(nsHashKey *aKey, void *aData, void* closure) {
00262     NS_ASSERTION((SCTableData*)aData, "no data in the table enumeration");
00263     
00264     nsHashtable *BFSTable = (nsHashtable*)closure;
00265     if (!BFSTable) return PR_FALSE;
00266 
00267     BFSState *state = new BFSState;
00268     if (!state) return PR_FALSE;
00269 
00270     state->color = white;
00271     state->distance = -1;
00272     state->predecessor = nsnull;
00273 
00274     SCTableData *data = new SCTableData(NS_STATIC_CAST(nsCStringKey*, aKey));
00275     if (!data) {
00276         delete state;
00277         return PR_FALSE;
00278     }
00279     data->data.state = state;
00280 
00281     BFSTable->Put(aKey, data);
00282     return PR_TRUE;   
00283 }
00284 
00285 // cleans up the BFS state table
00286 static PRBool PR_CALLBACK DeleteBFSEntry(nsHashKey *aKey, void *aData, void *closure) {
00287     SCTableData *data = (SCTableData*)aData;
00288     BFSState *state = data->data.state;
00289     delete state;
00290     data->key = nsnull;
00291     delete data;
00292     return PR_TRUE;
00293 }
00294 
00295 class CStreamConvDeallocator : public nsDequeFunctor {
00296 public:
00297     virtual void* operator()(void* anObject) {
00298         nsCStringKey *key = (nsCStringKey*)anObject;
00299         delete key;
00300         return 0;
00301     }
00302 };
00303 
00304 // walks the graph using a breadth-first-search algorithm which generates a discovered
00305 // verticies tree. This tree is then walked up (from destination vertex, to origin vertex)
00306 // and each link in the chain is added to an nsStringArray. A direct lookup for the given
00307 // CONTRACTID should be made prior to calling this method in an attempt to find a direct
00308 // converter rather than walking the graph.
00309 nsresult
00310 nsStreamConverterService::FindConverter(const char *aContractID, nsCStringArray **aEdgeList) {
00311     nsresult rv;
00312     if (!aEdgeList) return NS_ERROR_NULL_POINTER;
00313     *aEdgeList = nsnull;
00314 
00315     // walk the graph in search of the appropriate converter.
00316 
00317     PRInt32 vertexCount = mAdjacencyList->Count();
00318     if (0 >= vertexCount) return NS_ERROR_FAILURE;
00319 
00320     // Create a corresponding color table for each vertex in the graph.
00321     nsObjectHashtable lBFSTable(nsnull, nsnull, DeleteBFSEntry, nsnull);
00322     mAdjacencyList->Enumerate(InitBFSTable, &lBFSTable);
00323 
00324     NS_ASSERTION(lBFSTable.Count() == vertexCount, "strmconv BFS table init problem");
00325 
00326     // This is our source vertex; our starting point.
00327     nsCAutoString fromC, toC;
00328     rv = ParseFromTo(aContractID, fromC, toC);
00329     if (NS_FAILED(rv)) return rv;
00330 
00331     nsCStringKey *source = new nsCStringKey(fromC.get());
00332     if (!source) return NS_ERROR_OUT_OF_MEMORY;
00333 
00334     SCTableData *data = (SCTableData*)lBFSTable.Get(source);
00335     if (!data) {
00336         delete source;
00337         return NS_ERROR_FAILURE;
00338     }
00339 
00340     BFSState *state = data->data.state;
00341 
00342     state->color = gray;
00343     state->distance = 0;
00344     CStreamConvDeallocator *dtorFunc = new CStreamConvDeallocator();
00345     if (!dtorFunc) {
00346         delete source;
00347         return NS_ERROR_OUT_OF_MEMORY;
00348     }
00349 
00350     nsDeque grayQ(dtorFunc);
00351 
00352     // Now generate the shortest path tree.
00353     grayQ.Push(source);
00354     while (0 < grayQ.GetSize()) {
00355         nsCStringKey *currentHead = (nsCStringKey*)grayQ.PeekFront();
00356         SCTableData *data2 = (SCTableData*)mAdjacencyList->Get(currentHead);
00357         if (!data2) return NS_ERROR_FAILURE;
00358 
00359         nsCOMArray<nsIAtom> *edges = data2->data.edges;
00360         NS_ASSERTION(edges, "something went wrong with BFS strmconv algorithm");
00361         if (!edges) return NS_ERROR_FAILURE;
00362 
00363         // Get the state of the current head to calculate the distance of each
00364         // reachable vertex in the loop.
00365         data2 = (SCTableData*)lBFSTable.Get(currentHead);
00366         if (!data2) return NS_ERROR_FAILURE;
00367 
00368         BFSState *headVertexState = data2->data.state;
00369         NS_ASSERTION(headVertexState, "problem with the BFS strmconv algorithm");
00370         if (!headVertexState) return NS_ERROR_FAILURE;
00371 
00372         PRInt32 edgeCount = edges->Count();
00373 
00374         for (PRInt32 i = 0; i < edgeCount; i++) {
00375             nsIAtom* curVertexAtom = edges->ObjectAt(i);
00376             nsAutoString curVertexStr;
00377             curVertexAtom->ToString(curVertexStr);
00378             nsCStringKey *curVertex = new nsCStringKey(ToNewCString(curVertexStr), 
00379                                         curVertexStr.Length(), nsCStringKey::OWN);
00380             if (!curVertex) return NS_ERROR_OUT_OF_MEMORY;
00381 
00382             SCTableData *data3 = (SCTableData*)lBFSTable.Get(curVertex);
00383             if (!data3) {
00384                 delete curVertex;
00385                 return NS_ERROR_FAILURE;
00386             }
00387             BFSState *curVertexState = data3->data.state;
00388             NS_ASSERTION(curVertexState, "something went wrong with the BFS strmconv algorithm");
00389             if (!curVertexState) return NS_ERROR_FAILURE;
00390 
00391             if (white == curVertexState->color) {
00392                 curVertexState->color = gray;
00393                 curVertexState->distance = headVertexState->distance + 1;
00394                 curVertexState->predecessor = (nsCStringKey*)currentHead->Clone();
00395                 if (!curVertexState->predecessor) {
00396                     delete curVertex;
00397                     return NS_ERROR_OUT_OF_MEMORY;
00398                 }
00399                 grayQ.Push(curVertex);
00400             } else {
00401                 delete curVertex; // if this vertex has already been discovered, we don't want
00402                                   // to leak it. (non-discovered vertex's get cleaned up when
00403                                   // they're popped).
00404             }
00405         }
00406         headVertexState->color = black;
00407         nsCStringKey *cur = (nsCStringKey*)grayQ.PopFront();
00408         delete cur;
00409         cur = nsnull;
00410     }
00411     // The shortest path (if any) has been generated and is represetned by the chain of 
00412     // BFSState->predecessor keys. Start at the bottom and work our way up.
00413 
00414     // first parse out the FROM and TO MIME-types being registered.
00415 
00416     nsCAutoString fromStr, toStr;
00417     rv = ParseFromTo(aContractID, fromStr, toStr);
00418     if (NS_FAILED(rv)) return rv;
00419 
00420     // get the root CONTRACTID
00421     nsCAutoString ContractIDPrefix(NS_ISTREAMCONVERTER_KEY);
00422     nsCStringArray *shortestPath = new nsCStringArray();
00423     if (!shortestPath) return NS_ERROR_OUT_OF_MEMORY;
00424 
00425     nsCStringKey toMIMEType(toStr);
00426     data = (SCTableData*)lBFSTable.Get(&toMIMEType);
00427     if (!data) {
00428         // If this vertex isn't in the BFSTable, then no-one has registered for it,
00429         // therefore we can't do the conversion.
00430         delete shortestPath;
00431         return NS_ERROR_FAILURE;
00432     }
00433 
00434     while (data) {
00435         BFSState *curState = data->data.state;
00436 
00437         nsCStringKey *key = data->key;
00438         
00439         if (fromStr.Equals(key->GetString())) {
00440             // found it. We're done here.
00441             *aEdgeList = shortestPath;
00442             return NS_OK;
00443         }
00444 
00445         // reconstruct the CONTRACTID.
00446         // Get the predecessor.
00447         if (!curState->predecessor) break; // no predecessor
00448         SCTableData *predecessorData = (SCTableData*)lBFSTable.Get(curState->predecessor);
00449 
00450         if (!predecessorData) break; // no predecessor, chain doesn't exist.
00451 
00452         // build out the CONTRACTID.
00453         nsCAutoString newContractID(ContractIDPrefix);
00454         newContractID.AppendLiteral("?from=");
00455 
00456         nsCStringKey *predecessorKey = predecessorData->key;
00457         newContractID.Append(predecessorKey->GetString());
00458 
00459         newContractID.AppendLiteral("&to=");
00460         newContractID.Append(key->GetString());
00461     
00462         // Add this CONTRACTID to the chain.
00463         rv = shortestPath->AppendCString(newContractID) ? NS_OK : NS_ERROR_FAILURE;  // XXX this method incorrectly returns a bool
00464         NS_ASSERTION(NS_SUCCEEDED(rv), "AppendElement failed");
00465 
00466         // move up the tree.
00467         data = predecessorData;
00468     }
00469     delete shortestPath;
00470     return NS_ERROR_FAILURE; // couldn't find a stream converter or chain.
00471 }
00472 
00473 
00475 // nsIStreamConverter methods
00476 NS_IMETHODIMP
00477 nsStreamConverterService::Convert(nsIInputStream *aFromStream,
00478                                   const char *aFromType, 
00479                                   const char *aToType,
00480                                   nsISupports *aContext,
00481                                   nsIInputStream **_retval) {
00482     if (!aFromStream || !aFromType || !aToType || !_retval) return NS_ERROR_NULL_POINTER;
00483     nsresult rv;
00484 
00485     // first determine whether we can even handle this conversion
00486     // build a CONTRACTID
00487     nsCAutoString contractID;
00488     contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
00489     contractID.Append(aFromType);
00490     contractID.AppendLiteral("&to=");
00491     contractID.Append(aToType);
00492     const char *cContractID = contractID.get();
00493 
00494     nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(cContractID, &rv));
00495     if (NS_FAILED(rv)) {
00496         // couldn't go direct, let's try walking the graph of converters.
00497         rv = BuildGraph();
00498         if (NS_FAILED(rv)) return rv;
00499 
00500         nsCStringArray *converterChain = nsnull;
00501 
00502         rv = FindConverter(cContractID, &converterChain);
00503         if (NS_FAILED(rv)) {
00504             // can't make this conversion.
00505             // XXX should have a more descriptive error code.
00506             return NS_ERROR_FAILURE;
00507         }
00508 
00509         PRInt32 edgeCount = converterChain->Count();
00510         NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
00511 
00512 
00513         // convert the stream using each edge of the graph as a step.
00514         // this is our stream conversion traversal.
00515         nsCOMPtr<nsIInputStream> dataToConvert = aFromStream;
00516         nsCOMPtr<nsIInputStream> convertedData;
00517 
00518         for (PRInt32 i = edgeCount-1; i >= 0; i--) {
00519             nsCString *contractIDStr = converterChain->CStringAt(i);
00520             if (!contractIDStr) {
00521                 delete converterChain;
00522                 return NS_ERROR_FAILURE;
00523             }
00524             const char *lContractID = contractIDStr->get();
00525 
00526             converter = do_CreateInstance(lContractID, &rv);
00527 
00528             if (NS_FAILED(rv)) {
00529                 delete converterChain;                
00530                 return rv;
00531             }
00532 
00533             nsCAutoString fromStr, toStr;
00534             rv = ParseFromTo(lContractID, fromStr, toStr);
00535             if (NS_FAILED(rv)) {
00536                 delete converterChain;
00537                 return rv;
00538             }
00539 
00540             rv = converter->Convert(dataToConvert, fromStr.get(), toStr.get(), aContext, getter_AddRefs(convertedData));
00541             dataToConvert = convertedData;
00542             if (NS_FAILED(rv)) {
00543                 delete converterChain;
00544                 return rv;
00545             }
00546         }
00547 
00548         delete converterChain;
00549         *_retval = convertedData;
00550         NS_ADDREF(*_retval);
00551 
00552     } else {
00553         // we're going direct.
00554         rv = converter->Convert(aFromStream, aFromType, aToType, aContext, _retval);
00555     }
00556     
00557     return rv;
00558 }
00559 
00560 
00561 NS_IMETHODIMP
00562 nsStreamConverterService::AsyncConvertData(const char *aFromType, 
00563                                            const char *aToType, 
00564                                            nsIStreamListener *aListener,
00565                                            nsISupports *aContext,
00566                                            nsIStreamListener **_retval) {
00567     if (!aFromType || !aToType || !aListener || !_retval) return NS_ERROR_NULL_POINTER;
00568 
00569     nsresult rv;
00570 
00571     // first determine whether we can even handle this conversion
00572     // build a CONTRACTID
00573     nsCAutoString contractID;
00574     contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
00575     contractID.Append(aFromType);
00576     contractID.AppendLiteral("&to=");
00577     contractID.Append(aToType);
00578     const char *cContractID = contractID.get();
00579 
00580     nsCOMPtr<nsIStreamConverter> listener(do_CreateInstance(cContractID, &rv));
00581     if (NS_FAILED(rv)) {
00582         // couldn't go direct, let's try walking the graph of converters.
00583         rv = BuildGraph();
00584         if (NS_FAILED(rv)) return rv;
00585 
00586         nsCStringArray *converterChain = nsnull;
00587 
00588         rv = FindConverter(cContractID, &converterChain);
00589         if (NS_FAILED(rv)) {
00590             // can't make this conversion.
00591             // XXX should have a more descriptive error code.
00592             return NS_ERROR_FAILURE;
00593         }
00594 
00595         // aListener is the listener that wants the final, converted, data.
00596         // we initialize finalListener w/ aListener so it gets put at the 
00597         // tail end of the chain, which in the loop below, means the *first*
00598         // converter created.
00599         nsCOMPtr<nsIStreamListener> finalListener = aListener;
00600 
00601         // convert the stream using each edge of the graph as a step.
00602         // this is our stream conversion traversal.
00603         PRInt32 edgeCount = converterChain->Count();
00604         NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
00605         for (int i = 0; i < edgeCount; i++) {
00606             nsCString *contractIDStr = converterChain->CStringAt(i);
00607             if (!contractIDStr) {
00608                 delete converterChain;
00609                 return NS_ERROR_FAILURE;
00610             }
00611             const char *lContractID = contractIDStr->get();
00612 
00613             // create the converter for this from/to pair
00614             nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(lContractID, &rv));
00615             NS_ASSERTION(NS_SUCCEEDED(rv), "graph construction problem, built a contractid that wasn't registered");
00616 
00617             nsCAutoString fromStr, toStr;
00618             rv = ParseFromTo(lContractID, fromStr, toStr);
00619             if (NS_FAILED(rv)) {
00620                 delete converterChain;
00621                 return rv;
00622             }
00623 
00624             // connect the converter w/ the listener that should get the converted data.
00625             rv = converter->AsyncConvertData(fromStr.get(), toStr.get(), finalListener, aContext);
00626             if (NS_FAILED(rv)) {
00627                 delete converterChain;
00628                 return rv;
00629             }
00630 
00631             nsCOMPtr<nsIStreamListener> chainListener(do_QueryInterface(converter, &rv));
00632             if (NS_FAILED(rv)) {
00633                 delete converterChain;
00634                 return rv;
00635             }
00636 
00637             // the last iteration of this loop will result in finalListener
00638             // pointing to the converter that "starts" the conversion chain.
00639             // this converter's "from" type is the original "from" type. Prior
00640             // to the last iteration, finalListener will continuously be wedged
00641             // into the next listener in the chain, then be updated.
00642             finalListener = chainListener;
00643         }
00644         delete converterChain;
00645         // return the first listener in the chain.
00646         *_retval = finalListener;
00647         NS_ADDREF(*_retval);
00648 
00649     } else {
00650         // we're going direct.
00651         *_retval = listener;
00652         NS_ADDREF(*_retval);
00653 
00654         rv = listener->AsyncConvertData(aFromType, aToType, aListener, aContext);
00655     }
00656     
00657     return rv;
00658 
00659 }
00660 
00661 nsresult
00662 NS_NewStreamConv(nsStreamConverterService** aStreamConv)
00663 {
00664     NS_PRECONDITION(aStreamConv != nsnull, "null ptr");
00665     if (!aStreamConv) return NS_ERROR_NULL_POINTER;
00666 
00667     *aStreamConv = new nsStreamConverterService();
00668     if (!*aStreamConv) return NS_ERROR_OUT_OF_MEMORY;
00669 
00670     NS_ADDREF(*aStreamConv);
00671     nsresult rv = (*aStreamConv)->Init();
00672     if (NS_FAILED(rv))
00673         NS_RELEASE(*aStreamConv);
00674 
00675     return rv;
00676 }