1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 : *
3 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Netscape Communications Corporation.
20 : * Portions created by the Initial Developer are Copyright (C) 1998
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK *****
38 : *
39 : *
40 : * This Original Code has been modified by IBM Corporation.
41 : * Modifications made by IBM described herein are
42 : * Copyright (c) International Business Machines
43 : * Corporation, 2000
44 : *
45 : * Modifications to Mozilla code or documentation
46 : * identified per MPL Section 3.3
47 : *
48 : * Date Modified by Description of modification
49 : * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
50 : * use in OS2
51 : */
52 :
53 : #include "nsStreamConverterService.h"
54 : #include "nsIServiceManager.h"
55 : #include "nsIComponentManager.h"
56 : #include "nsIComponentRegistrar.h"
57 : #include "nsString.h"
58 : #include "nsReadableUtils.h"
59 : #include "nsIAtom.h"
60 : #include "nsDeque.h"
61 : #include "nsIInputStream.h"
62 : #include "nsIOutputStream.h"
63 : #include "nsIStreamConverter.h"
64 : #include "nsICategoryManager.h"
65 : #include "nsXPCOM.h"
66 : #include "nsISupportsPrimitives.h"
67 : #include "nsXPIDLString.h"
68 :
69 : ////////////////////////////////////////////////////////////
70 : // nsISupports methods
71 5388 : NS_IMPL_ISUPPORTS1(nsStreamConverterService, nsIStreamConverterService)
72 :
73 :
74 : ////////////////////////////////////////////////////////////
75 : // nsIStreamConverterService methods
76 :
77 : ////////////////////////////////////////////////////////////
78 : // nsStreamConverterService methods
79 80 : nsStreamConverterService::nsStreamConverterService() : mAdjacencyList(nsnull) {
80 80 : }
81 :
82 160 : nsStreamConverterService::~nsStreamConverterService() {
83 80 : NS_ASSERTION(mAdjacencyList, "init wasn't called, or the retval was ignored");
84 80 : delete mAdjacencyList;
85 320 : }
86 :
87 : // Delete all the entries in the adjacency list
88 0 : static bool DeleteAdjacencyEntry(nsHashKey *aKey, void *aData, void* closure) {
89 0 : SCTableData *entry = (SCTableData*)aData;
90 0 : NS_ASSERTION(entry->key && entry->data.edges, "malformed adjacency list entry");
91 0 : delete entry->key;
92 0 : delete entry->data.edges;
93 : delete entry;
94 0 : return true;
95 : }
96 :
97 : nsresult
98 80 : nsStreamConverterService::Init() {
99 : mAdjacencyList = new nsObjectHashtable(nsnull, nsnull,
100 80 : DeleteAdjacencyEntry, nsnull);
101 80 : if (!mAdjacencyList) return NS_ERROR_OUT_OF_MEMORY;
102 80 : return NS_OK;
103 : }
104 :
105 : // Builds the graph represented as an adjacency list (and built up in
106 : // memory using an nsObjectHashtable and nsISupportsArray combination).
107 : //
108 : // :BuildGraph() consults the category manager for all stream converter
109 : // CONTRACTIDS then fills the adjacency list with edges.
110 : // An edge in this case is comprised of a FROM and TO MIME type combination.
111 : //
112 : // CONTRACTID format:
113 : // @mozilla.org/streamconv;1?from=text/html&to=text/plain
114 : // XXX curently we only handle a single from and to combo, we should repeat the
115 : // XXX registration process for any series of from-to combos.
116 : // XXX can use nsTokenizer for this.
117 : //
118 :
119 : nsresult
120 0 : nsStreamConverterService::BuildGraph() {
121 :
122 : nsresult rv;
123 :
124 0 : nsCOMPtr<nsICategoryManager> catmgr(do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
125 0 : if (NS_FAILED(rv)) return rv;
126 :
127 0 : nsCOMPtr<nsISimpleEnumerator> entries;
128 0 : rv = catmgr->EnumerateCategory(NS_ISTREAMCONVERTER_KEY, getter_AddRefs(entries));
129 0 : if (NS_FAILED(rv)) return rv;
130 :
131 : // go through each entry to build the graph
132 0 : nsCOMPtr<nsISupportsCString> entry;
133 0 : rv = entries->GetNext(getter_AddRefs(entry));
134 0 : while (NS_SUCCEEDED(rv)) {
135 :
136 : // get the entry string
137 0 : nsCAutoString entryString;
138 0 : rv = entry->GetData(entryString);
139 0 : if (NS_FAILED(rv)) return rv;
140 :
141 : // cobble the entry string w/ the converter key to produce a full contractID.
142 0 : nsCAutoString contractID(NS_ISTREAMCONVERTER_KEY);
143 0 : contractID.Append(entryString);
144 :
145 : // now we've got the CONTRACTID, let's parse it up.
146 0 : rv = AddAdjacency(contractID.get());
147 0 : if (NS_FAILED(rv)) return rv;
148 :
149 0 : rv = entries->GetNext(getter_AddRefs(entry));
150 : }
151 :
152 0 : return NS_OK;
153 : }
154 :
155 :
156 : // XXX currently you can not add the same adjacency (i.e. you can't have multiple
157 : // XXX stream converters registering to handle the same from-to combination. It's
158 : // XXX not programatically prohibited, it's just that results are un-predictable
159 : // XXX right now.
160 : nsresult
161 0 : nsStreamConverterService::AddAdjacency(const char *aContractID) {
162 : nsresult rv;
163 : // first parse out the FROM and TO MIME-types.
164 :
165 0 : nsCAutoString fromStr, toStr;
166 0 : rv = ParseFromTo(aContractID, fromStr, toStr);
167 0 : if (NS_FAILED(rv)) return rv;
168 :
169 : // Each MIME-type is a vertex in the graph, so first lets make sure
170 : // each MIME-type is represented as a key in our hashtable.
171 :
172 0 : nsCStringKey fromKey(fromStr);
173 0 : SCTableData *fromEdges = (SCTableData*)mAdjacencyList->Get(&fromKey);
174 0 : if (!fromEdges) {
175 : // There is no fromStr vertex, create one.
176 :
177 0 : nsCStringKey *newFromKey = new nsCStringKey(ToNewCString(fromStr), fromStr.Length(), nsCStringKey::OWN);
178 0 : if (!newFromKey) return NS_ERROR_OUT_OF_MEMORY;
179 :
180 0 : SCTableData *data = new SCTableData(newFromKey);
181 0 : if (!data) {
182 0 : delete newFromKey;
183 0 : return NS_ERROR_OUT_OF_MEMORY;
184 : }
185 :
186 0 : nsCOMArray<nsIAtom>* edgeArray = new nsCOMArray<nsIAtom>;
187 0 : if (!edgeArray) {
188 0 : delete newFromKey;
189 0 : data->key = nsnull;
190 : delete data;
191 0 : return NS_ERROR_OUT_OF_MEMORY;
192 : }
193 0 : data->data.edges = edgeArray;
194 :
195 0 : mAdjacencyList->Put(newFromKey, data);
196 0 : fromEdges = data;
197 : }
198 :
199 0 : nsCStringKey toKey(toStr);
200 0 : if (!mAdjacencyList->Get(&toKey)) {
201 : // There is no toStr vertex, create one.
202 0 : nsCStringKey *newToKey = new nsCStringKey(ToNewCString(toStr), toStr.Length(), nsCStringKey::OWN);
203 0 : if (!newToKey) return NS_ERROR_OUT_OF_MEMORY;
204 :
205 0 : SCTableData *data = new SCTableData(newToKey);
206 0 : if (!data) {
207 0 : delete newToKey;
208 0 : return NS_ERROR_OUT_OF_MEMORY;
209 : }
210 :
211 0 : nsCOMArray<nsIAtom>* edgeArray = new nsCOMArray<nsIAtom>;
212 0 : if (!edgeArray) {
213 0 : delete newToKey;
214 0 : data->key = nsnull;
215 : delete data;
216 0 : return NS_ERROR_OUT_OF_MEMORY;
217 : }
218 0 : data->data.edges = edgeArray;
219 0 : mAdjacencyList->Put(newToKey, data);
220 : }
221 :
222 : // Now we know the FROM and TO types are represented as keys in the hashtable.
223 : // Let's "connect" the verticies, making an edge.
224 :
225 0 : nsCOMPtr<nsIAtom> vertex = do_GetAtom(toStr);
226 0 : if (!vertex) return NS_ERROR_OUT_OF_MEMORY;
227 :
228 0 : NS_ASSERTION(fromEdges, "something wrong in adjacency list construction");
229 0 : if (!fromEdges)
230 0 : return NS_ERROR_FAILURE;
231 :
232 0 : nsCOMArray<nsIAtom> *adjacencyList = fromEdges->data.edges;
233 0 : return adjacencyList->AppendObject(vertex) ? NS_OK : NS_ERROR_FAILURE;
234 : }
235 :
236 : nsresult
237 0 : nsStreamConverterService::ParseFromTo(const char *aContractID, nsCString &aFromRes, nsCString &aToRes) {
238 :
239 0 : nsCAutoString ContractIDStr(aContractID);
240 :
241 0 : PRInt32 fromLoc = ContractIDStr.Find("from=");
242 0 : PRInt32 toLoc = ContractIDStr.Find("to=");
243 0 : if (-1 == fromLoc || -1 == toLoc ) return NS_ERROR_FAILURE;
244 :
245 0 : fromLoc = fromLoc + 5;
246 0 : toLoc = toLoc + 3;
247 :
248 0 : nsCAutoString fromStr, toStr;
249 :
250 0 : ContractIDStr.Mid(fromStr, fromLoc, toLoc - 4 - fromLoc);
251 0 : ContractIDStr.Mid(toStr, toLoc, ContractIDStr.Length() - toLoc);
252 :
253 0 : aFromRes.Assign(fromStr);
254 0 : aToRes.Assign(toStr);
255 :
256 0 : return NS_OK;
257 : }
258 :
259 : // nsObjectHashtable enumerator functions.
260 :
261 : // Initializes the BFS state table.
262 0 : static bool InitBFSTable(nsHashKey *aKey, void *aData, void* closure) {
263 0 : NS_ASSERTION((SCTableData*)aData, "no data in the table enumeration");
264 :
265 0 : nsHashtable *BFSTable = (nsHashtable*)closure;
266 0 : if (!BFSTable) return false;
267 :
268 0 : BFSState *state = new BFSState;
269 0 : if (!state) return false;
270 :
271 0 : state->color = white;
272 0 : state->distance = -1;
273 0 : state->predecessor = nsnull;
274 :
275 0 : SCTableData *data = new SCTableData(static_cast<nsCStringKey*>(aKey));
276 0 : if (!data) {
277 0 : delete state;
278 0 : return false;
279 : }
280 0 : data->data.state = state;
281 :
282 0 : BFSTable->Put(aKey, data);
283 0 : return true;
284 : }
285 :
286 : // cleans up the BFS state table
287 0 : static bool DeleteBFSEntry(nsHashKey *aKey, void *aData, void *closure) {
288 0 : SCTableData *data = (SCTableData*)aData;
289 0 : BFSState *state = data->data.state;
290 0 : delete state;
291 0 : data->key = nsnull;
292 : delete data;
293 0 : return true;
294 : }
295 :
296 0 : class CStreamConvDeallocator : public nsDequeFunctor {
297 : public:
298 0 : virtual void* operator()(void* anObject) {
299 0 : nsCStringKey *key = (nsCStringKey*)anObject;
300 0 : delete key;
301 0 : return 0;
302 : }
303 : };
304 :
305 : // walks the graph using a breadth-first-search algorithm which generates a discovered
306 : // verticies tree. This tree is then walked up (from destination vertex, to origin vertex)
307 : // and each link in the chain is added to an nsStringArray. A direct lookup for the given
308 : // CONTRACTID should be made prior to calling this method in an attempt to find a direct
309 : // converter rather than walking the graph.
310 : nsresult
311 0 : nsStreamConverterService::FindConverter(const char *aContractID, nsTArray<nsCString> **aEdgeList) {
312 : nsresult rv;
313 0 : if (!aEdgeList) return NS_ERROR_NULL_POINTER;
314 0 : *aEdgeList = nsnull;
315 :
316 : // walk the graph in search of the appropriate converter.
317 :
318 0 : PRInt32 vertexCount = mAdjacencyList->Count();
319 0 : if (0 >= vertexCount) return NS_ERROR_FAILURE;
320 :
321 : // Create a corresponding color table for each vertex in the graph.
322 0 : nsObjectHashtable lBFSTable(nsnull, nsnull, DeleteBFSEntry, nsnull);
323 0 : mAdjacencyList->Enumerate(InitBFSTable, &lBFSTable);
324 :
325 0 : NS_ASSERTION(lBFSTable.Count() == vertexCount, "strmconv BFS table init problem");
326 :
327 : // This is our source vertex; our starting point.
328 0 : nsCAutoString fromC, toC;
329 0 : rv = ParseFromTo(aContractID, fromC, toC);
330 0 : if (NS_FAILED(rv)) return rv;
331 :
332 0 : nsCStringKey *source = new nsCStringKey(fromC.get());
333 0 : if (!source) return NS_ERROR_OUT_OF_MEMORY;
334 :
335 0 : SCTableData *data = (SCTableData*)lBFSTable.Get(source);
336 0 : if (!data) {
337 0 : delete source;
338 0 : return NS_ERROR_FAILURE;
339 : }
340 :
341 0 : BFSState *state = data->data.state;
342 :
343 0 : state->color = gray;
344 0 : state->distance = 0;
345 0 : CStreamConvDeallocator *dtorFunc = new CStreamConvDeallocator();
346 0 : if (!dtorFunc) {
347 0 : delete source;
348 0 : return NS_ERROR_OUT_OF_MEMORY;
349 : }
350 :
351 0 : nsDeque grayQ(dtorFunc);
352 :
353 : // Now generate the shortest path tree.
354 0 : grayQ.Push(source);
355 0 : while (0 < grayQ.GetSize()) {
356 0 : nsCStringKey *currentHead = (nsCStringKey*)grayQ.PeekFront();
357 0 : SCTableData *data2 = (SCTableData*)mAdjacencyList->Get(currentHead);
358 0 : if (!data2) return NS_ERROR_FAILURE;
359 :
360 0 : nsCOMArray<nsIAtom> *edges = data2->data.edges;
361 0 : NS_ASSERTION(edges, "something went wrong with BFS strmconv algorithm");
362 0 : if (!edges) return NS_ERROR_FAILURE;
363 :
364 : // Get the state of the current head to calculate the distance of each
365 : // reachable vertex in the loop.
366 0 : data2 = (SCTableData*)lBFSTable.Get(currentHead);
367 0 : if (!data2) return NS_ERROR_FAILURE;
368 :
369 0 : BFSState *headVertexState = data2->data.state;
370 0 : NS_ASSERTION(headVertexState, "problem with the BFS strmconv algorithm");
371 0 : if (!headVertexState) return NS_ERROR_FAILURE;
372 :
373 0 : PRInt32 edgeCount = edges->Count();
374 :
375 0 : for (PRInt32 i = 0; i < edgeCount; i++) {
376 0 : nsIAtom* curVertexAtom = edges->ObjectAt(i);
377 0 : nsAutoString curVertexStr;
378 0 : curVertexAtom->ToString(curVertexStr);
379 : nsCStringKey *curVertex = new nsCStringKey(ToNewCString(curVertexStr),
380 0 : curVertexStr.Length(), nsCStringKey::OWN);
381 0 : if (!curVertex) return NS_ERROR_OUT_OF_MEMORY;
382 :
383 0 : SCTableData *data3 = (SCTableData*)lBFSTable.Get(curVertex);
384 0 : if (!data3) {
385 0 : delete curVertex;
386 0 : return NS_ERROR_FAILURE;
387 : }
388 0 : BFSState *curVertexState = data3->data.state;
389 0 : NS_ASSERTION(curVertexState, "something went wrong with the BFS strmconv algorithm");
390 0 : if (!curVertexState) return NS_ERROR_FAILURE;
391 :
392 0 : if (white == curVertexState->color) {
393 0 : curVertexState->color = gray;
394 0 : curVertexState->distance = headVertexState->distance + 1;
395 0 : curVertexState->predecessor = (nsCStringKey*)currentHead->Clone();
396 0 : if (!curVertexState->predecessor) {
397 0 : delete curVertex;
398 0 : return NS_ERROR_OUT_OF_MEMORY;
399 : }
400 0 : grayQ.Push(curVertex);
401 : } else {
402 0 : delete curVertex; // if this vertex has already been discovered, we don't want
403 : // to leak it. (non-discovered vertex's get cleaned up when
404 : // they're popped).
405 : }
406 : }
407 0 : headVertexState->color = black;
408 0 : nsCStringKey *cur = (nsCStringKey*)grayQ.PopFront();
409 0 : delete cur;
410 0 : cur = nsnull;
411 : }
412 : // The shortest path (if any) has been generated and is represetned by the chain of
413 : // BFSState->predecessor keys. Start at the bottom and work our way up.
414 :
415 : // first parse out the FROM and TO MIME-types being registered.
416 :
417 0 : nsCAutoString fromStr, toStr;
418 0 : rv = ParseFromTo(aContractID, fromStr, toStr);
419 0 : if (NS_FAILED(rv)) return rv;
420 :
421 : // get the root CONTRACTID
422 0 : nsCAutoString ContractIDPrefix(NS_ISTREAMCONVERTER_KEY);
423 0 : nsTArray<nsCString> *shortestPath = new nsTArray<nsCString>();
424 0 : if (!shortestPath) return NS_ERROR_OUT_OF_MEMORY;
425 :
426 0 : nsCStringKey toMIMEType(toStr);
427 0 : data = (SCTableData*)lBFSTable.Get(&toMIMEType);
428 0 : if (!data) {
429 : // If this vertex isn't in the BFSTable, then no-one has registered for it,
430 : // therefore we can't do the conversion.
431 0 : delete shortestPath;
432 0 : return NS_ERROR_FAILURE;
433 : }
434 :
435 0 : while (data) {
436 0 : BFSState *curState = data->data.state;
437 :
438 0 : nsCStringKey *key = data->key;
439 :
440 0 : if (fromStr.Equals(key->GetString())) {
441 : // found it. We're done here.
442 0 : *aEdgeList = shortestPath;
443 0 : return NS_OK;
444 : }
445 :
446 : // reconstruct the CONTRACTID.
447 : // Get the predecessor.
448 0 : if (!curState->predecessor) break; // no predecessor
449 0 : SCTableData *predecessorData = (SCTableData*)lBFSTable.Get(curState->predecessor);
450 :
451 0 : if (!predecessorData) break; // no predecessor, chain doesn't exist.
452 :
453 : // build out the CONTRACTID.
454 0 : nsCAutoString newContractID(ContractIDPrefix);
455 0 : newContractID.AppendLiteral("?from=");
456 :
457 0 : nsCStringKey *predecessorKey = predecessorData->key;
458 0 : newContractID.Append(predecessorKey->GetString());
459 :
460 0 : newContractID.AppendLiteral("&to=");
461 0 : newContractID.Append(key->GetString());
462 :
463 : // Add this CONTRACTID to the chain.
464 0 : rv = shortestPath->AppendElement(newContractID) ? NS_OK : NS_ERROR_FAILURE; // XXX this method incorrectly returns a bool
465 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "AppendElement failed");
466 :
467 : // move up the tree.
468 0 : data = predecessorData;
469 : }
470 0 : delete shortestPath;
471 0 : return NS_ERROR_FAILURE; // couldn't find a stream converter or chain.
472 : }
473 :
474 :
475 : /////////////////////////////////////////////////////
476 : // nsIStreamConverterService methods
477 : NS_IMETHODIMP
478 0 : nsStreamConverterService::CanConvert(const char* aFromType,
479 : const char* aToType,
480 : bool* _retval) {
481 0 : nsCOMPtr<nsIComponentRegistrar> reg;
482 0 : nsresult rv = NS_GetComponentRegistrar(getter_AddRefs(reg));
483 0 : if (NS_FAILED(rv))
484 0 : return rv;
485 :
486 0 : nsCAutoString contractID;
487 0 : contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
488 0 : contractID.Append(aFromType);
489 0 : contractID.AppendLiteral("&to=");
490 0 : contractID.Append(aToType);
491 :
492 : // See if we have a direct match
493 0 : rv = reg->IsContractIDRegistered(contractID.get(), _retval);
494 0 : if (NS_FAILED(rv))
495 0 : return rv;
496 0 : if (*_retval)
497 0 : return NS_OK;
498 :
499 : // Otherwise try the graph.
500 0 : rv = BuildGraph();
501 0 : if (NS_FAILED(rv))
502 0 : return rv;
503 :
504 0 : nsTArray<nsCString> *converterChain = nsnull;
505 0 : rv = FindConverter(contractID.get(), &converterChain);
506 0 : *_retval = NS_SUCCEEDED(rv);
507 :
508 0 : delete converterChain;
509 0 : return NS_OK;
510 : }
511 :
512 : NS_IMETHODIMP
513 0 : nsStreamConverterService::Convert(nsIInputStream *aFromStream,
514 : const char *aFromType,
515 : const char *aToType,
516 : nsISupports *aContext,
517 : nsIInputStream **_retval) {
518 0 : if (!aFromStream || !aFromType || !aToType || !_retval) return NS_ERROR_NULL_POINTER;
519 : nsresult rv;
520 :
521 : // first determine whether we can even handle this conversion
522 : // build a CONTRACTID
523 0 : nsCAutoString contractID;
524 0 : contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
525 0 : contractID.Append(aFromType);
526 0 : contractID.AppendLiteral("&to=");
527 0 : contractID.Append(aToType);
528 0 : const char *cContractID = contractID.get();
529 :
530 0 : nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(cContractID, &rv));
531 0 : if (NS_FAILED(rv)) {
532 : // couldn't go direct, let's try walking the graph of converters.
533 0 : rv = BuildGraph();
534 0 : if (NS_FAILED(rv)) return rv;
535 :
536 0 : nsTArray<nsCString> *converterChain = nsnull;
537 :
538 0 : rv = FindConverter(cContractID, &converterChain);
539 0 : if (NS_FAILED(rv)) {
540 : // can't make this conversion.
541 : // XXX should have a more descriptive error code.
542 0 : return NS_ERROR_FAILURE;
543 : }
544 :
545 0 : PRInt32 edgeCount = PRInt32(converterChain->Length());
546 0 : NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
547 :
548 :
549 : // convert the stream using each edge of the graph as a step.
550 : // this is our stream conversion traversal.
551 0 : nsCOMPtr<nsIInputStream> dataToConvert = aFromStream;
552 0 : nsCOMPtr<nsIInputStream> convertedData;
553 :
554 0 : for (PRInt32 i = edgeCount-1; i >= 0; i--) {
555 0 : const char *lContractID = converterChain->ElementAt(i).get();
556 :
557 0 : converter = do_CreateInstance(lContractID, &rv);
558 :
559 0 : if (NS_FAILED(rv)) {
560 0 : delete converterChain;
561 0 : return rv;
562 : }
563 :
564 0 : nsCAutoString fromStr, toStr;
565 0 : rv = ParseFromTo(lContractID, fromStr, toStr);
566 0 : if (NS_FAILED(rv)) {
567 0 : delete converterChain;
568 0 : return rv;
569 : }
570 :
571 0 : rv = converter->Convert(dataToConvert, fromStr.get(), toStr.get(), aContext, getter_AddRefs(convertedData));
572 0 : dataToConvert = convertedData;
573 0 : if (NS_FAILED(rv)) {
574 0 : delete converterChain;
575 0 : return rv;
576 : }
577 : }
578 :
579 0 : delete converterChain;
580 0 : *_retval = convertedData;
581 0 : NS_ADDREF(*_retval);
582 :
583 : } else {
584 : // we're going direct.
585 0 : rv = converter->Convert(aFromStream, aFromType, aToType, aContext, _retval);
586 : }
587 :
588 0 : return rv;
589 : }
590 :
591 :
592 : NS_IMETHODIMP
593 925 : nsStreamConverterService::AsyncConvertData(const char *aFromType,
594 : const char *aToType,
595 : nsIStreamListener *aListener,
596 : nsISupports *aContext,
597 : nsIStreamListener **_retval) {
598 925 : if (!aFromType || !aToType || !aListener || !_retval) return NS_ERROR_NULL_POINTER;
599 :
600 : nsresult rv;
601 :
602 : // first determine whether we can even handle this conversion
603 : // build a CONTRACTID
604 1850 : nsCAutoString contractID;
605 925 : contractID.AssignLiteral(NS_ISTREAMCONVERTER_KEY "?from=");
606 925 : contractID.Append(aFromType);
607 925 : contractID.AppendLiteral("&to=");
608 925 : contractID.Append(aToType);
609 925 : const char *cContractID = contractID.get();
610 :
611 1850 : nsCOMPtr<nsIStreamConverter> listener(do_CreateInstance(cContractID, &rv));
612 925 : if (NS_FAILED(rv)) {
613 : // couldn't go direct, let's try walking the graph of converters.
614 0 : rv = BuildGraph();
615 0 : if (NS_FAILED(rv)) return rv;
616 :
617 0 : nsTArray<nsCString> *converterChain = nsnull;
618 :
619 0 : rv = FindConverter(cContractID, &converterChain);
620 0 : if (NS_FAILED(rv)) {
621 : // can't make this conversion.
622 : // XXX should have a more descriptive error code.
623 0 : return NS_ERROR_FAILURE;
624 : }
625 :
626 : // aListener is the listener that wants the final, converted, data.
627 : // we initialize finalListener w/ aListener so it gets put at the
628 : // tail end of the chain, which in the loop below, means the *first*
629 : // converter created.
630 0 : nsCOMPtr<nsIStreamListener> finalListener = aListener;
631 :
632 : // convert the stream using each edge of the graph as a step.
633 : // this is our stream conversion traversal.
634 0 : PRInt32 edgeCount = PRInt32(converterChain->Length());
635 0 : NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
636 0 : for (int i = 0; i < edgeCount; i++) {
637 0 : const char *lContractID = converterChain->ElementAt(i).get();
638 :
639 : // create the converter for this from/to pair
640 0 : nsCOMPtr<nsIStreamConverter> converter(do_CreateInstance(lContractID));
641 0 : NS_ASSERTION(converter, "graph construction problem, built a contractid that wasn't registered");
642 :
643 0 : nsCAutoString fromStr, toStr;
644 0 : rv = ParseFromTo(lContractID, fromStr, toStr);
645 0 : if (NS_FAILED(rv)) {
646 0 : delete converterChain;
647 0 : return rv;
648 : }
649 :
650 : // connect the converter w/ the listener that should get the converted data.
651 0 : rv = converter->AsyncConvertData(fromStr.get(), toStr.get(), finalListener, aContext);
652 0 : if (NS_FAILED(rv)) {
653 0 : delete converterChain;
654 0 : return rv;
655 : }
656 :
657 0 : nsCOMPtr<nsIStreamListener> chainListener(do_QueryInterface(converter, &rv));
658 0 : if (NS_FAILED(rv)) {
659 0 : delete converterChain;
660 0 : return rv;
661 : }
662 :
663 : // the last iteration of this loop will result in finalListener
664 : // pointing to the converter that "starts" the conversion chain.
665 : // this converter's "from" type is the original "from" type. Prior
666 : // to the last iteration, finalListener will continuously be wedged
667 : // into the next listener in the chain, then be updated.
668 0 : finalListener = chainListener;
669 : }
670 0 : delete converterChain;
671 : // return the first listener in the chain.
672 0 : *_retval = finalListener;
673 0 : NS_ADDREF(*_retval);
674 :
675 : } else {
676 : // we're going direct.
677 925 : *_retval = listener;
678 925 : NS_ADDREF(*_retval);
679 :
680 925 : rv = listener->AsyncConvertData(aFromType, aToType, aListener, aContext);
681 : }
682 :
683 925 : return rv;
684 :
685 : }
686 :
687 : nsresult
688 80 : NS_NewStreamConv(nsStreamConverterService** aStreamConv)
689 : {
690 80 : NS_PRECONDITION(aStreamConv != nsnull, "null ptr");
691 80 : if (!aStreamConv) return NS_ERROR_NULL_POINTER;
692 :
693 80 : *aStreamConv = new nsStreamConverterService();
694 80 : if (!*aStreamConv) return NS_ERROR_OUT_OF_MEMORY;
695 :
696 80 : NS_ADDREF(*aStreamConv);
697 80 : nsresult rv = (*aStreamConv)->Init();
698 80 : if (NS_FAILED(rv))
699 0 : NS_RELEASE(*aStreamConv);
700 :
701 80 : return rv;
702 : }
|