001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.fukurou.mail;
017
018// import java.io.InputStream;
019// import java.io.OutputStream;
020// import java.io.ByteArrayOutputStream;
021// import java.io.ByteArrayInputStream;
022import java.io.UnsupportedEncodingException;
023// import java.io.IOException;
024
025// import javax.activation.DataHandler;
026// import javax.activation.DataSource;
027import javax.mail.internet.InternetAddress;
028import javax.mail.internet.MimeMessage;
029// import javax.mail.internet.MimeUtility;
030import javax.mail.MessagingException;
031// import com.sun.mail.util.BASE64EncoderStream;
032
033// import java.nio.charset.Charset;             // 5.5.2.6 (2012/05/25)
034
035/**
036 * MailCharset は、E-Mail 送信時のエンコードに応じた処理を行う為の、
037 * インターフェースです。
038 *
039 * E-Mail で日本語を送信する場合、ISO-2022-JP(JISコード)化して、7bit で
040 * エンコードして送信する必要がありますが、Windows系の特殊文字や、unicodeと
041 * 文字のマッピングが異なる文字などが、文字化けします。
042 * 対応方法としては、
043 * 『1.Windows-31J + 8bit 送信』
044 * 『2.ISO-2022-JP に独自変換 + 7bit 送信』
045 * の方法があります。
046 * 今回、この2つの方法について、それぞれサブクラス化を行い、処理できるように
047 * したのが、このインターフェース、および、サブクラスです。
048 *
049 * 『1.Windows-31J + 8bit 送信』の方法は、通常の JavaMail API に準拠して
050 * 処理を行う、Mail_Windows31J_Charset サブクラスで実装しています。
051 * 古いメイラーおよび、古いメールサーバーではメール転送できない為、
052 * この方式は、社内で使用する場合のみに、利用できますが、主としてWindows系の
053 * 社内システムにおいては、こちらの方が、なにかとトラブルは少ないと思います。
054 *
055 * 『2.ISO-2022-JP に独自変換 + 7bit 送信』の実装は、
056 * JAVA PRESS Vol.37 (http://www.gihyo.co.jp/magazines/javapress)の
057 * 【特集1】 決定版! サーバサイドJavaの日本語処理
058 *  第3章:JavaMailの日本語処理プログラミング……木下信
059 *“マルチプラットフォーム”な日本語メール送信術 完全解説
060 * でのサンプルアプリケーション
061 * http://www.gihyo.co.jp/book/2004/225371/download/toku1_3.zip
062 * を、使用して、Mail_ISO2022JP_Charset サブクラスで実装しています。
063 *
064 * これらのサブクラスは、MailCharsetFactory ファクトリクラスより、作成されます。
065 * その場合、引数のキャラクタセット名は、Windows-31J 、MS932 か、それ以外となっています。
066 * それ以外が指定された場合は、ISO-2022-JP を使用します。
067 *
068 * @version  4.0
069 * @author   Kazuhiko Hasegawa
070 * @since    JDK5.0,
071 */
072public interface MailCharset {
073
074        /**
075         * テキストをセットします。
076         * Part#setText() の代わりにこちらを使うようにします。
077         *
078         * ※ 内部で、MessagingException が発生した場合は、RuntimeException に変換されて throw されます。
079         *
080         * @param mimeMsg MimeMessage最大取り込み件数
081         * @param text    設定するテキスト
082         */
083        void setTextContent( MimeMessage mimeMsg, String text ) ;
084
085        /**
086         * 日本語を含むヘッダ用テキストを生成します。
087         * 変換結果は ASCII なので、これをそのまま setSubject や InternetAddress
088         * のパラメタとして使用してください。
089         *
090         * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
091         *
092         * @param text    設定するテキスト
093         *
094         * @return      日本語を含むヘッダ用テキスト
095         */
096        String encodeWord( String text ) ;
097
098        /**
099         * 日本語を含むアドレスを生成します。
100         * personal に、日本語が含まれると想定しています。
101         * サブクラスで、日本語処理を行う場合の方法は、それぞれ異なります。
102         *
103         * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
104         *
105         * @param address    アドレス部分
106         * @param personal   日本語の説明部分
107         *
108         * @return      日本語を含むアドレス
109         */
110        InternetAddress getAddress( String address,String personal ) ;
111
112        /**
113         * Content-Transfer-Encoding を指定する場合の ビット数を返します。
114         *
115         * Windows系は、8bit / ISO-2022-JP 系は、7bit になります。
116         *
117         * @return      ビット数
118         */
119        String getBit() ;
120}
121
122/**
123 * MailCharsetFactory は、MailCharset インターフェースを実装したサブクラスを
124 * 作成する ファクトリクラスです。
125 *
126 * 引数のキャラクタセット名が、Windows-31J 、MS932 の場合は、
127 * 『1.Windows-31J + 8bit 送信』 の実装である、Mail_Windows31J_Charset
128 * サブクラスを返します。
129 * それ以外が指定された場合は、ISO-2022-JP を使用して、『2.ISO-2022-JP に独自変換 + 7bit 送信』
130 * の実装である、Mail_ISO2022JP_Charset サブクラスを返します。
131 *
132 * @version  4.0
133 * @author   Kazuhiko Hasegawa
134 * @since    JDK5.0,
135 */
136//class MailCharsetFactory {
137//
138//      /**
139//       * インスタンスの生成を抑止します。
140//       */
141//      private MailCharsetFactory() {
142//              // 何もありません。(PMD エラー回避)
143//      }
144//
145//      /**
146//       * キャラクタセットに応じた、MailCharset オブジェクトを返します。
147//       *
148//       * Windows-31J 、MS932 、Shift_JIS の場合は、Mail_Windows31J_Charset
149//       * その他は、ISO-2022-JP として、Mail_ISO2022JP_Charset を返します。
150//       *
151//       * 注意:null の場合は、デフォルトではなく、Mail_ISO2022JP_Charset を返します。
152//       *
153//       * @param  charset キャラクタセット[Windows-31J/MS932/Shift_JIS/その他]
154//       *
155//       * @return MailCharset
156//       */
157//      static MailCharset newInstance( final String charset ) {
158//              final MailCharset mcset;
159//
160//              if( "MS932".equalsIgnoreCase( charset ) ||
161//                      "Shift_JIS".equalsIgnoreCase( charset ) ||
162//                      "Windows-31J".equalsIgnoreCase( charset ) ) {
163//                              mcset = new Mail_Windows31J_Charset( charset );
164//              }
165//              else {
166//                      mcset = new Mail_ISO2022JP_Charset();
167//              }
168//              return mcset ;
169//      }
170//}
171
172/**
173 * MailCharset インターフェースを実装した Windwos-31J エンコード時のサブクラスです。
174 *
175 * 『1.Windows-31J + 8bit 送信』 の実装です。
176 *
177 * @version  4.0
178 * @author   Kazuhiko Hasegawa
179 * @since    JDK5.0,
180 */
181//class Mail_Windows31J_Charset implements MailCharset {
182//      private final String charset ;                  // "Windows-31J" or "MS932"
183//
184//      /**
185//       * 引数に、エンコード方式を指定して、作成するコンストラクタです。
186//       *
187//       * @param charset String
188//       */
189//      public Mail_Windows31J_Charset( final String charset ) {
190//              this.charset = charset;
191//      }
192//
193//      /**
194//       * テキストをセットします。
195//       * Part#setText() の代わりにこちらを使うようにします。
196//       *
197//       * @param mimeMsg MimeMessage
198//       * @param text    String
199//       * @throws RuntimeException(MessagingException)
200//       */
201//      public void setTextContent( final MimeMessage mimeMsg, final String text ) {
202//              try {
203//                      mimeMsg.setText( text,charset );                // "text/plain" Content
204//              }
205//              catch( MessagingException ex ) {
206//                      String errMsg = "指定のテキストをセットできません。"
207//                                                              + "text=" + text + " , charset=" + charset ;
208//                      throw new RuntimeException( errMsg,ex );
209//              }
210//      }
211//
212//      /**
213//       * 日本語を含むヘッダ用テキストを生成します。
214//       * 変換結果は ASCII なので、これをそのまま setSubject や InternetAddress
215//       * のパラメタとして使用してください。
216//       *
217//       * @param text    String
218//       *
219//       * @return      日本語を含むヘッダ用テキスト
220//       * @throws RuntimeException(UnsupportedEncodingException)
221//       */
222//      public String encodeWord( final String text ) {
223//              try {
224//                      return MimeUtility.encodeText( text, charset, "B" );
225//              }
226//              catch( UnsupportedEncodingException ex ) {
227//                      String errMsg = "指定のエンコードが出来ません。"
228//                                                              + "text=" + text + " , charset=" + charset ;
229//                      throw new RuntimeException( errMsg,ex );
230//              }
231//      }
232//
233//      /**
234//       * 日本語を含むアドレスを生成します。
235//       * personal に、日本語が含まれると想定しています。
236//       * サブクラスで、日本語処理を行う場合の方法は、それぞれ異なります。
237//       *
238//       * @param address    String
239//       * @param personal   String
240//       *
241//       * @return InternetAddress
242//       * @throws RuntimeException(UnsupportedEncodingException)
243//       */
244//      public InternetAddress getAddress( final String address,final String personal ) {
245//              try {
246//                      return new InternetAddress( address,personal,charset );
247//              }
248//              catch( UnsupportedEncodingException ex ) {
249//                      String errMsg = "指定のエンコードが出来ません。"
250//                                                              + "address=" + address + " , charset=" + charset ;
251//                      throw new RuntimeException( errMsg,ex );
252//              }
253//      }
254//
255//      /**
256//       * Content-Transfer-Encoding を指定する場合の ビット数を返します。
257//       *
258//       * Windows系は、8bit / ISO-2022-JP 系は、7bit になります。
259//       *
260//       * @return      ビット数("8bit" 固定)
261//       */
262//      public String getBit() {
263//              return "8bit" ;
264//      }
265//}
266
267/**
268 * MailCharset インターフェースを実装した ISO-2022-JP エンコード時のサブクラスです。
269 *
270 * 『2.ISO-2022-JP に独自変換 + 7bit 送信』 の実装です。
271 *
272 * @version  4.0
273 * @author   Kazuhiko Hasegawa
274 * @since    JDK5.0,
275 */
276//class Mail_ISO2022JP_Charset implements MailCharset {
277//
278//      /**
279//       * プラットフォーム依存のデフォルトの Charset です。
280//       * プラットフォーム依存性を考慮する場合、エンコード指定で作成しておく事をお勧めします。
281//       *
282//       * @og.rev 5.5.2.6 (2012/05/25) findbugs対応
283//       */
284//      private static final Charset DEFAULT_CHARSET = Charset.defaultCharset() ;
285//
286//      /**
287//       * テキストをセットします。
288//       * Part#setText() の代わりにこちらを使うようにします。
289//       *
290//       * @param mimeMsg MimeMessage
291//       * @param text    String
292//       * @throws RuntimeException(MessagingException)
293//       */
294//      public void setTextContent( final MimeMessage mimeMsg, final String text ) {
295//              try {
296//                      // mimeMsg.setText(text, "ISO-2022-JP");
297//                      mimeMsg.setDataHandler(new DataHandler(new JISDataSource(text)));
298//              }
299//              catch( MessagingException ex ) {
300//                      String errMsg = "指定のテキストをセットできません。"
301//                                                              + "text=" + text ;
302//                      throw new RuntimeException( errMsg,ex );
303//              }
304//      }
305//
306//      /**
307//       * 日本語を含むヘッダ用テキストを生成します。
308//       * 変換結果は ASCII なので、これをそのまま setSubject や InternetAddress
309//       * のパラメタとして使用してください。
310//       *
311//       * @param text    String
312//       *
313//       * @return      日本語を含むヘッダ用テキスト
314//       * @throws RuntimeException(UnsupportedEncodingException)
315//       */
316//      public String encodeWord( final String text ) {
317//              try {
318//                      return "=?ISO-2022-JP?B?" +
319//                              new String(
320//                                      BASE64EncoderStream.encode(
321//                                              CharCodeConverter.sjisToJis(
322//                                                      UnicodeCorrecter.correctToCP932(text).getBytes("Windows-31J")
323//                                              )
324//                                      )
325//                              ,DEFAULT_CHARSET ) + "?=";              // 5.5.2.6 (2012/05/25) findbugs対応
326//              }
327//              catch( UnsupportedEncodingException ex ) {
328//                      String errMsg = "指定のエンコードが出来ません。"
329//                                                              + "text=" + text + " , charset=Windows-31J" ;
330//                      throw new RuntimeException( errMsg,ex );
331//              }
332//      }
333//
334//      /**
335//       * 日本語を含むアドレスを生成します。
336//       * personal に、日本語が含まれると想定しています。
337//       * サブクラスで、日本語処理を行う場合の方法は、それぞれ異なります。
338//       *
339//       * @param address    String
340//       * @param personal   String
341//       *
342//       * @return InternetAddress
343//       * @throws RuntimeException(UnsupportedEncodingException)
344//       */
345//      public InternetAddress getAddress( final String address,final String personal ) {
346//              try {
347//                      return new InternetAddress( address,encodeWord( personal ) );
348//              }
349//              catch( UnsupportedEncodingException ex ) {
350//                      String errMsg = "指定のエンコードが出来ません。"
351//                                                              + "address=" + address ;
352//                      throw new RuntimeException( errMsg,ex );
353//              }
354//      }
355//
356//      /**
357//       * Content-Transfer-Encoding を指定する場合の ビット数を返します。
358//       *
359//       * Windows系は、8bit / ISO-2022-JP 系は、7bit になります。
360//       *
361//       * @return      ビット数("7bit" 固定)
362//       */
363//      public String getBit() {
364//              return "7bit" ;
365//      }
366//}
367
368/**
369 * テキストの本文を送信するための DataSource です。
370 *
371 * Windows-31J でバイトコードに変換した後、独自エンコードにて、
372 * Shift-JIS ⇒ JIS 変換しています。
373 *
374 * @version  4.0
375 * @author   Kazuhiko Hasegawa
376 * @since    JDK5.0,
377 */
378//class JISDataSource implements DataSource {
379//      private final byte[] data;
380//
381//      public JISDataSource( final String str ) {
382//              try {
383//                      data = CharCodeConverter.sjisToJis(
384//                              UnicodeCorrecter.correctToCP932(str).getBytes("Windows-31J"));
385//
386//              } catch (UnsupportedEncodingException e) {
387//                      String errMsg = "Windows-31J でのエンコーディングが出来ません。" + str;
388//                      throw new RuntimeException( errMsg,e );
389//              }
390//      }
391//
392//      /**
393//       * データの MIME タイプを文字列の形で返します。
394//       * かならず有効なタイプを返すべきです。
395//       * DataSource の実装がデータタイプを 決定できない場合は、
396//       * getContentType は "application/octet-stream" を返すことを 提案します。
397//       *
398//       * @return      MIME タイプ
399//       */
400//      public String getContentType() {
401//              return "text/plain; charset=ISO-2022-JP";
402//      }
403//
404//      /**
405//       * データを表す InputStream を返します。
406//       * それができない場合は適切な例外をスローします。
407//       *
408//       * @return InputStream
409//       * @throws IOException
410//       */
411//      public InputStream getInputStream() throws IOException {
412//              return new ByteArrayInputStream( data );
413//      }
414//
415//      /**
416//       * データが書込可能なら OutputStream を返します。
417//       * それができない場合は適切な例外をスローします。
418//       *
419//       * ※ このクラスでは実装されていません。
420//       *
421//       * @return OutputStream
422//       * @throws IOException
423//       */
424//      public OutputStream getOutputStream() throws IOException {
425//              String errMsg = "このクラスでは実装されていません。";
426//      //      throw new UnsupportedOperationException( errMsg );
427//              throw new IOException( errMsg );
428//      }
429//
430//      /**
431//       * このオブジェクトの '名前' を返します。
432//       * この名前は下層のオブジェクトの性質によります。
433//       * ファイルをカプセル化する DataSource なら オブジェクトの
434//       * ファイル名を返すようにするかもしれません。
435//       *
436//       * @return      オブジェクトの名前
437//       */
438//      public String getName() {
439//              return "JISDataSource";
440//      }
441//}
442
443/**
444 * 文字関係のコンバータです。
445 * 一部コードのオリジナルは<a href="http://www-cms.phys.s.u-tokyo.ac.jp/~naoki/CIPINTRO/CCGI/kanjicod.html">Japanese Kanji Code</a>にて公開されているものです。
446 * また、http://www.sk-jp.com/cgi-bin/treebbs.cgi?kako=1&all=644&s=681
447 * にて YOSI さんが公開されたコードも参考にしています(というか実質同じです)。
448 *
449 * @version  4.0
450 * @author   Kazuhiko Hasegawa
451 * @since    JDK5.0,
452 */
453//class CharCodeConverter {
454//      private static final byte[] SJIS_KANA;  // 5.1.9.0 (2010/09/01) public ⇒ private へ変更
455//
456//      /**
457//       * インスタンスの生成を抑止します。
458//       */
459//      private CharCodeConverter() {
460//              // 何もありません。(PMD エラー回避)
461//      }
462//
463//      static {
464//              try {
465//                      // 全角への変換テーブル
466//                      SJIS_KANA = "。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゛゜".getBytes("Shift_JIS");
467//              } catch( UnsupportedEncodingException ex ) {
468//                      throw new RuntimeException( "CANT HAPPEN",ex );
469//              }
470//      }
471//
472//      /**
473//       * Shift_JIS エンコーディングスキームに基づくバイト列を
474//       * ISO-2022-JP エンコーディングスキームに変換します。
475//       * 「半角カナ」は対応する全角文字に変換します。
476//       *
477//       * @param sjisBytes byte[] エンコードするShift_JISバイト配列
478//       *
479//       * @return byte[] 変換後のISO-2022-JP(JIS)バイト配列(not null)
480//       */
481//      public static byte[] sjisToJis( final byte[] sjisBytes ) {
482//              ByteArrayOutputStream out = new ByteArrayOutputStream();
483//              boolean nonAscii = false;
484//              int len = sjisBytes.length;
485//              for(int i = 0; i < len; i++ ) {
486//                      if(sjisBytes[i] >= 0) {
487//                              if(nonAscii) {
488//                                      nonAscii = false;
489//                                      out.write(0x1b);
490//                                      out.write('(');
491//                                      out.write('B');
492//                              }
493//                              out.write(sjisBytes[i]);
494//                      } else {
495//                              if(!nonAscii) {
496//                                      nonAscii = true;
497//                                      out.write(0x1b);
498//                                      out.write('$');
499//                                      out.write('B');
500//                              }
501//                              int bt = sjisBytes[i] & 0xff;
502//                              if(bt >= 0xa1 && bt <= 0xdf) {
503//                                      // 半角カナは全角に変換
504//                                      int kanaIndex = (bt - 0xA1) * 2;
505//                                      sjisToJis(out, SJIS_KANA[kanaIndex], SJIS_KANA[kanaIndex + 1]);
506//                              } else {
507//                                      i++;
508//                                      if(i == len) { break; }
509//                                      sjisToJis(out, sjisBytes[i - 1], sjisBytes[i]);
510//                              }
511//                      }
512//              }
513//              if(nonAscii) {
514//                      out.write(0x1b);
515//                      out.write('(');
516//                      out.write('B');
517//              }
518//              return out.toByteArray();
519//      }
520//
521//      /**
522//       * 1文字の2バイト Shift_JIS コードを JIS コードに変換して書き出します。
523//       */
524//      private static void sjisToJis(
525//                              final ByteArrayOutputStream out, final byte bhi, final byte blo) {
526//              int hi = (bhi << 1) & 0xFF;
527//              int lo = blo & 0xFF;
528//              if(lo < 0x9F) {
529//                      if(hi < 0x3F) { hi += 0x1F; } else { hi -= 0x61; }
530//                      if(lo > 0x7E) { lo -= 0x20; } else { lo -= 0x1F; }
531//              } else {
532//                      if(hi < 0x3F) { hi += 0x20; } else { hi -= 0x60; }
533//                      lo -= 0x7E;
534//              }
535//              out.write(hi);
536//              out.write(lo);
537//      }
538//}
539
540/**
541 * unicode と、JIS との文字コードの関係で、変換しています。
542 *
543 * 0x301c(&#x301c;) を、0xff5e(&#xff5e;) へ、
544 * 0x2016(&#x2016;) を、0x2225(&#x2225;) へ、
545 * 0x2212(&#x2212;) を、0xff0d(&#xff0d;) へ、
546 * それぞれコード変換します。
547 *
548 * @version  4.0
549 * @author   Kazuhiko Hasegawa
550 * @since    JDK5.0,
551 */
552//class UnicodeCorrecter {
553//
554//      /**
555//       * インスタンスの生成を抑止します。
556//       */
557//      private UnicodeCorrecter() {
558//              // 何もありません。(PMD エラー回避)
559//      }
560//
561//      /**
562//       * Unicode 文字列の補正を行います。
563//       * "MS932" コンバータでエンコードしようとした際に
564//       * 正常に変換できない部分を補正します。
565//       */
566//      public static String correctToCP932( final String str ) {
567//              String rtn = "";
568//
569//              if( str != null ) {
570//                      int cnt = str.length();
571//                      StringBuilder buf = new StringBuilder( cnt );
572//                      for(int i=0; i<cnt; i++) {
573//                              buf.append(correctToCP932(str.charAt(i)));
574//                      }
575//                      rtn = buf.toString() ;
576//              }
577//              return rtn ;
578//      }
579//
580//      /**
581//       * キャラクタ単位に、Unicode 文字列の補正を行います。
582//       *
583//       * 風間殿のページを参考にしています。
584//       * @see <a href="http://www.ingrid.org/java/i18n/encoding/ja-conv.html" target="_blank">
585//       * http://www.ingrid.org/java/i18n/encoding/ja-conv.html</a>
586//       */
587//      public static char correctToCP932( final char ch ) {
588//              char rtn = ch;
589//
590//              switch (ch) {
591//      //              case 0x00a2:    return 0xffe0;          // ≪
592//      //              case 0x00a3:    return 0xffe1;          //  ̄
593//      //              case 0x00ac:    return 0xffe2;          // μ
594//      //              case 0x03bc:    return 0x00b5;          // ・
595//      //              case 0x2014:    return 0x2015;          // ,
596//      //              case 0x2016:    return 0x2225;          // ≫
597//      //              case 0x2212:    return 0xff0d;          // ―
598//      //              case 0x226a:    return 0x00ab;          // ‖
599//      //              case 0x226b:    return 0x00bb;          // ヴ
600//      //              case 0x301c:    return 0xff5e;          // −
601//      //              case 0x30f4:    return 0x3094;          // 〜
602//      //              case 0x30fb:    return 0x00b7;          // ¢
603//      //              case 0xff0c:    return 0x00b8;          // £
604//      //              case 0xffe3:    return 0x00af;          // ¬
605//
606//                      case 0x00a2:    rtn = 0xffe0; break;            // ¢ (1-81, CENT SIGN)
607//                      case 0x00a3:    rtn = 0xffe1; break;            // £ (1-82, POUND SIGN)
608//                      case 0x00a5:    rtn = 0x005c; break;            // \ (D/12, YEN SIGN)
609//                      case 0x00ac:    rtn = 0xffe2; break;            // ¬ (2-44, NOT SIGN)
610//                      case 0x2016:    rtn = 0x2225; break;            // ‖ (1-34, DOUBLE VERTICAL LINE)
611//                      case 0x203e:    rtn = 0x007e; break;            // ~ (F/14, OVERLINE)
612//                      case 0x2212:    rtn = 0xff0d; break;            // − (1-61, MINUS SIGN)
613//                      case 0x301c:    rtn = 0xff5e; break;            // 〜 (1-33, WAVE DASH)
614//
615//      //              case 0x301c:    return 0xff5e;
616//      //              case 0x2016:    return 0x2225;
617//      //              case 0x2212:    return 0xff0d;
618//                      default:                break;                  // 4.0.0 (2005/01/31)
619//              }
620//              return rtn;
621//      }
622//}