Package er.extensions.appserver

Source Code of er.extensions.appserver.ERXBrowserFactory

//
// ERXBrowserFactory.java
// Project ERExtensions
//
// Created by tatsuya on Mon Jul 22 2002
//
package er.extensions.appserver;

import java.util.StringTokenizer;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import com.webobjects.appserver.WORequest;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSBundle;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;

import er.extensions.foundation.ERXMutableDictionary;
import er.extensions.foundation.ERXMutableInteger;
import er.extensions.foundation.ERXStringUtilities;

/**
* <span class="en">
* All WebObjects applications have exactly one <code>ERXBrowserFactory</code>
* instance. Its primary role is to manage {@link ERXBrowser} objects.
* It provides facility to parse <code>"user-agent"</code> HTTP header and to
* create an appropriate browser object. It also maintains the
* browser pool to store shared <code>ERXBrowser</code> objects.
* Since <code>ERXBrowser</code> object is immutable, it can be
* safely shared between sessions and <code>ERXBrowserFactory</code>
* tries to have only one instance of <code>ERXBrowser</code> for
* each kind of web browsers.
* <p>
* The primary method called by {@link ERXSession} and {@link ERXDirectAction}
* is {@link #browserMatchingRequest browserMatchingRequest}
* which takes a {@link com.webobjects.appserver.WORequest WORequest}
* as the parameter and returns a shared instance of browser object.
* You actually wouldn't have to call this function by yourself
* because <code>ERXSession</code> and <code>ERXDirectAction</code>
* provide {@link ERXSession#browser() browser}</code> method
* that returns a browser object for the current request for you.
* <p>
* Note that <code>ERXSession</code> and <code>ERXDirectAction</code>
* call <code>ERXBrowserFactory</code>'s
* {@link #retainBrowser retainBrowser} and {@link #releaseBrowser releaseBrowser} 
* to put the browser object to the browser pool when it is
* created and to remove the browser object from the pool when
* it is no longer referred from sessions and direct actions.
* <code>ERXSession</code> and <code>ERXDirectAction</code>
* automatically handle this and you do not have to call these
* methods from your code.<br>
* <p>
* The current implementation of the parsers support variety of
* Web browsers in the market such as Internet Explorer (IE),
* OmniWeb, Netscape, iCab and Opera, versions between 2.0 and 7.0. <br>
* <p>
* To customize the parsers for <code>"user-agent"</code> HTTP header,
* subclass <code>ERXBrowserFactory</code> and override methods
* like {@link #parseBrowserName parseBrowserName},
* {@link #parseVersion parseVersion},
* {@link #parseMozillaVersion parseMozillaVersion} and
* {@link #parsePlatform parsePlatForm}.
* Then put the following statement into the application's
* constructor.
* <p>
* <code>ERXBrowserFactory.{@link #setFactory
* setFactory(new SubClassOfERXBrowserFactory())};</code>
* <p>
* If you want to use your own subclass of <code>ERXBrowser</code>,
* put the follwoing statement into the application's constructor.
* <p>
* <code>ERXBrowserFactory.factory().{@link #setBrowserClassName
* setBrowserClassName("NameOfTheSubClassOfERXBrowser")}</code>
*
* <p>
* <pre>
* This implementation is tested with the following browsers (or "user-agent" strings)
* Please ask the guy (tatsuyak@mac.com) for WOUnitTest test cases.
*
* Mac OS X
* ----------------------------------------------------------------------------------
* iCab 2.8.1       Mozilla/4.5 (compatible; iCab 2.8.1; Macintosh; I; PPC)
* IE 5.21          Mozilla/4.0 (compatible; MSIE 5.21; Mac_PowerPC)
* Netscape 7.0b1   Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-US; rv:1.0rc2) Gecko/20020512 Netscape/7.0b1
* Netscape 6.2.3   Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-US; rv:0.9.4.1) Gecko/20020508 Netscape6/6.2.3
* OmniWeb 5.11.1   Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_7_3; en-US) AppleWebKit/533.21.1+(KHTML, like Gecko, Safari/533.19.4) Version/5.11.1 OmniWeb/622.18.0
* Safari 1.0b(v48) Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/48 (like Gecko) Safari/48
* iPhone 1.0       Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3
*
* Windows 2000
* ----------------------------------------------------------------------------------
* IE 6.0           Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
* IE 5.5           Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)
* Netscape 6.2.3   Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:0.9.4.1) Gecko/20020508 Netscape6/6.2.3
* Netscape 4.79    Mozilla/4.79 [en] (Windows NT 5.0; U)
* Opera 6.04       Mozilla/4.0 (compatible; MSIE 5.0; Windows 2000) Opera 6.04  [en]
*
* </pre>
*
* </span>
*
* <span class="ja">
* 全 WebObjects アプリケーションは一つの <code>ERXBrowserFactory</code> インスタンスを持っている。
* このメソッドは {@link ERXBrowser} オブジェクトを管理する責任があります。
*
* HTTP ヘッダーの <code>"user-agent"</code> をパース、及び適切なブラウザ・オブジェクトを作成する機能がある。
* さらに共有 <code>ERXBrowser</code> オブジェクトのブラウザ・プールをメンテナンスします。
*
* <code>ERXBrowser</code> オブジェクトは不可変で、セッション間での共有も安全です。
* <code>ERXBrowserFactory</code> は各ブラウザ種類に一つのオブジェクトのみを作成することを試します。
*
* {@link ERXSession} {@link ERXDirectAction} より呼び出されるメイン・メソッドは
* {@link #browserMatchingRequest browserMatchingRequest} で、引数として
* {@link com.webobjects.appserver.WORequest WORequest} を受け取り、
* ブラウザ・オブジェクトの共有インスタンスを戻します。
*
* 実際には直接呼ぶ必要はないでしょう。なぜなら、<code>ERXSession</code> <code>ERXDirectAction</code>
* は適切なブラウザ・オブジェクトをカレント・リクエストの為に戻す {@link ERXSession#browser browser} メソッドを用意しています。
*
* メモ:
* <code>ERXSession</code> <code>ERXDirectAction</code> <code>ERXBrowserFactory</code>
* {@link #retainBrowser retainBrowser} {@link #releaseBrowser releaseBrowser} を呼び出し、
* ブラウザ・オブジェクトが作成される時にブラウザ・プールに追加し、ダイレクト・アクションやセッションよりの参照が
* なくなるとブラウザ・プールから削除します。
*
* <code>ERXSession</code> <code>ERXDirectAction</code> は自動的に処理しますので、
* 自分でこれらのメソッドを呼ぶことはありません。
*
* カレント実装では多数の Webブラウザをサポートしています。例えば、 Internet Explorer (IE),
* OmniWeb, Netscape, iCab と Opera, バージョン番号は 2.0 から 7.0 まで <br>
*
* HTTP ヘッダーの <code>"user-agent"</code> パースを拡張する場合には <code>ERXBrowserFactory</code>
* をサブクラス化し、次のメソッド {@link #parseBrowserName parseBrowserName},
* {@link #parseVersion parseVersion}, {@link #parseMozillaVersion parseMozillaVersion}
* と {@link #parsePlatform parsePlatForm} をオーバライドすると良いです。
*
* 後は次のステートメントをコンストラクタにセットします。
* <code>ERXBrowserFactory.{@link #setFactory setFactory(new SubClassOfERXBrowserFactory())};</code>
*
* <code>ERXBrowser</code> の独自のサブクラスを作成されたい場合は次のステートメントをコンストラクタにセットします。
* <code>ERXBrowserFactory.factory().{@link #setBrowserClassName setBrowserClassName("NameOfTheSubClassOfERXBrowser")}</code>
*
* <pre>
* この実装は次のブラウザ "user-agent" でテストされています。
*
* Mac OS X
* ----------------------------------------------------------------------------------
* iCab 2.8.1       Mozilla/4.5 (compatible; iCab 2.8.1; Macintosh; I; PPC)
* IE 5.21          Mozilla/4.0 (compatible; MSIE 5.21; Mac_PowerPC)
* Netscape 7.0b1   Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-US; rv:1.0rc2) Gecko/20020512 Netscape/7.0b1
* Netscape 6.2.3   Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-US; rv:0.9.4.1) Gecko/20020508 Netscape6/6.2.3
* OmniWeb 5.11.1   Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_7_3; en-US) AppleWebKit/533.21.1+(KHTML, like Gecko, Safari/533.19.4) Version/5.11.1 OmniWeb/622.18.0
* Safari 1.0b(v48) Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/48 (like Gecko) Safari/48
* iPhone 1.0       Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3
*
* Windows 2000
* ----------------------------------------------------------------------------------
* IE 6.0           Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
* IE 5.5           Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)
* Netscape 6.2.3   Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:0.9.4.1) Gecko/20020508 Netscape6/6.2.3
* Netscape 4.79    Mozilla/4.79 [en] (Windows NT 5.0; U)
* Opera 6.04       Mozilla/4.0 (compatible; MSIE 5.0; Windows 2000) Opera 6.04  [en]
*
* </pre>
* </span>
*
* @property er.extensions.ERXBrowserFactory.FactoryClassName
* @property er.extensions.ERXBrowserFactory.BrowserClassName (default ERXBasicBrowser)
*/
public class ERXBrowserFactory {

    /** logging support */
    protected static final Logger log = Logger.getLogger(ERXBrowserFactory.class);

    /**
     * <span class="en">holds the default browser class name</span>
     * <span class="ja">デフォルト・ブラウザ・クラス名を保持</span>
     */
    private static final String _DEFAULT_BROWSER_CLASS_NAME = ERXBasicBrowser.class.getName();

    /**
     * <span class="en">Caches a reference to the browser factory</span>
     * <span class="ja">ブラウザ・ファクトリーへのリファレンス:キャシュ用</span>
     */
    private static ERXBrowserFactory _factory;

    /**
     * <span class="en">Expressions that define a robot</span>
     * <span class="ja">ロボットを認識できる定義:キャシュ用</span>
     */
    private static final NSMutableArray<Pattern> robotExpressions = new NSMutableArray();

    /**
     * <span class="en">Mapping of UAs to browsers</span>
     * <span class="ja">ブラウザと user-agent マップ:キャシュ用</span>
     */
    private static final NSMutableDictionary _cache = ERXMutableDictionary.synchronizedDictionary();

    /**
     * <span class="en">
     * Gets the singleton browser factory object.
     *
     * @return browser factory
     * </span>
     *
     * <span class="ja">
     * singleton ブラウザ・ファクトリー・オブジェクトを取得します
     *
     * @return ブラウザ・ファクトリー
     * </span>
     */
    @SuppressWarnings("javadoc")
    public static ERXBrowserFactory factory() {
        if (_factory == null) {
            String browserFactoryClass = System.getProperty("er.extensions.ERXBrowserFactory.FactoryClassName");
            if (browserFactoryClass != null && !browserFactoryClass.equals(ERXBrowserFactory.class.getName())) {
                log.debug("Creating browser factory for class name: " + browserFactoryClass);
                try {
                    Class browserClass = Class.forName(browserFactoryClass);
                    _factory = (ERXBrowserFactory)browserClass.newInstance();
                } catch (Exception e) {
                    log.error("Unable to create browser factory for class name \"" + browserFactoryClass + "\"", e);
                }
            }
            if (_factory == null) {
                log.debug("Factory null creating default browser factory. " + browserFactoryClass);
                _factory = new ERXBrowserFactory();
            }
        }
        return _factory;
    }

    /**
     * <span class="en">
     * Sets the browser factory used to create browser objects.
     *
     * @param newFactory new browser factory
     * </span>
     *
     * <span class="ja">
     * ブラウザ・オブジェクトを作成するブラウザ・ファクトリーをセットします
     *
     * @param newFactory - 新しいブラウザ・ファクトリー
     * </span>
     */
    public static void setFactory(ERXBrowserFactory newFactory) { _factory = newFactory; }

    /**
     * <span class="en">Caches the browser class name</span>
     * <span class="ja">ブラウザ・クラス名:キャシュ用</span>
     */
    protected String _browserClassName;

    /**
     * <span class="en">
     * Returns the name of the {@link ERXBrowser} subclass.
     * The default value is <code>"er.extensions.appserver.ERXBasicBrowser"</code>.
     *
     * @return  the name of the ERXBrowser subclass; default to
     *          <code>"er.extensions.appserver.ERXBasicBrowser"</code>
     * <span>
     *
     * <span class="ja">
     * {@link ERXBrowser} サブクラスの名前を戻します。
     *
     * デフォルト値は <code>"er.extensions.appserver.ERXBasicBrowser"</code>.
     *
     * @return ERXBrowser サブクラスの名前; デフォルト <code>"er.extensions.appserver.ERXBasicBrowser"</code>
     * </span>
     *
     * @see  #setBrowserClassName
     */
    @SuppressWarnings("javadoc")
    public String browserClassName() { return _browserClassName; }
   
    /**
     * <span class="en">
     * Sets the name of the {@link ERXBrowser} subclass.
     *
     * @param name  the name of the ERXBrowser subclass; ignored if null
     * </span>
     *
     * <span class="ja">
     * ERXBrowser サブクラスの名前をセットします
     *
     * @param name - ERXBrowser サブクラスの名前; null の場合は無視
     * </span>
     *
     * @see    #browserClassName
     * @see    #createBrowser
     */
    public void setBrowserClassName(String name) {
        if (name != null  &&  name.length() > 0)
            _browserClassName = name;
    }

    /**
     * Public browser constructor.
     */
    public ERXBrowserFactory() {
        // ENHANCEME: (tk) to arrow to set the class name from property files and launch arguments.
        setBrowserClassName(System.getProperty("er.extensions.ERXBrowserFactory.BrowserClassName",
                                               _DEFAULT_BROWSER_CLASS_NAME));
    }

    /**
     * <span class="en">
     * Gets a shared browser object for given request.
     * Parses <code>"user-agent"</code> string in the request and gets
     * the appropiate browser object.
     * <p>
     * This is the primary method to call from application logics, and
     * once you get a browser object, you are responsible to call
     * {@link #retainBrowser retainBrowser} to keep the browser
     * object in the browser pool.
     * <p>
     * You are also required to call {@link #releaseBrowser releaseBrowser}
     * to release the browser from the pool when it is no longer needed.
     *
     * @param request - WORequest
     *
     * @return     a shared browser object
     * </span>
     *
     * <span class="ja">
     * 指定 WORequest より共有ブラウザ・オブジェクトを取得します。
     * リクエスト内の <code>"user-agent"</code> 文字列をパースし、適切なブラウザ・オブジェクトを取得します。
     * <p>
     * アプリケーション・ロジックより呼ばれるメイン・メソッドになります。
     * ブラウザ・オブジェクトを取得した後、ブラウザ・オブジェクトをブラウザ・プールに登録する
     * retainBrowser メソッドの呼び出しは開発者の責任です。
     * </p>
     * <p>
     * 他にもオブジェクトが不必要になった場合には releaseBrowser を呼ばなければなりません。
     * </p>
     * @param request - WORequest
     *
     * @return 共有ブラウザ・オブジェクト
     * </span>
     */
    @SuppressWarnings("javadoc")
    public ERXBrowser browserMatchingRequest(WORequest request) {
        if (request == null) {
          throw new IllegalArgumentException("Request can't be null.");
        }

        String ua = request.headerForKey("user-agent");
        if (ua == null) {
            return getBrowserInstance(ERXBrowser.UNKNOWN_BROWSER, ERXBrowser.UNKNOWN_VERSION,
                ERXBrowser.UNKNOWN_VERSION, ERXBrowser.UNKNOWN_PLATFORM, null);
        }
       
         ERXBrowser result = (ERXBrowser) _cache.objectForKey(ua);
         if (result == null) {
           String browserName     = parseBrowserName(ua);
           String version       = parseVersion(ua);
           String mozillaVersion  = parseMozillaVersion(ua);
           String platform     = parsePlatform(ua);
           NSDictionary userInfo   = new NSDictionary(
               new Object[] {parseCPU(ua), parseGeckoVersion(ua)},
               new Object[] {"cpu", "geckoRevision"});
          
          result = getBrowserInstance(browserName, version, mozillaVersion, platform, userInfo);
          _cache.setObjectForKey(result, ua);
        }
        return result;
    }

    /**
     * <span class="en">
     * Gets a shared browser object from browser pool. If such browser
     * object does not exist, this method will create one by using
     * {@link #createBrowser createBrowser} method.
     *
     * @param browserName - string
     * @param version - string
     * @param mozillaVersion - string
     * @param platform - string
     * @param userInfo - dictionary
     *
     * @return a shared browser object
     * </span>
     *
     * <span class="en">
     * ブラウザ・プールより共有ブラウザ・オブジェクトを戻します。
     * このようなブラウザ・オブジェクトがなければ、
     * このメソッドは {@link #createBrowser createBrowser} メソッドを使って作成します。
     *
     * @param browserName - ブラウザ名
     * @param version - バージョン
     * @param mozillaVersion - mozillaの対応バージョン
     * @param platform - プラットフォーム
     * @param userInfo - ユーザ情報を持つディクショナリー
     *
     * @return 共有ブラウザ・オブジェクト
     * </span>
     */
     @SuppressWarnings("javadoc")
    public synchronized ERXBrowser getBrowserInstance(String browserName, String version, String mozillaVersion,
                                                String platform, NSDictionary userInfo) {
        String key = _computeKey(browserName, version, mozillaVersion, platform, userInfo);
        ERXBrowser browser = (ERXBrowser)_browserPool().objectForKey(key);
        if (browser == null)
            browser = createBrowser(browserName, version, mozillaVersion, platform, userInfo);
        return browser;
    }

    /**
     * <span class="en">
     * Creates a new browser object for given parameters. Override this
     * method if you need to provide your own subclass of {@link ERXBrowser}.
     * If you override it, your implementation should not call <code>super</code>.
     * <p>
     * Alternatively, use {@link #setBrowserClassName} and {@link #browserClassName}.
     *
     * @param browserName - string
     * @param version - string
     * @param mozillaVersion - string
     * @param platform - string
     * @param userInfo - dictionary
     *
     * @return new browser object that is a concrete subclass of <code>ERXBrowser</code>
     * </span>
     *
     * <span class="ja">
     * 指定パラメータを使って、新しいブラウザ・オブジェクトを作成します。
     * 独自のサブクラスが必要な場合にはこのメソッドをオーバライドすると良いのです。
     * オーバライドをした場合の実装が <code>super</code> を呼ばないこと。
     * <p>
     * 他には {@link #setBrowserClassName} {@link #browserClassName} を使用できます。
     *
     * @param browserName - ブラウザ名
     * @param version - バージョン
     * @param mozillaVersion - mozillaの対応バージョン
     * @param platform - プラットフォーム
     * @param userInfo - ユーザ情報を持つディクショナリー
     *
     * @return <code>ERXBrowser</code> を明確なサブクラスとして持つ新規ブラウザ・オブジェクト
     * </span>
     *
     * @see  #setBrowserClassName
     * @see  #browserClassName
     */
    @SuppressWarnings("javadoc")
    public synchronized ERXBrowser createBrowser(String browserName, String version, String mozillaVersion,
                                                String platform, NSDictionary userInfo) {
        ERXBrowser browser = null;
        try {
            browser = _createBrowserWithClassName(browserClassNameForBrowserNamed(browserName),
                                        browserName, version, mozillaVersion, platform, userInfo);
        } catch (Exception ex) {
            log.error("Unable to create a browser for class name: " + browserClassNameForBrowserNamed(browserName)
                            + " with exception: " + ex.getMessage() + ".  Will use default classes."
                            + " Please ensure that the fully-qualified name for the class is specified"
                            + " if it is in a different package.", ex);
        }
        if (browser == null) {
            try {
                browser = _createBrowserWithClassName(_DEFAULT_BROWSER_CLASS_NAME,
                                        browserName, version, mozillaVersion, platform, userInfo);
            } catch (Exception ex) {
                log.error("Unable to create even a default browser for class name: " + _DEFAULT_BROWSER_CLASS_NAME
                            + " with exception: " + ex.getMessage()
                            + "  Will instanciate a browser with regular"
                            + " new " + _DEFAULT_BROWSER_CLASS_NAME + "(...) statement.", ex);
                browser = new ERXBasicBrowser(browserName, version, mozillaVersion, platform, userInfo);
            }
        }
        return browser;
    }

    /**
     * <span class="ja">
     * クラス名を使って、ERXBrowserオブジェクトを作成します
     *
     * @param className - クラス名
     * @param browserName - ブラウザ名
     * @param version - バージョン情報
     * @param mozillaVersion - Mozilla バージョン
     * @param platform - プラットフォーム
     * @param userInfo - ユーザ・ディクショナリー
     *
     * @return ERXBrowser オブジェクト
     *
     * @throws ClassNotFoundException
     * @throws NoSuchMethodException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws java.lang.reflect.InvocationTargetException
     * </span>
     */
    private ERXBrowser _createBrowserWithClassName(String className, String browserName, String version,
                                            String mozillaVersion, String platform, NSDictionary userInfo)

                                            throws  ClassNotFoundException,
                                                    NoSuchMethodException,
                                                    InstantiationException,
                                                    IllegalAccessException,
                                                    java.lang.reflect.InvocationTargetException
    {
                           
        Class browserClass = Class.forName(className);
        Class[] paramArray = new Class[] { String.class, String.class, String.class,
                                                                String.class, NSDictionary.class };
        java.lang.reflect.Constructor constructor = browserClass.getConstructor(paramArray);
        return (ERXBrowser)constructor.newInstance(new Object[] {
                    browserName, version, mozillaVersion, platform, userInfo } );
    }
       

    /**
     * <span class="en">
     * Retains a given browser object.
     *
     * @param browser to be retained
     * </span>
     *
     * <span class="ja">
     * 指定ブラウザ・オブジェクトをブラウザ・プールに登録します
     *
     * @param browser - ブラウザ・プールに登録するブラウザ・オブジェクト
     * </span>
     */
    public synchronized void retainBrowser(ERXBrowser browser) {
        String key = _computeKey(browser);
        _browserPool().setObjectForKey(browser, key);
        _incrementReferenceCounterForKey(key);
    }

    /**
     * <span class="en">
     * Decrements the retain count for a given
     * browser object.
     *
     * @param browser - to be released
     * </span>
     *
     * <span class="ja">
     * 指定ブラウザ・オブジェクトをブラウザ・プールの登録から解除します
     *
     * @param browser - ブラウザ・プールの登録から解除するブラウザ・オブジェクト
     * </span>
     */
    public synchronized void releaseBrowser(ERXBrowser browser) {
        String key = _computeKey(browser);
        ERXMutableInteger count = _decrementReferenceCounterForKey(key);
        if (count == null) {
            // Perhaps forgot to call registerBrowser() but try to remove the browser for sure
            _browserPool().removeObjectForKey(key);
        } else if (count.intValue() <= 0) {
            _browserPool().removeObjectForKey(key);
            _referenceCounters().removeObjectForKey(key);
        }
    }

    /**
     * <span class="en">
     * Adds the option to use multiple different ERXBrowser subclasses
     * depending on the name of the browser.
     *
     * @param browserName - name of the browser
     *
     * @return ERXBrowser subclass class name
     * </span>
     *
     * <span class="ja">
     * 複数の ERXBrowser サブクラスを使用できる様にブラウザ名を渡します
     *
     * @param browserName - ブラウザ名
     *
     * @return ERXBrowser サブクラス名
     * </span>
     */
    @SuppressWarnings("javadoc")
    public String browserClassNameForBrowserNamed(String browserName) {
        return browserClassName();
    }

    /**
     * <span class="ja">
     * ブラウザ名をパースします
     *
     * @param userAgent
     *
     * @return ブラウザ名
     * </span>
     */
    public String parseBrowserName(String userAgent) {
        String browserString = _browserString(userAgent);
        String browser  = ERXBrowser.UNKNOWN_BROWSER;
        if (isRobot(browserString))             browser  = ERXBrowser.ROBOT;
        else if (browserString.indexOf("Chrome") > -1)     browser  = ERXBrowser.CHROME;
        else if (browserString.indexOf("MSIE") > -1)     browser  = ERXBrowser.IE;
        else if (browserString.indexOf("Safari") > -1)     browser  = ERXBrowser.SAFARI;
        else if (browserString.indexOf("Firefox") > -1)   browser  = ERXBrowser.FIREFOX;
        else if (browserString.indexOf("OmniWeb") > -1)    browser  = ERXBrowser.OMNIWEB;
        else if (browserString.indexOf("iCab") > -1)    browser  = ERXBrowser.ICAB;
        else if (browserString.indexOf("Opera") > -1)    browser  = ERXBrowser.OPERA;
        else if (browserString.indexOf("Netscape") > -1browser  = ERXBrowser.NETSCAPE;
        else if (browserString.startsWith("Mozilla") && (browserString.indexOf("compatible") == -1))  browser  = ERXBrowser.MOZILLA;

        // This condition should always come last because *all* browsers have
        // the word Mozilla at the beginning of their user-agent string.
        else if (browserString.indexOf("Mozilla") > -1)    browser  = ERXBrowser.NETSCAPE;

        return browser;
    }
   
    /**
     * <span class="ja">
     * ロボットかどうかを調べる
     * [Resources内:robots.txtを必須]
     *
     * @param userAgent
     *
     * @return ロボットの場合は true が戻ります
     * </span>
     */
    private boolean isRobot(String userAgent) {
      synchronized (robotExpressions) {
      if(robotExpressions.count()==0) {
        String strings = ERXStringUtilities.stringFromResource("robots", "txt", NSBundle.bundleForName("ERExtensions"));
        for (String item : NSArray.componentsSeparatedByString(strings, "\n")) {
          if(item.trim().length() > 0 && item.charAt(0) != '#') {
            robotExpressions.addObject(Pattern.compile(item));
          }
        }
      }
      userAgent = userAgent.toLowerCase();
      for (Pattern pattern : robotExpressions) {
        if (pattern.matcher(userAgent).find()) {
          log.debug(pattern + " matches  " + userAgent);
          return true;
        }
      }
    }
   
      return false;
    }
   
    /**
     * <span class="ja">
     * GeckoVersionをパースします
     *
     * @param userAgent
     *
     * @return GeckoVersion
     * </span>
     */
    public String parseGeckoVersion(String userAgent) {
      if (userAgent.indexOf("Gecko") >= 0) {
        final String revString = "; rv:";
        int startPos = userAgent.indexOf(revString) + revString.length();
            if (startPos > revString.length()) {
             
              int endPos = userAgent.indexOf(")", startPos);
             
              if (endPos > startPos) {
                return userAgent.substring(startPos, endPos);
              }  
            }
        }
        return ERXBrowser.NO_GECKO;
    }

    /**
     * <span class="ja">
     * バージョン番号をパースします
     *
     * @param userAgent
     *
     * @return バージョン番号
     * </span>
     */
    public String parseVersion(String userAgent) {
        String versionString = _versionString(userAgent);
        int startpos;
        String version = ERXBrowser.UNKNOWN_VERSION;
       
        // Remove "Netscape6" from string such as "Netscape6/6.2.3",
        // otherwise this method will produce wrong result "6/6.2.3" as the version
        final String netscape6 = "Netscape6";
        startpos = versionString.indexOf(netscape6);
        if (startpos > -1)
            versionString = versionString.substring(startpos + netscape6.length());
       
        // Find first numeric in the string such as "MSIE 5.21; Mac_PowerPC)"
        startpos = ERXStringUtilities.indexOfNumericInString(versionString);

        if (startpos > -1) {
            StringTokenizer st = new StringTokenizer(versionString.substring(startpos), " ;");
            if (st.hasMoreTokens())
                version = st.nextToken()// Will return "5.21" portion of "5.21; Mac_PowerPC)"
        }
    // Test if we got a real number
    try {
          String normalizedVersion = ERXStringUtilities.removeExtraDotsFromVersionString(version);
      Double.parseDouble(normalizedVersion);
    }
    catch (NumberFormatException e) {
      version = ERXBrowser.UNKNOWN_VERSION;
    }
        return version;
    }

    /**
     * <span class="ja">
     * Mozillaバージョンをパースします
     *
     * @param userAgent
     *
     * @return Mozillaバージョン
     * </span>
     */
    public String parseMozillaVersion(String userAgent) {
        final String mozilla = "Mozilla/";
        String mozillaVersion = ERXBrowser.UNKNOWN_VERSION;
        int startpos = userAgent.indexOf(mozilla);
        if (startpos > -1) {
            StringTokenizer st = new StringTokenizer(userAgent.substring(startpos + mozilla.length()), " ;");
            if (st.hasMoreTokens())
                mozillaVersion = st.nextToken()// Will return "5.21" portion of "5.21; Mac_PowerPC)"
        }
        return mozillaVersion;
    }

    /**
     * <span class="ja">
     * プラットフォームをパースします
     *
     * @param userAgent
     *
     * @return プラットフォーム
     * </span>
     */
    public String parsePlatform(String userAgent) {
        String platform = ERXBrowser.UNKNOWN_PLATFORM;
        if      (userAgent.indexOf("Win") > -1)   platform = ERXBrowser.WINDOWS;
        else if ((userAgent.indexOf("iPhone") > -1) || (userAgent.indexOf("iPod") > -1))   platform = ERXBrowser.IPHONE;
        else if (userAgent.indexOf("iPad") > -1) platform = ERXBrowser.IPAD;
        else if (userAgent.indexOf("Mac") > -1)   platform = ERXBrowser.MACOS;
        else if (userAgent.indexOf("Linux") > -1)   platform = ERXBrowser.LINUX;
        return platform;
    }

    /**
     * <span class="ja">
     * CPUをパースします
     *
     * @param userAgent
     *
     * @return CPU
     * </span>
     */
    public String parseCPU(String userAgent) {
        String cpu = ERXBrowser.UNKNOWN_CPU;
        if      (userAgent.indexOf("PowerPC") > -1)   cpu = ERXBrowser.POWER_PC;
        else if (userAgent.indexOf("PPC") > -1)   cpu = ERXBrowser.POWER_PC;
        return cpu;
    }

    /**
     * <span class="ja">
     * userAgent よりブラウザ文字列を戻します
     *
     * @param userAgent
     *
     * @return ブラウザ文字列
     * </span>
     */
    private String _browserString(String userAgent) {
        int startpos;

        // Get substring "Chrome/0.X.Y.Z Safari/525.13"
        // from          "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) "
        //               "AppleWebKit/525.13 (KHTML, wie z. B. Gecko) Chrome/0.X.Y.Z Safari/525.13"
        final String chrome = "Chrome";
        startpos = userAgent.indexOf(chrome);
        if (startpos > -1)
            return userAgent.substring(startpos);
       
        // Get substring "OmniWeb/622.18.0"
        // from          "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_7_3; en-US)"
        //               "AppleWebKit/533.21.1+(KHTML, like Gecko, Safari/533.19.4) "
        //               "Version/5.11.1 OmniWeb/622.18.0"
        final String omniWeb = "OmniWeb";
        startpos = userAgent.indexOf(omniWeb);
        if (startpos > -1)
          return userAgent.substring(startpos);
       
        // Get substring "Safari/48"
        // from          "Safari 1.0b(v48) Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) "
        //               "AppleWebKit/48 (like Gecko) Safari/48"
        final String safari = "Safari";
        startpos = userAgent.indexOf(safari);
        if (startpos > -1)
          return userAgent.substring(startpos);

        // Get substring "Opera 6.04  [en]"
        // from          "Mozilla/4.0 (compatible; MSIE 5.0; Windows 2000) Opera 6.04  [en]"
        final String opera = "Opera";
        startpos = userAgent.indexOf(opera);
        if (startpos > -1)
          return userAgent.substring(startpos);

        // Get substring "MSIE 5.21; Mac_PowerPC)"
        // from          "Mozilla/4.0 (compatible; MSIE 5.21; Mac_PowerPC)"
        final String compatible = "compatible;";
        startpos = userAgent.indexOf(compatible);
        if (startpos > -1)
          return userAgent.substring(startpos + compatible.length());
           
        // Get substring "Netscape6/6.2.3"
        // from          "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-US; rv:0.9.4.1)
        //                Gecko/20020508 Netscape6/6.2.3"
        final String netscape = "Netscape";
        startpos = userAgent.indexOf(netscape);
        if (startpos > -1)
          return userAgent.substring(startpos);
           
        return userAgent;
    }

    /** <span class="ja">ブラウザ・プール</span> */
    private NSMutableDictionary _browserPool;
    private NSMutableDictionary _browserPool() {
        if (_browserPool == null)
            _browserPool = new NSMutableDictionary();
        return _browserPool;
    }

    /** <span class="ja">リファレンス・カウント・プール</span> */
    private NSMutableDictionary _referenceCounters;
    private NSMutableDictionary _referenceCounters() {
        if (_referenceCounters == null)
            _referenceCounters = new NSMutableDictionary();
        return _referenceCounters;
    }

    /**
     * <span class="ja">
     * key を使って、_referenceCounters ディクショナリー内のカウンターに1を足すこと
     *
     * @param key - カウンターを足す key
     *
     * @return 新しいカウンターの値を戻します
     * </span>
     */
    private ERXMutableInteger _incrementReferenceCounterForKey(String key) {
        ERXMutableInteger count = (ERXMutableInteger)_referenceCounters().objectForKey(key);
        if (count != null)
            count.increment();
        else {
            count = new ERXMutableInteger(1);
            _referenceCounters().setObjectForKey(count, key);
        }
        if (log.isDebugEnabled())
            log.debug("_incrementReferenceCounterForKey() - count = " + count + ", key = " + key);
        return count;
    }

    /**
     * <span class="ja">
     * key を使って、_referenceCounters ディクショナリー内のカウンターに1を減らすこと
     *
     * @param key - カウンターを減らす key
     *
     * @return 新しいカウンターの値を戻します
     * <span>
     */
    private ERXMutableInteger _decrementReferenceCounterForKey(String key) {
        ERXMutableInteger count = (ERXMutableInteger)_referenceCounters().objectForKey(key);
        if (count != null
            count.decrement();
       
        if (log.isDebugEnabled())
            log.debug("_decrementReferenceCounterForKey() - count = " + count + ", key = " + key);
        return count;
    }

    /**
     * <span class="ja">
     * 下記の引数を使って、合計されている文字列キーを戻します。
     *
     * @param browser - ブラウザ・オブジェクト
     *
     * @return 合計されている文字列キー
     * </span>
     */
    private String _computeKey(ERXBrowser browser) {
        return browser.browserName() + "." + browser.version() + "." + browser.mozillaVersion() + "."
                        + browser.platform() + "." + browser.userInfo();
    }

    /**
     * <span class="ja">
     * 下記の引数を使って、合計されている文字列キーを戻します。
     *
     * @param browserName - ブラウザ名
     * @param version - バージョン
     * @param mozillaVersion - mozillaバージョン
     * @param platform - プラットフォーム
     * @param userInfo - ユーザ情報ディクショナリー
     *
     * @return 合計されている文字列キー
     * </span>
     */
    private String _computeKey(String browserName, String version, String mozillaVersion,
                                                                String platform, NSDictionary userInfo) {
        return browserName + "." + version + "." + mozillaVersion + "." + platform + "." + userInfo;
    }
   
    private String _versionString(String userAgent) {
      String versionString;

      int startpos = userAgent.indexOf("Version/");
      if (startpos > -1)  {
        versionString = userAgent.substring(startpos);
      } else {
        startpos = userAgent.indexOf("Firefox/");
          if (startpos > -1)  {
            versionString = userAgent.substring(startpos);
          } else {
            versionString = _browserString(userAgent);
          }
      }

      return versionString;
    }

}
TOP

Related Classes of er.extensions.appserver.ERXBrowserFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.