1*b1cdbd2cSJim Jagielski /**************************************************************
2*b1cdbd2cSJim Jagielski  *
3*b1cdbd2cSJim Jagielski  * Licensed to the Apache Software Foundation (ASF) under one
4*b1cdbd2cSJim Jagielski  * or more contributor license agreements.  See the NOTICE file
5*b1cdbd2cSJim Jagielski  * distributed with this work for additional information
6*b1cdbd2cSJim Jagielski  * regarding copyright ownership.  The ASF licenses this file
7*b1cdbd2cSJim Jagielski  * to you under the Apache License, Version 2.0 (the
8*b1cdbd2cSJim Jagielski  * "License"); you may not use this file except in compliance
9*b1cdbd2cSJim Jagielski  * with the License.  You may obtain a copy of the License at
10*b1cdbd2cSJim Jagielski  *
11*b1cdbd2cSJim Jagielski  *   http://www.apache.org/licenses/LICENSE-2.0
12*b1cdbd2cSJim Jagielski  *
13*b1cdbd2cSJim Jagielski  * Unless required by applicable law or agreed to in writing,
14*b1cdbd2cSJim Jagielski  * software distributed under the License is distributed on an
15*b1cdbd2cSJim Jagielski  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16*b1cdbd2cSJim Jagielski  * KIND, either express or implied.  See the License for the
17*b1cdbd2cSJim Jagielski  * specific language governing permissions and limitations
18*b1cdbd2cSJim Jagielski  * under the License.
19*b1cdbd2cSJim Jagielski  *
20*b1cdbd2cSJim Jagielski  *************************************************************/
21*b1cdbd2cSJim Jagielski 
22*b1cdbd2cSJim Jagielski 
23*b1cdbd2cSJim Jagielski 
24*b1cdbd2cSJim Jagielski // MARKER(update_precomp.py): autogen include statement, do not remove
25*b1cdbd2cSJim Jagielski #include "precompiled_connectivity.hxx"
26*b1cdbd2cSJim Jagielski #include "calc/CConnection.hxx"
27*b1cdbd2cSJim Jagielski #include "calc/CDatabaseMetaData.hxx"
28*b1cdbd2cSJim Jagielski #include "calc/CCatalog.hxx"
29*b1cdbd2cSJim Jagielski #ifndef _CONNECTIVITY_CALC_ODRIVER_HXX_
30*b1cdbd2cSJim Jagielski #include "calc/CDriver.hxx"
31*b1cdbd2cSJim Jagielski #endif
32*b1cdbd2cSJim Jagielski #ifndef CONNECTIVITY_RESOURCE_CALC_HRC
33*b1cdbd2cSJim Jagielski #include "resource/calc_res.hrc"
34*b1cdbd2cSJim Jagielski #endif
35*b1cdbd2cSJim Jagielski #include "resource/sharedresources.hxx"
36*b1cdbd2cSJim Jagielski #include <com/sun/star/lang/DisposedException.hpp>
37*b1cdbd2cSJim Jagielski #include <com/sun/star/frame/XComponentLoader.hpp>
38*b1cdbd2cSJim Jagielski #include <com/sun/star/sheet/XSpreadsheetDocument.hpp>
39*b1cdbd2cSJim Jagielski #include <tools/urlobj.hxx>
40*b1cdbd2cSJim Jagielski #include "calc/CPreparedStatement.hxx"
41*b1cdbd2cSJim Jagielski #include "calc/CStatement.hxx"
42*b1cdbd2cSJim Jagielski #include <unotools/pathoptions.hxx>
43*b1cdbd2cSJim Jagielski #include <connectivity/dbexception.hxx>
44*b1cdbd2cSJim Jagielski #include <cppuhelper/exc_hlp.hxx>
45*b1cdbd2cSJim Jagielski #include <rtl/logfile.hxx>
46*b1cdbd2cSJim Jagielski 
47*b1cdbd2cSJim Jagielski using namespace connectivity::calc;
48*b1cdbd2cSJim Jagielski using namespace connectivity::file;
49*b1cdbd2cSJim Jagielski 
50*b1cdbd2cSJim Jagielski typedef connectivity::file::OConnection OConnection_BASE;
51*b1cdbd2cSJim Jagielski 
52*b1cdbd2cSJim Jagielski //------------------------------------------------------------------------------
53*b1cdbd2cSJim Jagielski 
54*b1cdbd2cSJim Jagielski using namespace ::com::sun::star::uno;
55*b1cdbd2cSJim Jagielski using namespace ::com::sun::star::beans;
56*b1cdbd2cSJim Jagielski using namespace ::com::sun::star::sdbcx;
57*b1cdbd2cSJim Jagielski using namespace ::com::sun::star::sdbc;
58*b1cdbd2cSJim Jagielski using namespace ::com::sun::star::lang;
59*b1cdbd2cSJim Jagielski using namespace ::com::sun::star::frame;
60*b1cdbd2cSJim Jagielski using namespace ::com::sun::star::sheet;
61*b1cdbd2cSJim Jagielski 
62*b1cdbd2cSJim Jagielski // --------------------------------------------------------------------------------
63*b1cdbd2cSJim Jagielski 
OCalcConnection(ODriver * _pDriver)64*b1cdbd2cSJim Jagielski OCalcConnection::OCalcConnection(ODriver* _pDriver) : OConnection(_pDriver),m_nDocCount(0)
65*b1cdbd2cSJim Jagielski {
66*b1cdbd2cSJim Jagielski     RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "calc", "Ocke.Janssen@sun.com", "OCalcConnection::OCalcConnection" );
67*b1cdbd2cSJim Jagielski 	// m_aFilenameExtension is not used
68*b1cdbd2cSJim Jagielski }
69*b1cdbd2cSJim Jagielski 
~OCalcConnection()70*b1cdbd2cSJim Jagielski OCalcConnection::~OCalcConnection()
71*b1cdbd2cSJim Jagielski {
72*b1cdbd2cSJim Jagielski }
73*b1cdbd2cSJim Jagielski 
construct(const::rtl::OUString & url,const Sequence<PropertyValue> & info)74*b1cdbd2cSJim Jagielski void OCalcConnection::construct(const ::rtl::OUString& url,const Sequence< PropertyValue >& info)
75*b1cdbd2cSJim Jagielski 	throw(SQLException)
76*b1cdbd2cSJim Jagielski {
77*b1cdbd2cSJim Jagielski     RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "calc", "Ocke.Janssen@sun.com", "OCalcConnection::construct" );
78*b1cdbd2cSJim Jagielski 	//	open file
79*b1cdbd2cSJim Jagielski 
80*b1cdbd2cSJim Jagielski 	sal_Int32 nLen = url.indexOf(':');
81*b1cdbd2cSJim Jagielski 	nLen = url.indexOf(':',nLen+1);
82*b1cdbd2cSJim Jagielski 	::rtl::OUString aDSN(url.copy(nLen+1));
83*b1cdbd2cSJim Jagielski 
84*b1cdbd2cSJim Jagielski 	m_aFileName = aDSN;
85*b1cdbd2cSJim Jagielski 	INetURLObject aURL;
86*b1cdbd2cSJim Jagielski 	aURL.SetSmartProtocol(INET_PROT_FILE);
87*b1cdbd2cSJim Jagielski 	{
88*b1cdbd2cSJim Jagielski 		SvtPathOptions aPathOptions;
89*b1cdbd2cSJim Jagielski 		m_aFileName = aPathOptions.SubstituteVariable(m_aFileName);
90*b1cdbd2cSJim Jagielski 	}
91*b1cdbd2cSJim Jagielski 	aURL.SetSmartURL(m_aFileName);
92*b1cdbd2cSJim Jagielski 	if ( aURL.GetProtocol() == INET_PROT_NOT_VALID )
93*b1cdbd2cSJim Jagielski 	{
94*b1cdbd2cSJim Jagielski 		//	don't pass invalid URL to loadComponentFromURL
95*b1cdbd2cSJim Jagielski 		throw SQLException();
96*b1cdbd2cSJim Jagielski 	}
97*b1cdbd2cSJim Jagielski 	m_aFileName = aURL.GetMainURL(INetURLObject::NO_DECODE);
98*b1cdbd2cSJim Jagielski 
99*b1cdbd2cSJim Jagielski     m_sPassword = ::rtl::OUString();
100*b1cdbd2cSJim Jagielski 	const char* pPwd		= "password";
101*b1cdbd2cSJim Jagielski 
102*b1cdbd2cSJim Jagielski 	const PropertyValue *pIter	= info.getConstArray();
103*b1cdbd2cSJim Jagielski 	const PropertyValue *pEnd	= pIter + info.getLength();
104*b1cdbd2cSJim Jagielski 	for(;pIter != pEnd;++pIter)
105*b1cdbd2cSJim Jagielski 	{
106*b1cdbd2cSJim Jagielski 		if(!pIter->Name.compareToAscii(pPwd))
107*b1cdbd2cSJim Jagielski 		{
108*b1cdbd2cSJim Jagielski 			pIter->Value >>= m_sPassword;
109*b1cdbd2cSJim Jagielski 			break;
110*b1cdbd2cSJim Jagielski 		}
111*b1cdbd2cSJim Jagielski 	} // for(;pIter != pEnd;++pIter)
112*b1cdbd2cSJim Jagielski     ODocHolder aDocHodler(this); // just to test that the doc can be loaded
113*b1cdbd2cSJim Jagielski     acquireDoc();
114*b1cdbd2cSJim Jagielski }
115*b1cdbd2cSJim Jagielski // -----------------------------------------------------------------------------
acquireDoc()116*b1cdbd2cSJim Jagielski Reference< XSpreadsheetDocument> OCalcConnection::acquireDoc()
117*b1cdbd2cSJim Jagielski {
118*b1cdbd2cSJim Jagielski     RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "calc", "Ocke.Janssen@sun.com", "OCalcConnection::acquireDoc" );
119*b1cdbd2cSJim Jagielski     if ( m_xDoc.is() )
120*b1cdbd2cSJim Jagielski     {
121*b1cdbd2cSJim Jagielski         osl_incrementInterlockedCount(&m_nDocCount);
122*b1cdbd2cSJim Jagielski         return m_xDoc;
123*b1cdbd2cSJim Jagielski     }
124*b1cdbd2cSJim Jagielski     //	open read-only as long as updating isn't implemented
125*b1cdbd2cSJim Jagielski 	Sequence<PropertyValue> aArgs(2);
126*b1cdbd2cSJim Jagielski     aArgs[0].Name = ::rtl::OUString::createFromAscii("Hidden");
127*b1cdbd2cSJim Jagielski     aArgs[0].Value <<= (sal_Bool) sal_True;
128*b1cdbd2cSJim Jagielski     aArgs[1].Name = ::rtl::OUString::createFromAscii("ReadOnly");
129*b1cdbd2cSJim Jagielski     aArgs[1].Value <<= (sal_Bool) sal_True;
130*b1cdbd2cSJim Jagielski 
131*b1cdbd2cSJim Jagielski 	if ( m_sPassword.getLength() )
132*b1cdbd2cSJim Jagielski 	{
133*b1cdbd2cSJim Jagielski 		const sal_Int32 nPos = aArgs.getLength();
134*b1cdbd2cSJim Jagielski 		aArgs.realloc(nPos+1);
135*b1cdbd2cSJim Jagielski 		aArgs[nPos].Name = ::rtl::OUString::createFromAscii("Password");
136*b1cdbd2cSJim Jagielski 		aArgs[nPos].Value <<= m_sPassword;
137*b1cdbd2cSJim Jagielski 	}
138*b1cdbd2cSJim Jagielski 
139*b1cdbd2cSJim Jagielski     Reference< XComponentLoader > xDesktop( getDriver()->getFactory()->createInstance(
140*b1cdbd2cSJim Jagielski 					::rtl::OUString::createFromAscii("com.sun.star.frame.Desktop")), UNO_QUERY );
141*b1cdbd2cSJim Jagielski 	if (!xDesktop.is())
142*b1cdbd2cSJim Jagielski 	{
143*b1cdbd2cSJim Jagielski 		OSL_ASSERT("no desktop");
144*b1cdbd2cSJim Jagielski 		throw SQLException();
145*b1cdbd2cSJim Jagielski 	}
146*b1cdbd2cSJim Jagielski     Reference< XComponent > xComponent;
147*b1cdbd2cSJim Jagielski     Any aLoaderException;
148*b1cdbd2cSJim Jagielski     try
149*b1cdbd2cSJim Jagielski     {
150*b1cdbd2cSJim Jagielski 	    xComponent = xDesktop->loadComponentFromURL(
151*b1cdbd2cSJim Jagielski             m_aFileName, ::rtl::OUString::createFromAscii("_blank"), 0, aArgs );
152*b1cdbd2cSJim Jagielski     }
153*b1cdbd2cSJim Jagielski     catch( const Exception& )
154*b1cdbd2cSJim Jagielski     {
155*b1cdbd2cSJim Jagielski         aLoaderException = ::cppu::getCaughtException();
156*b1cdbd2cSJim Jagielski     }
157*b1cdbd2cSJim Jagielski 
158*b1cdbd2cSJim Jagielski     m_xDoc.set(xComponent, UNO_QUERY );
159*b1cdbd2cSJim Jagielski 
160*b1cdbd2cSJim Jagielski     //	if the URL is not a spreadsheet document, throw the exception here
161*b1cdbd2cSJim Jagielski 	//	instead of at the first access to it
162*b1cdbd2cSJim Jagielski     if ( !m_xDoc.is() )
163*b1cdbd2cSJim Jagielski     {
164*b1cdbd2cSJim Jagielski         Any aErrorDetails;
165*b1cdbd2cSJim Jagielski         if ( aLoaderException.hasValue() )
166*b1cdbd2cSJim Jagielski         {
167*b1cdbd2cSJim Jagielski             Exception aLoaderError;
168*b1cdbd2cSJim Jagielski             OSL_VERIFY( aLoaderException >>= aLoaderError );
169*b1cdbd2cSJim Jagielski 
170*b1cdbd2cSJim Jagielski             SQLException aDetailException;
171*b1cdbd2cSJim Jagielski             aDetailException.Message = m_aResources.getResourceStringWithSubstitution(
172*b1cdbd2cSJim Jagielski                 STR_LOAD_FILE_ERROR_MESSAGE,
173*b1cdbd2cSJim Jagielski                 "$exception_type$", aLoaderException.getValueTypeName(),
174*b1cdbd2cSJim Jagielski                 "$error_message$", aLoaderError.Message
175*b1cdbd2cSJim Jagielski             );
176*b1cdbd2cSJim Jagielski             aErrorDetails <<= aDetailException;
177*b1cdbd2cSJim Jagielski         }
178*b1cdbd2cSJim Jagielski 
179*b1cdbd2cSJim Jagielski         const ::rtl::OUString sError( m_aResources.getResourceStringWithSubstitution(
180*b1cdbd2cSJim Jagielski             STR_COULD_NOT_LOAD_FILE,
181*b1cdbd2cSJim Jagielski             "$filename$", m_aFileName
182*b1cdbd2cSJim Jagielski          ) );
183*b1cdbd2cSJim Jagielski         ::dbtools::throwGenericSQLException( sError, *this, aErrorDetails );
184*b1cdbd2cSJim Jagielski     }
185*b1cdbd2cSJim Jagielski     osl_incrementInterlockedCount(&m_nDocCount);
186*b1cdbd2cSJim Jagielski     return m_xDoc;
187*b1cdbd2cSJim Jagielski }
188*b1cdbd2cSJim Jagielski // -----------------------------------------------------------------------------
releaseDoc()189*b1cdbd2cSJim Jagielski void OCalcConnection::releaseDoc()
190*b1cdbd2cSJim Jagielski {
191*b1cdbd2cSJim Jagielski     RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "calc", "Ocke.Janssen@sun.com", "OCalcConnection::releaseDoc" );
192*b1cdbd2cSJim Jagielski     if ( osl_decrementInterlockedCount(&m_nDocCount) == 0 )
193*b1cdbd2cSJim Jagielski         ::comphelper::disposeComponent( m_xDoc );
194*b1cdbd2cSJim Jagielski }
195*b1cdbd2cSJim Jagielski // -----------------------------------------------------------------------------
disposing()196*b1cdbd2cSJim Jagielski void OCalcConnection::disposing()
197*b1cdbd2cSJim Jagielski {
198*b1cdbd2cSJim Jagielski     RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "calc", "Ocke.Janssen@sun.com", "OCalcConnection::disposing" );
199*b1cdbd2cSJim Jagielski 	::osl::MutexGuard aGuard(m_aMutex);
200*b1cdbd2cSJim Jagielski 
201*b1cdbd2cSJim Jagielski     m_nDocCount = 0;
202*b1cdbd2cSJim Jagielski     ::comphelper::disposeComponent( m_xDoc );
203*b1cdbd2cSJim Jagielski 
204*b1cdbd2cSJim Jagielski 	OConnection::disposing();
205*b1cdbd2cSJim Jagielski }
206*b1cdbd2cSJim Jagielski 
207*b1cdbd2cSJim Jagielski // XServiceInfo
208*b1cdbd2cSJim Jagielski // --------------------------------------------------------------------------------
209*b1cdbd2cSJim Jagielski 
210*b1cdbd2cSJim Jagielski IMPLEMENT_SERVICE_INFO(OCalcConnection, "com.sun.star.sdbc.drivers.calc.Connection", "com.sun.star.sdbc.Connection")
211*b1cdbd2cSJim Jagielski 
212*b1cdbd2cSJim Jagielski // --------------------------------------------------------------------------------
213*b1cdbd2cSJim Jagielski 
getMetaData()214*b1cdbd2cSJim Jagielski Reference< XDatabaseMetaData > SAL_CALL OCalcConnection::getMetaData(  ) throw(SQLException, RuntimeException)
215*b1cdbd2cSJim Jagielski {
216*b1cdbd2cSJim Jagielski     RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "calc", "Ocke.Janssen@sun.com", "OCalcConnection::getMetaData" );
217*b1cdbd2cSJim Jagielski 	::osl::MutexGuard aGuard( m_aMutex );
218*b1cdbd2cSJim Jagielski 	checkDisposed(OConnection_BASE::rBHelper.bDisposed);
219*b1cdbd2cSJim Jagielski 
220*b1cdbd2cSJim Jagielski 
221*b1cdbd2cSJim Jagielski 	Reference< XDatabaseMetaData > xMetaData = m_xMetaData;
222*b1cdbd2cSJim Jagielski 	if(!xMetaData.is())
223*b1cdbd2cSJim Jagielski 	{
224*b1cdbd2cSJim Jagielski 		xMetaData = new OCalcDatabaseMetaData(this);
225*b1cdbd2cSJim Jagielski 		m_xMetaData = xMetaData;
226*b1cdbd2cSJim Jagielski 	}
227*b1cdbd2cSJim Jagielski 
228*b1cdbd2cSJim Jagielski 	return xMetaData;
229*b1cdbd2cSJim Jagielski }
230*b1cdbd2cSJim Jagielski 
231*b1cdbd2cSJim Jagielski //------------------------------------------------------------------------------
232*b1cdbd2cSJim Jagielski 
createCatalog()233*b1cdbd2cSJim Jagielski ::com::sun::star::uno::Reference< XTablesSupplier > OCalcConnection::createCatalog()
234*b1cdbd2cSJim Jagielski {
235*b1cdbd2cSJim Jagielski     RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "calc", "Ocke.Janssen@sun.com", "OCalcConnection::createCatalog" );
236*b1cdbd2cSJim Jagielski 	::osl::MutexGuard aGuard( m_aMutex );
237*b1cdbd2cSJim Jagielski 	Reference< XTablesSupplier > xTab = m_xCatalog;
238*b1cdbd2cSJim Jagielski 	if(!xTab.is())
239*b1cdbd2cSJim Jagielski 	{
240*b1cdbd2cSJim Jagielski 		OCalcCatalog *pCat = new OCalcCatalog(this);
241*b1cdbd2cSJim Jagielski 		xTab = pCat;
242*b1cdbd2cSJim Jagielski 		m_xCatalog = xTab;
243*b1cdbd2cSJim Jagielski 	}
244*b1cdbd2cSJim Jagielski 	return xTab;
245*b1cdbd2cSJim Jagielski }
246*b1cdbd2cSJim Jagielski 
247*b1cdbd2cSJim Jagielski // --------------------------------------------------------------------------------
248*b1cdbd2cSJim Jagielski 
createStatement()249*b1cdbd2cSJim Jagielski Reference< XStatement > SAL_CALL OCalcConnection::createStatement(  ) throw(SQLException, RuntimeException)
250*b1cdbd2cSJim Jagielski {
251*b1cdbd2cSJim Jagielski     RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "calc", "Ocke.Janssen@sun.com", "OCalcConnection::createStatement" );
252*b1cdbd2cSJim Jagielski 	::osl::MutexGuard aGuard( m_aMutex );
253*b1cdbd2cSJim Jagielski 	checkDisposed(OConnection_BASE::rBHelper.bDisposed);
254*b1cdbd2cSJim Jagielski 
255*b1cdbd2cSJim Jagielski 
256*b1cdbd2cSJim Jagielski 	Reference< XStatement > xReturn = new OCalcStatement(this);
257*b1cdbd2cSJim Jagielski     m_aStatements.push_back(WeakReferenceHelper(xReturn));
258*b1cdbd2cSJim Jagielski 	return xReturn;
259*b1cdbd2cSJim Jagielski }
260*b1cdbd2cSJim Jagielski 
261*b1cdbd2cSJim Jagielski // --------------------------------------------------------------------------------
262*b1cdbd2cSJim Jagielski 
prepareStatement(const::rtl::OUString & sql)263*b1cdbd2cSJim Jagielski Reference< XPreparedStatement > SAL_CALL OCalcConnection::prepareStatement( const ::rtl::OUString& sql )
264*b1cdbd2cSJim Jagielski 	throw(SQLException, RuntimeException)
265*b1cdbd2cSJim Jagielski {
266*b1cdbd2cSJim Jagielski     RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "calc", "Ocke.Janssen@sun.com", "OCalcConnection::prepareStatement" );
267*b1cdbd2cSJim Jagielski 	::osl::MutexGuard aGuard( m_aMutex );
268*b1cdbd2cSJim Jagielski 	checkDisposed(OConnection_BASE::rBHelper.bDisposed);
269*b1cdbd2cSJim Jagielski 
270*b1cdbd2cSJim Jagielski 
271*b1cdbd2cSJim Jagielski 	OCalcPreparedStatement* pStmt = new OCalcPreparedStatement(this);
272*b1cdbd2cSJim Jagielski 	Reference< XPreparedStatement > xHoldAlive = pStmt;
273*b1cdbd2cSJim Jagielski 	pStmt->construct(sql);
274*b1cdbd2cSJim Jagielski     m_aStatements.push_back(WeakReferenceHelper(*pStmt));
275*b1cdbd2cSJim Jagielski 	return pStmt;
276*b1cdbd2cSJim Jagielski }
277*b1cdbd2cSJim Jagielski 
278*b1cdbd2cSJim Jagielski // --------------------------------------------------------------------------------
279*b1cdbd2cSJim Jagielski 
prepareCall(const::rtl::OUString &)280*b1cdbd2cSJim Jagielski Reference< XPreparedStatement > SAL_CALL OCalcConnection::prepareCall( const ::rtl::OUString& /*sql*/ )
281*b1cdbd2cSJim Jagielski 	throw(SQLException, RuntimeException)
282*b1cdbd2cSJim Jagielski {
283*b1cdbd2cSJim Jagielski     RTL_LOGFILE_CONTEXT_AUTHOR( aLogger, "calc", "Ocke.Janssen@sun.com", "OCalcConnection::prepareCall" );
284*b1cdbd2cSJim Jagielski 	::osl::MutexGuard aGuard( m_aMutex );
285*b1cdbd2cSJim Jagielski 	checkDisposed(OConnection_BASE::rBHelper.bDisposed);
286*b1cdbd2cSJim Jagielski 
287*b1cdbd2cSJim Jagielski     ::dbtools::throwFeatureNotImplementedException( "XConnection::prepareCall", *this );
288*b1cdbd2cSJim Jagielski     return NULL;
289*b1cdbd2cSJim Jagielski }
290*b1cdbd2cSJim Jagielski // -----------------------------------------------------------------------------
291*b1cdbd2cSJim Jagielski 
292