text: /**************************************************************
   * 
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   * 
   *************************************************************/
  
  
  
  // MARKER(update_precomp.py): autogen include statement, do not remove
  #include "precompiled_filter.hxx"
  #include "typedetection.hxx"
  #include "constant.hxx"
  
  //_______________________________________________
  // includes
  #include <com/sun/star/document/XExtendedFilterDetection.hpp>
  #include <com/sun/star/util/XURLTransformer.hpp>
  
  #ifndef _COM_SUN_STAR_IO_XINPUSTREAM_HPP_
  #include <com/sun/star/io/XInputStream.hpp>
  #endif
  #include <com/sun/star/io/XSeekable.hpp>
  #include <com/sun/star/task/XInteractionHandler.hpp>
  #include <tools/wldcrd.hxx>
  #include <rtl/ustrbuf.hxx>
  #include <framework/interaction.hxx>
  #include <tools/urlobj.hxx>
  #include <unotools/localfilehelper.hxx>
  
  //_______________________________________________
  // namespace
  
  namespace filter{
      namespace config{
  
  namespace css = ::com::sun::star;
  
  //_______________________________________________
  // definitions
  
  // Use this switch to change the behaviour of preselection DocumentService ... (see using for further informations)
  #define IGNORE_NON_URLMATCHING_TYPES_FOR_PRESELECTION_DOCUMENTSERVICE
  
  // enable/disable special handling for CSV/TXT problem
  #define WORKAROUND_CSV_TXT_BUG_i60158
  
  /*-----------------------------------------------
      03.07.2003 11:25
  -----------------------------------------------*/
  TypeDetection::TypeDetection(const css::uno::Reference< css::lang::XMultiServiceFactory >& xSMGR)
  {
      BaseContainer::init(xSMGR                                         ,
                          TypeDetection::impl_getImplementationName()   ,
                          TypeDetection::impl_getSupportedServiceNames(),
                          FilterCache::E_TYPE                           );
  }
  
  /*-----------------------------------------------
      03.07.2003 10:36
  -----------------------------------------------*/
  TypeDetection::~TypeDetection()
  {
  }
  
  /*-----------------------------------------------
      03.11.2003 08:43
  -----------------------------------------------*/
  ::rtl::OUString SAL_CALL TypeDetection::queryTypeByURL(const ::rtl::OUString& sURL)
      throw (css::uno::RuntimeException)
  {
      ::rtl::OUString sType;
  
      // SAFE ->
      ::osl::ResettableMutexGuard aLock(m_aLock);
  
      css::util::URL  aURL;
      aURL.Complete = sURL;
      css::uno::Reference< css::util::XURLTransformer > xParser(m_xSMGR->createInstance(SERVICE_URLTRANSFORMER), css::uno::UNO_QUERY);
      xParser->parseStrict(aURL);
  
      // set std types as minimum requirement first!
      // Only in case no type was found for given URL,
      // use optional types too ...
      FlatDetection lFlatTypes;
      m_rCache->detectFlatForURL(aURL, lFlatTypes);
  
      if (
          (lFlatTypes.size() < 1                                ) &&
          (!m_rCache->isFillState(FilterCache::E_CONTAINS_TYPES))
         )
      {
          m_rCache->load(FilterCache::E_CONTAINS_TYPES);
          m_rCache->detectFlatForURL(aURL, lFlatTypes);
      }
  
      // first item is guaranteed as "preferred" one!
      if (lFlatTypes.size() > 0)
      {
          const FlatDetectionInfo& aMatch = *(lFlatTypes.begin());
          sType = aMatch.sType;
      }
  
      return sType;
      // <- SAFE
  }
  
  /*-----------------------------------------------
      31.10.2003 09:36
  -----------------------------------------------*/
  ::rtl::OUString SAL_CALL TypeDetection::queryTypeByDescriptor(css::uno::Sequence< css::beans::PropertyValue >& lDescriptor,
                                                                sal_Bool                                         bAllowDeep )
      throw (css::uno::RuntimeException)
  {
      // make the descriptor more useable :-)
      ::comphelper::MediaDescriptor stlDescriptor(lDescriptor);
  
      // SAFE -> ----------------------------------
      ::osl::ResettableMutexGuard aLock(m_aLock);
  
      //*******************************************
      // parse given URL to split it into e.g. main and jump marks ...
      ::rtl::OUString sURL = stlDescriptor.getUnpackedValueOrDefault(::comphelper::MediaDescriptor::PROP_URL(), ::rtl::OUString());
  
  #if OSL_DEBUG_LEVEL > 0
      if (stlDescriptor.find(::rtl::OUString::createFromAscii("FileName")) != stlDescriptor.end())
          OSL_ENSURE(sal_False, "Detect using of deprecated and already unsupported MediaDescriptor property \"FileName\"!");
  #endif
  
      css::util::URL  aURL;
      aURL.Complete = sURL;
      css::uno::Reference< css::util::XURLTransformer > xParser(m_xSMGR->createInstance(SERVICE_URLTRANSFORMER), css::uno::UNO_QUERY);
      xParser->parseStrict(aURL);
  
      //*******************************************
      // preselected filter, type or document service?
      // use it as first "flat" detected type later!
      FlatDetection lFlatTypes;
      impl_getPreselection(aURL, stlDescriptor, lFlatTypes);
  
      //*******************************************
      // get all types, which match to the given descriptor
      // That can be true by: extensions/url pattern/mime type etcpp.
      m_rCache->detectFlatForURL(aURL, lFlatTypes);
  
      aLock.clear();
      // <- SAFE ----------------------------------
  
      ::rtl::OUString sType      ;
      ::rtl::OUString sLastChance;
  
      try
      {
          //*******************************************
          // verify every flat detected (or preselected!) type
          // by calling its registered deep detection service.
          // But break this loop if a type match to the given descriptor
          // by an URL pattern(!) or if deep detection isn't allowed from
          // outside (bAllowDeep=sal_False) or break the whole detection by
          // throwing an exception if creation of the might needed input
          // stream failed by e.g. an IO exception ...
          OUStringList lUsedDetectors;
          if (lFlatTypes.size()>0)
              sType = impl_detectTypeFlatAndDeep(stlDescriptor, lFlatTypes, bAllowDeep, lUsedDetectors, sLastChance);
  
          //*******************************************
          // if no flat detected (nor preselected!) type could be
          // verified and no error occurred during creation of
          // the might needed input stream, start detection
          // which uses all registered deep detection services.
          if (
              (!sType.getLength()) &&
              (bAllowDeep        )
             )
          {
              sType = impl_detectTypeDeepOnly(stlDescriptor, lUsedDetectors);
          }
          
          //*******************************************
          // flat detection failed
          // pure deep detection failed
          // => ask might existing InteractionHandler
          // means: ask user for it's decision
          if (!sType.getLength())
              sType = impl_askUserForTypeAndFilterIfAllowed(stlDescriptor);
  
          //*******************************************
          // no real detected type - but a might valid one.
          // update descriptor and set last chance for return.
          if (!sType.getLength() && sLastChance.getLength())
          {
              OSL_ENSURE(sal_False, "set first flat detected type without a registered deep detection service as \"last chance\" ... nevertheless some other deep detections said \"NO\". I TRY IT!");
              sType = sLastChance;
          }
      }
      catch(const css::uno::RuntimeException&)
          { throw; }
      catch(const css::uno::Exception&)
          { sType = ::rtl::OUString(); }
  
      //*******************************************
      // adapt media descriptor, so it contains the right values
      // for type/filter name/document service/ etcpp.
      impl_checkResultsAndAddBestFilter(stlDescriptor, sType); // Attention: sType is used as IN/OUT param here and will might be changed inside this method !!!
      impl_validateAndSetTypeOnDescriptor(stlDescriptor, sType);
  
      stlDescriptor >> lDescriptor;
      return sType;
  }
  
  /*-----------------------------------------------
      03.07.2003 10:36
  -----------------------------------------------*/
  void TypeDetection::impl_checkResultsAndAddBestFilter(::comphelper::MediaDescriptor& rDescriptor,
                                                        ::rtl::OUString&               sType      )
  {
      // a)
      // Dont overwrite a might preselected filter!
      ::rtl::OUString sFilter = rDescriptor.getUnpackedValueOrDefault(
                                  ::comphelper::MediaDescriptor::PROP_FILTERNAME(),
                                  ::rtl::OUString());
      if (sFilter.getLength())
          return;
  
      // b)
      // check a preselected document service too.
      // Then we have to search a suitable filter witin this module.
      ::rtl::OUString sDocumentService = rDescriptor.getUnpackedValueOrDefault(
                                              ::comphelper::MediaDescriptor::PROP_DOCUMENTSERVICE(),
                                              ::rtl::OUString());
      if (sDocumentService.getLength())
      {
          try
          {
              ::rtl::OUString sRealType = sType;
  
              #ifdef WORKAROUND_CSV_TXT_BUG_i60158
              // Workaround for #i60158#
              // We do not have right filter for Text_Ascii in calc nor a suitable filter for CSV in writer.
              // So we must overrule our detection and make the right things. Normaly we should have
              // one type TextAscii and two filters registered for these one type.
              // But then we loose automatic opening of CSV files in calc instead of opening these files
              // inside writer.
              if (
                  (sDocumentService.equalsAscii("com.sun.star.sheet.SpreadsheetDocument")) &&
                  (
                      (sRealType.equalsAscii("writer_Text"        )) ||
                      (sRealType.equalsAscii("writer_Text_encoded"))
                  )
                 )
              {
                  sRealType = ::rtl::OUString::createFromAscii("calc_Text_txt_csv_StarCalc");
              }
              else
              if (
                  (sDocumentService.equalsAscii("com.sun.star.text.TextDocument")) &&
                  (sRealType.equalsAscii("calc_Text_txt_csv_StarCalc"           ))
                 )
              {
                  sRealType = ::rtl::OUString::createFromAscii("writer_Text");
              }
              #endif // WORKAROUND_CSV_TXT_BUG_i60158
  
              // SAFE ->
              ::osl::ResettableMutexGuard aLock(m_aLock);
  
              // Attention: For executing next lines of code, We must be shure that
              // all filters already loaded :-(
              // That can disturb our "load on demand feature". But we have no other chance!
              m_rCache->load(FilterCache::E_CONTAINS_FILTERS);
  
              CacheItem lIProps;
              lIProps[PROPNAME_DOCUMENTSERVICE] <<= sDocumentService;
              lIProps[PROPNAME_TYPE           ] <<= sRealType;
              OUStringList lFilters = m_rCache->getMatchingItemsByProps(FilterCache::E_FILTER, lIProps);
  
              aLock.clear();
              // <- SAFE
  
              for (  OUStringList::const_iterator pIt  = lFilters.begin();
                     pIt != lFilters.end() && sFilter.getLength() == 0 ;
                   ++pIt                    )
              {
                  // SAFE ->
                  aLock.reset();
                  try
                  {
                      CacheItem aFilter = m_rCache->getItem(FilterCache::E_FILTER, *pIt);
                      sal_Int32 nFlags  = 0;
                      aFilter[PROPNAME_FLAGS] >>= nFlags;
  
                      if ((nFlags & FLAGVAL_IMPORT) == FLAGVAL_IMPORT)
                          sFilter = *pIt;
                  }
                  catch(const css::uno::Exception&) {}
                  aLock.clear();
                  // <- SAFE
              }
  
              if (sFilter.getLength() > 0)
              {
                  rDescriptor[::comphelper::MediaDescriptor::PROP_TYPENAME()  ] <<= sRealType;
                  rDescriptor[::comphelper::MediaDescriptor::PROP_FILTERNAME()] <<= sFilter;
                  sType = sRealType;
                  return;
              }
          }
          catch(const css::uno::Exception&)
              {}
      }
  
      // c)
      // We can use the preferred filter for the specified type.
      // Such preferred filter points:
      // - to the default filter of the preferred application
      // - or to any other filter if no preferred filter was set.
      // Note: It's an optimization only!
      // It's not guaranteed, that such preferred filter exists.
      sFilter = ::rtl::OUString();
      try
      {
          // SAFE ->
          ::osl::ResettableMutexGuard aLock(m_aLock);
  
          CacheItem aType = m_rCache->getItem(FilterCache::E_TYPE, sType);
          aType[PROPNAME_PREFERREDFILTER] >>= sFilter;
          CacheItem aFilter = m_rCache->getItem(FilterCache::E_FILTER, sFilter);
  
          aLock.clear();
          // <- SAFE
  
          // no exception => found valid type and filter => set it on the given descriptor
          rDescriptor[::comphelper::MediaDescriptor::PROP_TYPENAME()  ] <<= sType  ;
          rDescriptor[::comphelper::MediaDescriptor::PROP_FILTERNAME()] <<= sFilter;
          return;
      }
      catch(const css::uno::Exception&)
          {}
  
      // d)
      // Search for any import(!) filter, which is registered for this type.
      sFilter = ::rtl::OUString();
      try
      {
          // SAFE ->
          ::osl::ResettableMutexGuard aLock(m_aLock);
  
          // Attention: For executing next lines of code, We must be shure that
          // all filters already loaded :-(
          // That can disturb our "load on demand feature". But we have no other chance!
          m_rCache->load(FilterCache::E_CONTAINS_FILTERS);
  
          CacheItem lIProps;
          lIProps[PROPNAME_TYPE] <<= sType;
          OUStringList lFilters = m_rCache->getMatchingItemsByProps(FilterCache::E_FILTER, lIProps);
  
          aLock.clear();
          // <- SAFE
  
          OUStringList::const_iterator pIt;
          for (  pIt  = lFilters.begin();
                 pIt != lFilters.end()  ;
               ++pIt                    )
          {
              sFilter = *pIt;
  
              // SAFE ->
              aLock.reset();
              try
              {
                  CacheItem aFilter = m_rCache->getItem(FilterCache::E_FILTER, sFilter);
                  sal_Int32 nFlags  = 0;
                  aFilter[PROPNAME_FLAGS] >>= nFlags;
  
                  if ((nFlags & FLAGVAL_IMPORT) == FLAGVAL_IMPORT)
                      break;
              }
              catch(const css::uno::Exception&)
                  { continue; }
              aLock.clear();
              // <- SAFE
  
              sFilter = ::rtl::OUString();
          }
  
          if (sFilter.getLength())
          {
              rDescriptor[::comphelper::MediaDescriptor::PROP_TYPENAME()  ] <<= sType  ;
              rDescriptor[::comphelper::MediaDescriptor::PROP_FILTERNAME()] <<= sFilter;
              return;
          }
      }
      catch(const css::uno::Exception&)
          {}
  }
  
  /*-----------------------------------------------
      14.11.2003 12:06
  -----------------------------------------------*/
  sal_Bool TypeDetection::impl_getPreselectionForType(const ::rtl::OUString& sPreSelType,
                                                      const css::util::URL&  aParsedURL ,
                                                            FlatDetection&   rFlatTypes )
  {
      // Can be used to suppress execution of some parts of this method
      // if its already clear that detected type is valid or not.
      // Its necessary to use shared code at the end, which update
      // all return parameters constistency!
      sal_Bool bBreakDetection = sal_False;
  
      // Further we must know if it matches by pattern
      // Every flat detected type by pattern wont be detected deep!
      sal_Bool bMatchByPattern = sal_False;
  
      // And we must know if a preselection must be preferred, because
      // it matches by it's extension too.
      sal_Bool bMatchByExtension = sal_False;
  
      // If we e.g. collect all filters of a factory (be a forced factory preselection)
      // we should preferr all filters of this factory, where the type match the given URL.
      // All other types (which sorrespond to filters of the same factory - but dont match
      // the URL) should be "used later" for detection and sorted at the end of our return vector
      // rFlatTypes!
      // => bPreferredPreselection = (matchByExtension || matchByURLPattern)
      sal_Bool bPreferredPreselection = sal_False;
  
      // validate type
      ::rtl::OUString sType(sPreSelType);
      CacheItem       aType;
      try
      {
          // SAFE -> --------------------------
          ::osl::ResettableMutexGuard aLock(m_aLock);
          aType = m_rCache->getItem(FilterCache::E_TYPE, sType);
          aLock.clear();
          // <- SAFE --------------------------
      }
      catch(const css::container::NoSuchElementException&)
      {
          sType = ::rtl::OUString();
          bBreakDetection = sal_True;
      }
  
      if (!bBreakDetection)
      {
          // We can't check a preselected type for a given stream!
          // So we must believe, that it can work ...
          if (aParsedURL.Complete.equalsAsciiL("private:stream", 14))
              bBreakDetection = sal_True;
      }
  
      if (!bBreakDetection)
      {
          // extract extension from URL .. to check it case-insensitive !
          INetURLObject   aParser    (aParsedURL.Main);
          ::rtl::OUString sExtension = aParser.getExtension(INetURLObject::LAST_SEGMENT       ,
                                                            sal_True                          ,
                                                            INetURLObject::DECODE_WITH_CHARSET);
          sExtension = sExtension.toAsciiLowerCase();
  
          // otherwise we must know, if it matches to the given URL really.
          // especially if it matches by its extension or pattern registration.
          OUStringList lExtensions(aType[PROPNAME_EXTENSIONS]);
          OUStringList lURLPattern(aType[PROPNAME_URLPATTERN]);
  
          for (OUStringList::const_iterator pIt  = lExtensions.begin();
                                            pIt != lExtensions.end()  ;
                                          ++pIt                       )
          {
              ::rtl::OUString sCheckExtension(pIt->toAsciiLowerCase());
              if (sCheckExtension.equals(sExtension))
              {
                  bBreakDetection        = sal_True;
                  bMatchByExtension      = sal_True;
                  bPreferredPreselection = sal_True;
                  break;
              }
          }
  
          if (!bBreakDetection)
          {
              for (OUStringList::const_iterator pIt  = lURLPattern.begin();
                                                pIt != lURLPattern.end()  ;
                                              ++pIt                       )
              {
                  WildCard aCheck(*pIt);
                  if (aCheck.Matches(aParsedURL.Main))
                  {
                      bBreakDetection        = sal_True;
                      bMatchByPattern        = sal_True;
                      bPreferredPreselection = sal_True;
                      break;
                  }
              }
          }
  
          /*
              Comment ... why the following line of code should be comened out .-)
  
              This type does not seem to fit the requirements
              But its an existing and well known type.
              At least - [because may be the extension was missing :-( ]
              we should try to detect this type deep ...
              So we accept it here :-)
  
  		if (!bBreakDetection)
              sType = ::rtl::OUString();
  		*/
      }
  
      // if its a valid type - set it on all return values!
      if (sType.getLength())
      {
          FlatDetectionInfo aInfo;
          aInfo.sType              = sType;
          aInfo.bMatchByExtension  = bMatchByExtension;
          aInfo.bMatchByPattern    = bMatchByPattern;
          aInfo.bPreselectedAsType = sal_True;
  
          if (bPreferredPreselection)
              rFlatTypes.push_front(aInfo);
          else
              rFlatTypes.push_back(aInfo);
  
          return sal_True;
      }
  
      // not valid!
      return sal_False;
  }
  
  /*-----------------------------------------------
      14.11.2003 12:09
  -----------------------------------------------*/
  sal_Bool TypeDetection::impl_getPreselectionForFilter(const ::rtl::OUString& sPreSelFilter,
                                                        const css::util::URL&  aParsedURL   ,
                                                              FlatDetection&   rFlatTypes   )
  {
      // Can be used to suppress execution of some parts of this method
      // if its already clear that detected filter is valid or not.
      // Its necessary to use shared code at the end, which update
      // all return parameters constistency!
      sal_Bool bBreakDetection = sal_False;
  
      // validate filter
      ::rtl::OUString sFilter(sPreSelFilter);
      CacheItem       aFilter;
      try
      {
          // SAFE -> --------------------------
          ::osl::ResettableMutexGuard aLock(m_aLock);
          aFilter = m_rCache->getItem(FilterCache::E_FILTER, sFilter);
          aLock.clear();
          // <- SAFE --------------------------
      }
      catch(const css::container::NoSuchElementException&)
      {
          sFilter = ::rtl::OUString();
          bBreakDetection = sal_True;
      }
  
      if (!bBreakDetection)
      {
          // get its type and check if it matches the given URL
          ::rtl::OUString sType;
          aFilter[PROPNAME_TYPE] >>= sType;
  
          bBreakDetection = impl_getPreselectionForType(sType, aParsedURL, rFlatTypes);
  
          // not a valid type? -> not a valid filter!
          if (!bBreakDetection)
              sFilter = ::rtl::OUString();
      }
  
      // We have to mark all retrieved preselection items as "preselected by filter"!
      FlatDetection::iterator pIt;
      for (  pIt  = rFlatTypes.begin();
             pIt != rFlatTypes.end()  ;
           ++pIt                      )
      {
          FlatDetectionInfo& rInfo = *pIt;
          rInfo.bPreselectedAsType   = sal_False;
          rInfo.bPreselectedByFilter = sal_True;
      }
  
      if (sFilter.getLength())
          return sal_True;
      else
          return sal_False;
  }
  
  /*-----------------------------------------------
      14.11.2003 12:11
  -----------------------------------------------*/
  sal_Bool TypeDetection::impl_getPreselectionForDocumentService(const ::rtl::OUString& sPreSelDocumentService,
                                                                 const css::util::URL&  aParsedURL            ,
                                                                       FlatDetection&   rFlatTypes            )
  {
      // get all filters, which match to this doc service
      OUStringList lFilters;
      try
      {
          // SAFE -> --------------------------
          ::osl::ResettableMutexGuard aLock(m_aLock);
  
          // Attention: For executing next lines of code, We must be shure that
          // all filters already loaded :-(
          // That can disturb our "load on demand feature". But we have no other chance!
          m_rCache->load(FilterCache::E_CONTAINS_FILTERS);
  
          CacheItem lIProps;
          lIProps[PROPNAME_DOCUMENTSERVICE] <<= sPreSelDocumentService;
          lFilters = m_rCache->getMatchingItemsByProps(FilterCache::E_FILTER, lIProps);
  
          aLock.clear();
          // <- SAFE --------------------------
      }
      catch(const css::container::NoSuchElementException&)
      {
          lFilters.clear();
      }
  
      // step over all filters, and check if its registered type
      // match the given URL.
      // But use temp. list of "preselected types" instead of incoming rFlatTypes list!
      // The reason behind: we must filter the getted results. And copying of stl entries
      // is an easier job then removing it .-)
      FlatDetection lPreselections;
      for (OUStringList::const_iterator pFilter  = lFilters.begin();
                                        pFilter != lFilters.end()  ;
                                      ++pFilter                    )
      {
          const ::rtl::OUString sFilter = *pFilter;
          impl_getPreselectionForFilter(sFilter, aParsedURL, lPreselections);
      }
  
      // We have to mark all retrieved preselection items as "preselected by document service".
      // Further we must ignore all preselected items, which does not match the URL!
      FlatDetection::iterator pIt;
      for (  pIt  = lPreselections.begin();
             pIt != lPreselections.end()  ;
           ++pIt                          )
      {
          FlatDetectionInfo& rInfo = *pIt;
  
          /*
              #i60158#
              Preselection by DocumentService ...
              How many filters (and corresponding types) must be checked ?
              All or only the list of filters/types, which match to the given URL too ?
              There is no final decision about this currently. So we make it "configurable" .-)
          */
          #ifdef IGNORE_NON_URLMATCHING_TYPES_FOR_PRESELECTION_DOCUMENTSERVICE
          if (
              (!rInfo.bMatchByExtension) &&
              (!rInfo.bMatchByPattern  )
             )
             continue;
          #endif
  
          rInfo.bPreselectedAsType            = sal_False;
          rInfo.bPreselectedByFilter          = sal_False;
          rInfo.bPreselectedByDocumentService = sal_True ;
          rFlatTypes.push_back(rInfo);
      }
  
      return sal_True;
  }
  
  /*-----------------------------------------------
      14.11.2003 12:21
  -----------------------------------------------*/
  void TypeDetection::impl_getPreselection(const css::util::URL&                aParsedURL ,
                                                 ::comphelper::MediaDescriptor& rDescriptor,
                                                 FlatDetection&                 rFlatTypes )
  {
      // done to be shure, that only valid results leave this function!
      rFlatTypes.clear();
  
      /* #i55122#
          Sometimes we must detect files without or with real unknown extensions.
          If it does not work /which can happen of course .-)/, the user tried to preselect
          the right format. But some special dialogs (e.g. "Insert->Sheet From File")
          add it's own preselection too.
          So we have a combination of preselected values ...
  
          The we should preferr the most important one - set by the user.
          And the user normaly preselects a filter or type. The preslected
          document service cames from the dialog.
  
          Further it doesn't matter if the user preselected a filter or a document service.
          A filter selection is always more explicit then a document service selection.
          So it must be pereferred. An order between type and filter selection can't be discussed .-)
      */
  
      ::rtl::OUString sSelectedType = rDescriptor.getUnpackedValueOrDefault(::comphelper::MediaDescriptor::PROP_TYPENAME(), ::rtl::OUString());
      if (sSelectedType.getLength())
          impl_getPreselectionForType(sSelectedType, aParsedURL, rFlatTypes);
  
      ::rtl::OUString sSelectedFilter = rDescriptor.getUnpackedValueOrDefault(::comphelper::MediaDescriptor::PROP_FILTERNAME(), ::rtl::OUString());
      if (sSelectedFilter.getLength())
          impl_getPreselectionForFilter(sSelectedFilter, aParsedURL, rFlatTypes);
  
      ::rtl::OUString sSelectedDoc = rDescriptor.getUnpackedValueOrDefault(::comphelper::MediaDescriptor::PROP_DOCUMENTSERVICE(), ::rtl::OUString());
      if (sSelectedDoc.getLength())
          impl_getPreselectionForDocumentService(sSelectedDoc, aParsedURL, rFlatTypes);
  }
  
  /*-----------------------------------------------
      03.11.2003 09:17
  -----------------------------------------------*/
  ::rtl::OUString TypeDetection::impl_detectTypeFlatAndDeep(      ::comphelper::MediaDescriptor& rDescriptor   ,
                                                            const FlatDetection&                 lFlatTypes    ,
                                                                  sal_Bool                       bAllowDeep    ,
                                                                  OUStringList&                  rUsedDetectors,
                                                                  ::rtl::OUString&               rLastChance   )
  {
      // reset it everytimes, so the outside code can distinguish between
      // a set and a not set value.
      rLastChance = ::rtl::OUString();
      rUsedDetectors.clear();
  
      // step over all possible types for this URL.
      // solutions:
      // a) no types                                => no detection
      // b) deep detection not allowed              => return first valid type of list (because its the preferred or the first valid one)
      //    or(!) match by URLPattern               => in such case a deep detection will be suppressed!
      // c) type has no detect service              => safe the first occurred type without a detect service
      //                                               as "last chance"(!). It will be used outside of this method
      //                                               if no further type could be detected.
      //                                               It must be the first one, because it can be a preferred type.
      //                                               Our types list was sorted by such criteria!
      // d) detect service return a valid result    => return its decision
      // e) detect service return an invalid result
      //    or any needed information could not be
      //    getted from the cache                   => ignore it, and continue with search
  
      for (FlatDetection::const_iterator pFlatIt  = lFlatTypes.begin();
                                         pFlatIt != lFlatTypes.end()  ;
                                       ++pFlatIt                      )
      {
          const FlatDetectionInfo& aFlatTypeInfo = *pFlatIt;
                ::rtl::OUString    sFlatType     = aFlatTypeInfo.sType;
  
          if (!impl_validateAndSetTypeOnDescriptor(rDescriptor, sFlatType))
              continue;
  
          // b)
          if (
              (!bAllowDeep                  ) ||
              (aFlatTypeInfo.bMatchByPattern)
             )
          {
              return sFlatType;
          }
  
          try
          {
              // SAFE -> ----------------------------------
              ::osl::ResettableMutexGuard aLock(m_aLock);
              CacheItem aType = m_rCache->getItem(FilterCache::E_TYPE, sFlatType);
              aLock.clear();
  
              ::rtl::OUString sDetectService;
              aType[PROPNAME_DETECTSERVICE] >>= sDetectService;
  
              // c)
              if (!sDetectService.getLength())
              {
                  // accept or not accept flat types without deep detection: that's the question :-)
                  // May be there exists some states, where we have to use our LastChance feature instead
                  // of using the flat type directly.
                  // Here the list of task ID's, which wasrelated to these lines of code:
                  // #i47159#, #i43404#, #i46494#
  
                  // a flat detected type without the chance for a deep detection ... but preselected by the user
                  // explicitly (means preselected as type or filter ... not as documentservice!)
                  // should be accepted. So the user can overrule our detection.
                  if (
                      (aFlatTypeInfo.bPreselectedAsType  ) ||
                      (aFlatTypeInfo.bPreselectedByFilter)
                     )
                      return sFlatType;
  
                  // flat detected types without any registered deep detection service and not
                  // preselected by the user can be used as LAST CHANCE in case no other type could
                  // be detected. Of course only the first type without deep detector can be used.
                  // Further ones has to be ignored.
                  if (rLastChance.getLength() < 1)
                      rLastChance = sFlatType;
  
                  continue;
              }
  
              // dont forget to add every real asked deep detection service here.
              // Such detectors will be ignored if may be "impl_detectTypeDeepOnly()"
              // must be called later!
              rUsedDetectors.push_back(sDetectService);
              ::rtl::OUString sDeepType = impl_askDetectService(sDetectService, rDescriptor);
  
              // d)
              if (sDeepType.getLength())
                  return sDeepType;
          }
          catch(const css::container::NoSuchElementException&)
              {}
          // e)
      }
  
      return ::rtl::OUString();
      // <- SAFE ----------------------------------
  }
  
  /*-----------------------------------------------
      03.11.2003 09:19
  -----------------------------------------------*/
  ::rtl::OUString TypeDetection::impl_detectTypeDeepOnly(      ::comphelper::MediaDescriptor& rDescriptor          ,
                                                         const OUStringList&                  lOutsideUsedDetectors)
  {
      // We must know if a detect service was already used:
      //  i) in a combined flat/deep detection scenario outside or
      // ii) in this method for a deep detection only.
      // Reason: Such deep detection services work differently in these two modes!
      OUStringList                 lInsideUsedDetectors;
      OUStringList::const_iterator pIt;
  
      // a)
      // The list of "already used detect services" correspond to the list
      // of preselected or flat detected types. But these detect services was called
      // to check these types explicitly and return black/white ... yes/no only.
      // Now they are called to return any possible result. But we should preferr
      // these already used detect services against all other ones!
      for (  pIt  = lOutsideUsedDetectors.begin();
             pIt != lOutsideUsedDetectors.end()  ;
           ++pIt                                 )
      {
          const ::rtl::OUString& sDetectService = *pIt;
                ::rtl::OUString  sDeepType      = impl_askDetectService(sDetectService, rDescriptor);
          if (sDeepType.getLength())
              return sDeepType;
          lInsideUsedDetectors.push_back(sDetectService);
      }
  
      // SAFE -> ----------------------------------
      ::osl::ResettableMutexGuard aLock(m_aLock);
      OUStringList lDetectors = m_rCache->getItemNames(FilterCache::E_DETECTSERVICE);
      aLock.clear();
      // <- SAFE ----------------------------------
  
      // b)
      // Sometimes it would be nice to ask a special set of detect services before
      // any other detect service is asked. E.g. by using a preselection of a DocumentService.
      // That's needed to prevent us from asking the "wrong application module" and
      // opening the files into the "wrong application".
      ::rtl::OUString sPreselDocumentService = rDescriptor.getUnpackedValueOrDefault(
                                                  ::comphelper::MediaDescriptor::PROP_DOCUMENTSERVICE(),
                                                  ::rtl::OUString());
      if (sPreselDocumentService.getLength())
      {
          for (  pIt  = lDetectors.begin();
                 pIt != lDetectors.end()  ;
               ++pIt                      )
          {
              const ::rtl::OUString& sDetectService = *pIt;
  
              OUStringList::const_iterator pAlreadyUsed = ::std::find(lInsideUsedDetectors.begin(), lInsideUsedDetectors.end(), sDetectService);
              if (pAlreadyUsed != lInsideUsedDetectors.end())
                  continue;
  
              // SAFE -> --------------------------------------------------------
              aLock.reset();
  
              CacheItem lIProps;
              lIProps[PROPNAME_DETECTSERVICE] <<= sDetectService;
              OUStringList lTypes = m_rCache->getMatchingItemsByProps(FilterCache::E_TYPE, lIProps);
  
              aLock.clear();
              // <- SAFE --------------------------------------------------------
  
              sal_Bool bMatchDetectorToDocumentService = sal_False;
              OUStringList::const_iterator pIt2;
              for (  pIt2  = lTypes.begin();
                     pIt2 != lTypes.end()  ;
                   ++pIt2                  )
              {
                  const ::rtl::OUString& sType  = *pIt2;
  
                  try
                  {
                      // SAFE -> ----------------------------------------------------
                      aLock.reset();
  
                      CacheItem aType = m_rCache->getItem(FilterCache::E_TYPE, sType);
                      ::rtl::OUString sFilter;
                      aType[PROPNAME_PREFERREDFILTER] >>= sFilter;
                      CacheItem aFilter = m_rCache->getItem(FilterCache::E_FILTER, sFilter);
                      ::rtl::OUString sCheckDocumentService;
                      aFilter[PROPNAME_DOCUMENTSERVICE] >>= sCheckDocumentService;
  
                      aLock.clear();
                      // <- SAFE
  
                      if (sCheckDocumentService.equals(sPreselDocumentService))
                      {
                          bMatchDetectorToDocumentService = sal_True;
                          break;
                      }
                  }
                  catch(const css::uno::Exception&)
                      { continue; }
              }
  
              if (bMatchDetectorToDocumentService)
              {
                  ::rtl::OUString sDeepType = impl_askDetectService(sDetectService, rDescriptor);
                  if (sDeepType.getLength())
                      return sDeepType;
                  lInsideUsedDetectors.push_back(sDetectService);
              }
          }
      }
  
      // c)
      // Last chance. No "used detectors", no "preselected detectors" ... ask any existing detect services
      // for this till know unknown format.
      for (  pIt  = lDetectors.begin();
             pIt != lDetectors.end()  ;
           ++pIt                      )
      {
          const ::rtl::OUString& sDetectService = *pIt;
          
          OUStringList::const_iterator pAlreadyUsed = ::std::find(lInsideUsedDetectors.begin(), lInsideUsedDetectors.end(), sDetectService);
          if (pAlreadyUsed != lInsideUsedDetectors.end())
              continue;
  
          ::rtl::OUString sDeepType = impl_askDetectService(sDetectService, rDescriptor);
          if (sDeepType.getLength())
              return sDeepType;
      }
  
      return ::rtl::OUString();
  }
  
  /*-----------------------------------------------
      07.03.2005 11:13
  -----------------------------------------------*/
  void TypeDetection::impl_seekStreamToZero(comphelper::MediaDescriptor& rDescriptor)
  {
      // try to seek to 0 ... 
      // But because XSeekable is an optional interface ... try it only .-)
      css::uno::Reference< css::io::XInputStream > xStream = rDescriptor.getUnpackedValueOrDefault(
                                                              ::comphelper::MediaDescriptor::PROP_INPUTSTREAM(),
                                                              css::uno::Reference< css::io::XInputStream >());
      css::uno::Reference< css::io::XSeekable > xSeek(xStream, css::uno::UNO_QUERY);
      if (xSeek.is())
      {
          try
          {
              xSeek->seek(0);
          }
          catch(const css::uno::RuntimeException& exRun)
              { throw exRun; }
          catch(const css::uno::Exception&)
              {}
      }
  }
  
  /*-----------------------------------------------
      30.10.2003 15:12
  -----------------------------------------------*/
  ::rtl::OUString TypeDetection::impl_askDetectService(const ::rtl::OUString&               sDetectService,
                                                             ::comphelper::MediaDescriptor& rDescriptor   )
  {
      // Open the stream and add it to the media descriptor if this method is called for the first time.
      // All following requests to this method will detect, that there already exists a stream .-)
      // Attention: This method throws an exception if the stream could not be opened.
      // It's important to break any further detection in such case.
      // Catch it on the highest detection level only !!!
      impl_openStream(rDescriptor);
  
      // seek to 0 is an optional feature to be more robust against
      // "simple implemented detect services" .-)
      impl_seekStreamToZero(rDescriptor);
          
      css::uno::Reference< css::document::XExtendedFilterDetection > xDetector;
      css::uno::Reference< css::lang::XMultiServiceFactory >         xSMGR;
  
      // SAFE ->
      ::osl::ResettableMutexGuard aLock(m_aLock);
      xSMGR = m_xSMGR;
      aLock.clear();
      // <- SAFE
  
      // Attention! If e.g. an office module was not installed sometimes we find a
      // registered detect service, which is referred inside the configuration ... but not really
      // installed. On the other side we use third party components here, which can make trouble anyway.
      // So we should handle errors during creation of such services more gracefully .-)
      xDetector = css::uno::Reference< css::document::XExtendedFilterDetection >(
              xSMGR->createInstance(sDetectService),
              css::uno::UNO_QUERY);
              
      if ( ! xDetector.is())
          return ::rtl::OUString();
      
      ::rtl::OUString sDeepType;
      try
      {
          // start deep detection
          // Dont forget to convert stl descriptor to its uno representation.
      
          /* Attention!
                  You have to use an explicit instance of this uno sequence ...
                  Because its used as an in out parameter. And in case of a temp. used object
                  we will run into memory corruptions!
          */
          css::uno::Sequence< css::beans::PropertyValue > lDescriptor;
          rDescriptor >> lDescriptor;
          sDeepType = xDetector->detect(lDescriptor);
          rDescriptor << lDescriptor;
      }
      catch(const css::uno::Exception&)
          {
              // We should ignore errors here.
              // Thrown exceptions mostly will end in crash recovery ...
              // But might be we find another deep detection service which can detect the same
              // document without a problem .-)
              sDeepType = ::rtl::OUString();
          }
              
      // seek to 0 is an optional feature to be more robust against
      // "simple implemented detect services" .-)
      impl_seekStreamToZero(rDescriptor);
          
      // analyze the results
      // a) detect service returns "" => return "" too and remove TYPE/FILTER prop from descriptor
      // b) returned type is unknown  => return "" too and remove TYPE/FILTER prop from descriptor
      // c) returned type is valid    => check TYPE/FILTER props inside descriptor and return the type
  
      // this special helper checks for a valid type
      // and set right values on the descriptor!
      sal_Bool bValidType = impl_validateAndSetTypeOnDescriptor(rDescriptor, sDeepType);
      if (bValidType)
          return sDeepType;
      
      return ::rtl::OUString();
  }
  
  /*-----------------------------------------------
      17.12.2004 13:47
  -----------------------------------------------*/
  ::rtl::OUString TypeDetection::impl_askUserForTypeAndFilterIfAllowed(::comphelper::MediaDescriptor& rDescriptor)
  {
      // SAFE ->
      ::osl::ResettableMutexGuard aLock(m_aLock);
      css::uno::Reference< css::lang::XMultiServiceFactory > xSMGR = m_xSMGR;
      aLock.clear();
      // <- SAFE
  
      css::uno::Reference< css::task::XInteractionHandler > xInteraction = 
          rDescriptor.getUnpackedValueOrDefault(::comphelper::MediaDescriptor::PROP_INTERACTIONHANDLER(),
          css::uno::Reference< css::task::XInteractionHandler >());
          
      if (!xInteraction.is())
          return ::rtl::OUString();
          
      ::rtl::OUString sURL =
          rDescriptor.getUnpackedValueOrDefault(::comphelper::MediaDescriptor::PROP_URL(),
          ::rtl::OUString());
          
      css::uno::Reference< css::io::XInputStream > xStream =
          rDescriptor.getUnpackedValueOrDefault(::comphelper::MediaDescriptor::PROP_INPUTSTREAM(),
          css::uno::Reference< css::io::XInputStream >());
  
      // Dont distrub the user for "non existing files - means empty URLs" or
      // if we was forced to detect a stream.
      // Reason behind: We must be shure to ask user for "unknown contents" only ...
      // and not for "missing files". Especially if detection is done by a stream only
      // we can't check if the stream points to an "existing content"!
      if (
          (!sURL.getLength()                                     ) || // "non existing file" ?
          (!xStream.is()                                         ) || // non existing file !
          (sURL.equalsIgnoreAsciiCaseAsciiL("private:stream", 14))    // not a good idea .-)
         )
          return ::rtl::OUString();       
          
      try
      {
          // create a new request to ask user for it's decision about the usable filter
          ::framework::RequestFilterSelect aRequest(sURL);
          xInteraction->handle(aRequest.GetRequest());
      
          // "Cancel" pressed? => return with error
          if (aRequest.isAbort())
              return ::rtl::OUString();
          
          // "OK" pressed => verify the selected filter, get it's coressponding
          // type and return it. (BTW: We must update the media descriptor here ...)
          // The user selected explicitly a filter ... but normaly we are interested on
          // a type here only. But we must be shure, that the selected filter is used
          // too and no ambigous filter registration disturb us .-)
          
          ::rtl::OUString sFilter = aRequest.getFilter();
          if (!impl_validateAndSetFilterOnDescriptor(rDescriptor, sFilter))
              return ::rtl::OUString();
          
          ::rtl::OUString sType;
          rDescriptor[::comphelper::MediaDescriptor::PROP_TYPENAME()] >>= sType;
          return sType;
      }
      catch(const css::uno::Exception&)
          {}
          
      return ::rtl::OUString();        
  }
  
  /*-----------------------------------------------
      10.03.2004 10:30
  -----------------------------------------------*/
  void TypeDetection::impl_openStream(::comphelper::MediaDescriptor& rDescriptor)
      throw (css::uno::Exception)
  {
      sal_Bool bSuccess = sal_False;
      ::rtl::OUString sURL = rDescriptor.getUnpackedValueOrDefault( ::comphelper::MediaDescriptor::PROP_URL(), ::rtl::OUString() );
      sal_Bool bRequestedReadOnly = rDescriptor.getUnpackedValueOrDefault( ::comphelper::MediaDescriptor::PROP_READONLY(), sal_False );
      if ( sURL.getLength() && ::utl::LocalFileHelper::IsLocalFile( INetURLObject( sURL ).GetMainURL( INetURLObject::NO_DECODE ) ) )
      {
          // OOo uses own file locking mechanics in case of local file
          bSuccess = rDescriptor.addInputStreamOwnLock();
      }
      else
          bSuccess = rDescriptor.addInputStream();
  
      if ( !bSuccess )
          throw css::uno::Exception(_FILTER_CONFIG_FROM_ASCII_("Could not open stream."), static_cast< css::document::XTypeDetection* >(this));
  
      if ( !bRequestedReadOnly )
      {
          // The MediaDescriptor implementation adds ReadOnly argument if the file can not be opened for writing
          // this argument should be either removed or an additional argument should be added so that application
          // can separate the case when the user explicitly requests readonly document.
          // The current solution is to remove it here.
          rDescriptor.erase( ::comphelper::MediaDescriptor::PROP_READONLY() );
      }
  }
  
  /*-----------------------------------------------
      04.07.2003 13:47
  -----------------------------------------------*/
  void TypeDetection::impl_removeTypeFilterFromDescriptor(::comphelper::MediaDescriptor& rDescriptor)
  {
      ::comphelper::MediaDescriptor::iterator pItType   = rDescriptor.find(::comphelper::MediaDescriptor::PROP_TYPENAME()  );
      ::comphelper::MediaDescriptor::iterator pItFilter = rDescriptor.find(::comphelper::MediaDescriptor::PROP_FILTERNAME());
      if (pItType != rDescriptor.end())
          rDescriptor.erase(pItType);
      if (pItFilter != rDescriptor.end())
          rDescriptor.erase(pItFilter);
  }
  
  /*-----------------------------------------------
      14.10.2003 11:15
  -----------------------------------------------*/
  sal_Bool TypeDetection::impl_validateAndSetTypeOnDescriptor(      ::comphelper::MediaDescriptor& rDescriptor,
                                                              const ::rtl::OUString&               sType      )
  {
      // SAFE ->
      ::osl::ResettableMutexGuard aLock(m_aLock);
      if (m_rCache->hasItem(FilterCache::E_TYPE, sType))
      {
          rDescriptor[::comphelper::MediaDescriptor::PROP_TYPENAME()] <<= sType;
          return sal_True;
      }
      aLock.clear();
      // <- SAFE
  
      // remove all related informations from the descriptor
      impl_removeTypeFilterFromDescriptor(rDescriptor);
      return sal_False;
  }
  
  /*-----------------------------------------------
      04.07.2003 14:01
  -----------------------------------------------*/
  sal_Bool TypeDetection::impl_validateAndSetFilterOnDescriptor(      ::comphelper::MediaDescriptor& rDescriptor,
                                                                const ::rtl::OUString&               sFilter    )
  {
      try
      {
          // SAFE ->
          ::osl::ResettableMutexGuard aLock(m_aLock);
  
          CacheItem aFilter = m_rCache->getItem(FilterCache::E_FILTER, sFilter);
          ::rtl::OUString sType;
          aFilter[PROPNAME_TYPE] >>= sType;
          CacheItem aType = m_rCache->getItem(FilterCache::E_TYPE, sType);
  
          aLock.clear();
          // <- SAFE
  
          // found valid type and filter => set it on the given descriptor
          rDescriptor[::comphelper::MediaDescriptor::PROP_TYPENAME()  ] <<= sType  ;
          rDescriptor[::comphelper::MediaDescriptor::PROP_FILTERNAME()] <<= sFilter;
          return sal_True;
      }
      catch(const css::container::NoSuchElementException&){}
  
      // remove all related informations from the descriptor
      impl_removeTypeFilterFromDescriptor(rDescriptor);
      return sal_False;
  }
  
  /*-----------------------------------------------
      03.07.2003 10:36
  -----------------------------------------------*/
  ::rtl::OUString TypeDetection::impl_getImplementationName()
  {
      return ::rtl::OUString::createFromAscii("com.sun.star.comp.filter.config.TypeDetection");
  }
  
  /*-----------------------------------------------
      03.07.2003 11:27
  -----------------------------------------------*/
  css::uno::Sequence< ::rtl::OUString > TypeDetection::impl_getSupportedServiceNames()
  {
      css::uno::Sequence< ::rtl::OUString > lServiceNames(1);
      lServiceNames[0] = ::rtl::OUString::createFromAscii("com.sun.star.document.TypeDetection");
      return lServiceNames;
  }
  
  /*-----------------------------------------------
      09.07.2003 08:02
  -----------------------------------------------*/
  css::uno::Reference< css::uno::XInterface > SAL_CALL TypeDetection::impl_createInstance(const css::uno::Reference< css::lang::XMultiServiceFactory >& xSMGR)
  {
      TypeDetection* pNew = new TypeDetection(xSMGR);
      return css::uno::Reference< css::uno::XInterface >(static_cast< css::document::XTypeDetection* >(pNew), css::uno::UNO_QUERY);
  }
  
      } // namespace config
  } // namespace filter
  