Files
Mozilla/mozilla/netwerk/streamconv/src/nsStreamConverterService.cpp
dveditz%netscape.com 4eddb0ee15 Bug 23941 speed up install on Mac, r=cathleen, a=jar;
Bug 12817 no Autoreg (in optimized builds) unless xpinstall detects flag indicating install has happened or build number changed, r=dp, a=jar;
Bug 23859 add wstring API to nsIRegistry for profile manager/i18n, r=gayatrib, a=jar;


git-svn-id: svn://10.0.0.236/trunk@61370 18797224-902f-48f8-a5cc-f745e15eee43
2000-02-20 03:12:59 +00:00

712 lines
24 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*/
#include "nsStreamConverterService.h"
#include "nsIServiceManager.h"
#include "nsIComponentManager.h"
#include "nsString2.h"
#include "nsIAtom.h"
#include "nsDeque.h"
#include "nsIRegistry.h"
#include "nsIEnumerator.h"
#include "nsIBufferInputStream.h"
#include "nsIBufferOutputStream.h"
#include "nsIStreamConverter.h"
#include "nsCOMPtr.h"
////////////////////////////////////////////////////////////
// nsISupports methods
NS_IMPL_ISUPPORTS(nsStreamConverterService, NS_GET_IID(nsIStreamConverterService));
////////////////////////////////////////////////////////////
// nsIStreamConverterService methods
////////////////////////////////////////////////////////////
// nsStreamConverterService methods
nsStreamConverterService::nsStreamConverterService() {
NS_INIT_ISUPPORTS();
mAdjacencyList = nsnull;
}
// Delete all the entries in the adjacency list
PRBool DeleteAdjacencyEntry(nsHashKey *aKey, void *aData, void* closure) {
SCTableData *entry = (SCTableData*)aData;
NS_ASSERTION(entry->key && entry->keyString && entry->data, "malformed adjacency list entry");
delete entry->key;
delete entry->keyString;
// clear out the edges
nsVoidArray *edges = (nsVoidArray*)entry->data;
nsIAtom *vertex;
while ( (vertex = (nsIAtom*)edges->ElementAt(0)) ) {
NS_RELEASE(vertex);
edges->RemoveElementAt(0);
}
delete (nsVoidArray *)entry->data;
delete entry;
return PR_TRUE;
};
nsStreamConverterService::~nsStreamConverterService() {
// Clean up the adjacency list table.
mAdjacencyList->Reset(DeleteAdjacencyEntry, nsnull);
delete mAdjacencyList;
}
nsresult
nsStreamConverterService::Init() {
mAdjacencyList = new nsHashtable();
if (!mAdjacencyList) return NS_ERROR_OUT_OF_MEMORY;
return NS_OK;
}
// Builds the graph represented as an adjacency list (and built up in
// memory using an nsHashtable and nsVoidArray combination).
//
// :BuildGraph() consults the registry for all stream converter PROGIDS then fills the
// adjacency list with edges.
// An edge in this case is comprised of a FROM and TO MIME type combination.
//
// PROGID format:
// component://netscape/strmconv?from=text/html?to=text/plain
// XXX curently we only handle a single from and to combo, we should repeat the
// XXX registration process for any series of from-to combos.
// XXX can use nsTokenizer for this.
//
nsresult
nsStreamConverterService::BuildGraph() {
nsresult rv;
// enumerate the registry subkeys
nsIRegistry *registry = nsnull;
nsRegistryKey key;
nsIEnumerator *components = nsnull;
rv = nsServiceManager::GetService(NS_REGISTRY_PROGID,
NS_GET_IID(nsIRegistry),
(nsISupports**)&registry);
if (NS_FAILED(rv)) return rv;
rv = registry->OpenWellKnownRegistry(nsIRegistry::ApplicationComponentRegistry);
if (NS_FAILED(rv)) return rv;
rv = registry->GetSubtree(nsIRegistry::Common,
NS_ISTREAMCONVERTER_KEY,
&key);
if (NS_FAILED(rv)) return rv;
rv = registry->EnumerateSubtrees(key, &components);
if (NS_FAILED(rv)) return rv;
// go ahead and enumerate through.
rv = components->First();
while (NS_SUCCEEDED(rv) && (NS_OK != components->IsDone())) {
nsISupports *base = nsnull;
rv = components->CurrentItem(&base);
if (NS_FAILED(rv)) return rv;
nsIRegistryNode *node = nsnull;
nsIID nodeIID = NS_IREGISTRYNODE_IID;
rv = base->QueryInterface(nodeIID, (void**)&node);
if (NS_FAILED(rv)) return rv;
char *name = nsnull;
rv = node->GetNameUTF8(&name);
if (NS_FAILED(rv)) return rv;
nsCString actualProgID(NS_ISTREAMCONVERTER_KEY);
actualProgID.Append(name);
// now we've got the PROGID, let's parse it up.
rv = AddAdjacency(actualProgID.GetBuffer());
// cleanup
nsCRT::free(name);
NS_RELEASE(node);
NS_RELEASE(base);
rv = components->Next();
}
NS_IF_RELEASE( components );
nsServiceManager::ReleaseService( NS_REGISTRY_PROGID, registry );
return NS_OK;
}
// XXX currently you can not add the same adjacency (i.e. you can't have multiple
// XXX stream converters registering to handle the same from-to combination. It's
// XXX not programatically prohibited, it's just that results are un-predictable
// XXX right now.
nsresult
nsStreamConverterService::AddAdjacency(const char *aProgID) {
nsresult rv;
// first parse out the FROM and TO MIME-types.
nsCString fromStr, toStr;
rv = ParseFromTo(aProgID, fromStr, toStr);
if (NS_FAILED(rv)) return rv;
// Each MIME-type is a vertex in the graph, so first lets make sure
// each MIME-type is represented as a key in our hashtable.
PRBool delFrom = PR_TRUE, delTo = PR_TRUE;
nsStringKey *fromKey = new nsStringKey(fromStr.GetBuffer());
if (!fromKey) return NS_ERROR_OUT_OF_MEMORY;
nsStringKey *toKey = new nsStringKey(toStr.GetBuffer());
if (!toKey) {
delete fromKey;
return NS_ERROR_OUT_OF_MEMORY;
}
SCTableData *edges = (SCTableData*)mAdjacencyList->Get(fromKey);
if (!edges) {
// There is no fromStr vertex, create one.
SCTableData *data = new SCTableData;
if (!data) {
delete fromKey;
delete toKey;
return NS_ERROR_OUT_OF_MEMORY;
}
data->key = fromKey;
delFrom = PR_FALSE;
data->keyString = new nsCString(fromStr.GetBuffer());
if (!data->keyString) {
delete fromKey;
delete toKey;
delete data;
return NS_ERROR_OUT_OF_MEMORY;
}
data->data = new nsVoidArray();
if (!data->data) {
delete data->keyString;
delete fromKey;
delete toKey;
delete data;
return NS_ERROR_OUT_OF_MEMORY;
}
mAdjacencyList->Put(fromKey, data);
}
edges = (SCTableData*)mAdjacencyList->Get(toKey);
if (!edges) {
// There is no toStr vertex, create one.
SCTableData *data = new SCTableData;
if (!data) {
delete fromKey;
delete toKey;
return NS_ERROR_OUT_OF_MEMORY;
}
data->key = toKey;
delTo = PR_FALSE;
data->keyString = new nsCString(toStr.GetBuffer());
if (!data->keyString) {
delete fromKey;
delete toKey;
delete data;
return NS_ERROR_OUT_OF_MEMORY;
}
data->data = new nsVoidArray();
if (!data->data) {
delete data->keyString;
delete fromKey;
delete toKey;
delete data;
return NS_ERROR_OUT_OF_MEMORY;
}
mAdjacencyList->Put(toKey, data);
}
if (delTo)
delete toKey;
// Now we know the FROM and TO types are represented as keys in the hashtable.
// Let's "connect" the verticies, making an edge.
edges = (SCTableData*)mAdjacencyList->Get(fromKey);
if (delFrom)
delete fromKey;
NS_ASSERTION(edges, "something wrong in adjacency list construction");
const char *toCStr = toStr.GetBuffer();
nsIAtom *vertex = NS_NewAtom(toCStr);
if (!vertex) return NS_ERROR_OUT_OF_MEMORY;
nsVoidArray *adjacencyList = (nsVoidArray*)edges->data;
rv = adjacencyList->AppendElement(vertex) ? NS_OK : NS_ERROR_FAILURE; // XXX this method incorrectly returns a bool
return rv;
}
nsresult
nsStreamConverterService::ParseFromTo(const char *aProgID, nsCString &aFromRes, nsCString &aToRes) {
nsCString ProgIDStr(aProgID);
PRInt32 fromLoc = ProgIDStr.Find("from=") + 5;
PRInt32 toLoc = ProgIDStr.Find("to=") + 3;
if (-1 == fromLoc || -1 == toLoc ) return NS_ERROR_FAILURE;
nsCString fromStr, toStr;
ProgIDStr.Mid(fromStr, fromLoc, toLoc - 4 - fromLoc);
ProgIDStr.Mid(toStr, toLoc, ProgIDStr.Length() - toLoc);
aFromRes.SetString(fromStr);
aToRes.SetString(toStr);
return NS_OK;
}
// nsHashtable enumerator functions.
// Initializes the BFS state table.
PRBool InitBFSTable(nsHashKey *aKey, void *aData, void* closure) {
nsHashtable *BFSTable = (nsHashtable*)closure;
if (!BFSTable) return PR_FALSE;
BFSState *state = new BFSState;
if (!state) return NS_ERROR_OUT_OF_MEMORY;
state->color = white;
state->distance = -1;
state->predecessor = nsnull;
SCTableData *data = new SCTableData;
if (!data) return NS_ERROR_OUT_OF_MEMORY;
data->key = aKey->Clone();
SCTableData *origData = (SCTableData*)aData;
NS_ASSERTION(origData, "no data in the table enumeration");
data->keyString = new nsCString(*origData->keyString);
data->data = state;
BFSTable->Put(aKey, data);
return PR_TRUE;
};
// cleans up the BFS state table
PRBool DeleteBFSEntry(nsHashKey *aKey, void *aData, void *closure) {
SCTableData *data = (SCTableData*)aData;
delete data->key;
delete data->keyString;
BFSState *state = (BFSState*)data->data;
if (state->predecessor) // there might not be a predecessor depending on the graph
delete state->predecessor;
delete state;
delete data;
return PR_TRUE;
}
// walks the graph using a breadth-first-search algorithm which generates a discovered
// verticies tree. This tree is then walked up (from destination vertex, to origin vertex)
// and each link in the chain is added to an nsStringArray. A direct lookup for the given
// PROGID should be made prior to calling this method in an attempt to find a direct
// converter rather than walking the graph.
nsresult
nsStreamConverterService::FindConverter(const char *aProgID, nsCStringArray **aEdgeList) {
nsresult rv;
if (!aEdgeList) return NS_ERROR_NULL_POINTER;
// walk the graph in search of the appropriate converter.
PRInt32 vertexCount = mAdjacencyList->Count();
if (0 >= vertexCount)
return NS_ERROR_FAILURE;
// Create a corresponding color table for each vertex in the graph.
nsHashtable lBFSTable;
mAdjacencyList->Enumerate(InitBFSTable, &lBFSTable);
NS_ASSERTION(lBFSTable.Count() == vertexCount, "strmconv BFS table init problem");
// This is our source vertex; our starting point.
nsCString fromC, toC;
rv = ParseFromTo(aProgID, fromC, toC);
if (NS_FAILED(rv)) return rv;
nsStringKey *source = new nsStringKey(fromC.GetBuffer());
if (!source) return NS_ERROR_OUT_OF_MEMORY;
SCTableData *data = (SCTableData*)lBFSTable.Get(source);
NS_ASSERTION(data, "trying to convert a from type that hasn't been registered.");
if (!data) return NS_ERROR_FAILURE;
BFSState *state = (BFSState*)data->data;
state->color = gray;
state->distance = 0;
nsDeque *grayQ = new nsDeque(0);
// Now generate the shortest path tree.
grayQ->Push(source);
while (0 < grayQ->GetSize()) {
nsHashKey *currentHead = (nsHashKey*)grayQ->PeekFront();
SCTableData *data2 = (SCTableData*)mAdjacencyList->Get(currentHead);
if (!data2) return NS_ERROR_FAILURE;
nsVoidArray *edges = (nsVoidArray*)data2->data;
NS_ASSERTION(edges, "something went wrong with BFS strmconv algorithm");
// Get the state of the current head to calculate the distance of each
// reachable vertex in the loop.
data2 = (SCTableData*)lBFSTable.Get(currentHead);
BFSState *headVertexState = (BFSState*)data2->data;
NS_ASSERTION(headVertexState, "problem with the BFS strmconv algorithm");
PRInt32 edgeCount = edges->Count();
for (int i = 0; i < edgeCount; i++) {
nsIAtom *curVertexAtom = (nsIAtom*)edges->ElementAt(i);
nsString2 curVertexStr;
nsStr::Initialize(curVertexStr, eOneByte);
curVertexAtom->ToString(curVertexStr);
char * curVertexCString = curVertexStr.ToNewCString();
nsStringKey *curVertex = new nsStringKey(curVertexCString);
nsAllocator::Free(curVertexCString);
SCTableData *data3 = (SCTableData*)lBFSTable.Get(curVertex);
BFSState *curVertexState = (BFSState*)data3->data;
NS_ASSERTION(curVertexState, "something went wrong with the BFS strmconv algorithm");
if (white == curVertexState->color) {
curVertexState->color = gray;
curVertexState->distance = headVertexState->distance + 1;
curVertexState->predecessor = currentHead->Clone();
grayQ->Push(curVertex);
} else {
delete curVertex; // if this vertex has already been discovered, we don't want
// to leak it. (non-discovered vertex's get cleaned up when
// they're popped).
}
}
headVertexState->color = black;
nsStringKey *cur = (nsStringKey*)grayQ->PopFront();
delete cur;
cur = nsnull;
}
delete grayQ;
// The shortest path (if any) has been generated and is represetned by the chain of
// BFSState->predecessor keys. Start at the bottom and work our way up.
// first parse out the FROM and TO MIME-types being registered.
nsCString fromStr, toStr;
rv = ParseFromTo(aProgID, fromStr, toStr);
if (NS_FAILED(rv)) return rv;
// get the root PROGID
nsCString ProgIDPrefix(NS_ISTREAMCONVERTER_KEY);
nsCStringArray *shortestPath = new nsCStringArray();
//nsVoidArray *shortestPath = new nsVoidArray();
nsStringKey *toMIMEType = new nsStringKey(toStr);
data = (SCTableData*)lBFSTable.Get(toMIMEType);
delete toMIMEType;
if (!data) {
// If this vertex isn't in the BFSTable, then no-one has registered for it,
// therefore we can't do the conversion.
*aEdgeList = nsnull;
return NS_ERROR_FAILURE;
}
while (data) {
BFSState *curState = (BFSState*)data->data;
if (data->keyString->Equals(fromStr)) {
// found it. We're done here.
*aEdgeList = shortestPath;
lBFSTable.Reset(DeleteBFSEntry, nsnull);
return NS_OK;
}
// reconstruct the PROGID.
// Get the predecessor.
SCTableData *predecessorData = (SCTableData*)lBFSTable.Get(curState->predecessor);
if (!predecessorData) break; // no predecessor, chain doesn't exist.
// build out the PROGID.
nsCString *newProgID = new nsCString(ProgIDPrefix);
newProgID->Append("?from=");
newProgID->Append(predecessorData->keyString->GetBuffer());
newProgID->Append("?to=");
newProgID->Append(data->keyString->GetBuffer());
// Add this PROGID to the chain.
rv = shortestPath->AppendCString(*newProgID) ? NS_OK : NS_ERROR_FAILURE; // XXX this method incorrectly returns a bool
NS_ASSERTION(NS_SUCCEEDED(rv), "AppendElement failed");
// move up the tree.
data = predecessorData;
}
lBFSTable.Reset(DeleteBFSEntry, nsnull);
*aEdgeList = nsnull;
return NS_ERROR_FAILURE; // couldn't find a stream converter or chain.
}
/////////////////////////////////////////////////////
// nsIStreamConverter methods
NS_IMETHODIMP
nsStreamConverterService::Convert(nsIInputStream *aFromStream,
const PRUnichar *aFromType,
const PRUnichar *aToType,
nsISupports *aContext,
nsIInputStream **_retval) {
if (!aFromStream || !aFromType || !aToType || !_retval) return NS_ERROR_NULL_POINTER;
nsresult rv;
// first determine whether we can even handle this covnversion
// build a PROGID
nsCString progID(NS_ISTREAMCONVERTER_KEY);
progID.Append("?from=");
progID.Append(aFromType);
progID.Append("?to=");
progID.Append(aToType);
const char *cProgID = progID.GetBuffer();
nsIComponentManager *comMgr;
rv = NS_GetGlobalComponentManager(&comMgr);
if (NS_FAILED(rv)) return rv;
nsISupports *converter = nsnull;
rv = comMgr->CreateInstanceByProgID(cProgID, nsnull,
NS_GET_IID(nsIStreamConverter),
(void**)&converter);
if (NS_FAILED(rv)) {
// couldn't go direct, let's try walking the graph of converters.
rv = BuildGraph();
if (NS_FAILED(rv)) return rv;
nsCStringArray *converterChain = nsnull;
rv = FindConverter(cProgID, &converterChain);
if (NS_FAILED(rv)) {
// can't make this conversion.
// XXX should have a more descriptive error code.
return NS_ERROR_FAILURE;
}
PRInt32 edgeCount = converterChain->Count();
NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
// convert the stream using each edge of the graph as a step.
// this is our stream conversion traversal.
nsIInputStream *dataToConvert = aFromStream;
nsIInputStream *convertedData = nsnull;
NS_ADDREF(dataToConvert);
for (PRInt32 i = edgeCount-1; i >= 0; i--) {
nsCString *progIDStr = converterChain->CStringAt(i);
if (!progIDStr) {
delete converterChain;
return NS_ERROR_FAILURE;
}
const char *lProgID = progIDStr->GetBuffer();
rv = comMgr->CreateInstanceByProgID(lProgID, nsnull,
NS_GET_IID(nsIStreamConverter),
(void**)&converter);
if (NS_FAILED(rv)) {
delete converterChain;
return rv;
}
nsCString fromStr, toStr;
rv = ParseFromTo(lProgID, fromStr, toStr);
if (NS_FAILED(rv)) {
delete converterChain;
return rv;
}
nsIStreamConverter *conv = nsnull;
rv = converter->QueryInterface(NS_GET_IID(nsIStreamConverter), (void**)&conv);
NS_RELEASE(converter);
if (NS_FAILED(rv)) {
delete converterChain;
return rv;
}
PRUnichar *fromUni = fromStr.ToNewUnicode();
PRUnichar *toUni = toStr.ToNewUnicode();
rv = conv->Convert(dataToConvert, fromUni, toUni, aContext, &convertedData);
nsAllocator::Free(fromUni);
nsAllocator::Free(toUni);
NS_RELEASE(conv);
NS_RELEASE(dataToConvert);
dataToConvert = convertedData;
if (NS_FAILED(rv)) {
delete converterChain;
return rv;
}
}
delete converterChain;
*_retval = convertedData;
} else {
// we're going direct.
nsIStreamConverter *conv = nsnull;
rv = converter->QueryInterface(NS_GET_IID(nsIStreamConverter), (void**)&conv);
NS_RELEASE(converter);
if (NS_FAILED(rv)) return rv;
rv = conv->Convert(aFromStream, aFromType, aToType, aContext, _retval);
NS_RELEASE(conv);
}
return rv;
}
NS_IMETHODIMP
nsStreamConverterService::AsyncConvertData(const PRUnichar *aFromType,
const PRUnichar *aToType,
nsIStreamListener *aListener,
nsISupports *aContext,
nsIStreamListener **_retval) {
if (!aFromType || !aToType || !aListener || !_retval) return NS_ERROR_NULL_POINTER;
nsresult rv;
// first determine whether we can even handle this covnversion
// build a PROGID
nsCString progID(NS_ISTREAMCONVERTER_KEY);
progID.Append("?from=");
progID.Append(aFromType);
progID.Append("?to=");
progID.Append(aToType);
const char *cProgID = progID.GetBuffer();
nsIComponentManager *comMgr;
rv = NS_GetGlobalComponentManager(&comMgr);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIStreamConverter> listener;
rv = comMgr->CreateInstanceByProgID(cProgID, nsnull,
NS_GET_IID(nsIStreamConverter),
getter_AddRefs(listener));
if (NS_FAILED(rv)) {
// couldn't go direct, let's try walking the graph of converters.
rv = BuildGraph();
if (NS_FAILED(rv)) return rv;
nsCStringArray *converterChain = nsnull;
rv = FindConverter(cProgID, &converterChain);
if (NS_FAILED(rv)) {
// can't make this conversion.
// XXX should have a more descriptive error code.
return NS_ERROR_FAILURE;
}
PRInt32 edgeCount = converterChain->Count();
NS_ASSERTION(edgeCount > 0, "findConverter should have failed");
// convert the stream using each edge of the graph as a step.
// this is our stream conversion traversal.
nsCOMPtr<nsIStreamListener> forwardListener = aListener;
nsCOMPtr<nsIStreamListener> fromListener;
for (int i = 0; i < edgeCount; i++) {
nsCString *progIDStr = converterChain->CStringAt(i);
if (!progIDStr) {
delete converterChain;
return NS_ERROR_FAILURE;
}
const char *lProgID = progIDStr->GetBuffer();
nsCOMPtr<nsIStreamConverter> converter;
rv = comMgr->CreateInstanceByProgID(lProgID, nsnull,
NS_GET_IID(nsIStreamConverter),
getter_AddRefs(converter));
NS_ASSERTION(NS_SUCCEEDED(rv), "graph construction problem, built a progid that wasn't registered");
nsCString fromStr, toStr;
rv = ParseFromTo(lProgID, fromStr, toStr);
if (NS_FAILED(rv)) {
delete converterChain;
return rv;
}
PRUnichar *fromStrUni = fromStr.ToNewUnicode();
PRUnichar *toStrUni = toStr.ToNewUnicode();
rv = converter->AsyncConvertData(fromStrUni, toStrUni, forwardListener, aContext);
nsAllocator::Free(fromStrUni);
nsAllocator::Free(toStrUni);
if (NS_FAILED(rv)) {
delete converterChain;
return rv;
}
nsCOMPtr<nsIStreamListener> chainListener(do_QueryInterface(converter, &rv));
if (NS_FAILED(rv)) {
delete converterChain;
return rv;
}
// store the listener of the first converter in the chain.
if (!fromListener)
fromListener = chainListener;
forwardListener = chainListener;
}
delete converterChain;
// return the first listener in the chain.
*_retval = fromListener;
NS_ADDREF(*_retval);
} else {
// we're going direct.
*_retval = listener;
NS_ADDREF(*_retval);
nsCOMPtr<nsIStreamConverter> conv(do_QueryInterface(listener, &rv));
if (NS_FAILED(rv)) return rv;
rv = conv->AsyncConvertData(aFromType, aToType, aListener, aContext);
}
return rv;
}
nsresult
NS_NewStreamConv(nsStreamConverterService** aStreamConv)
{
NS_PRECONDITION(aStreamConv != nsnull, "null ptr");
if (! aStreamConv)
return NS_ERROR_NULL_POINTER;
*aStreamConv = new nsStreamConverterService();
if (! *aStreamConv)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aStreamConv);
(*aStreamConv)->Init();
return NS_OK;
}