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     */
016    package org.opengion.fukurou.util;
017    
018    import java.io.File;
019    import java.io.StringWriter;
020    import java.net.MalformedURLException;
021    import java.net.URL;
022    import java.net.URLClassLoader;
023    import java.util.Arrays;
024    import java.util.Map;
025    import java.util.WeakHashMap;
026    
027    import javax.tools.JavaCompiler;
028    import javax.tools.StandardJavaFileManager;
029    import javax.tools.ToolProvider;
030    import javax.tools.JavaCompiler.CompilationTask;
031    
032    /**
033     * AutoCompile機è?ã€HotDeploy機è?を実ç¾ã™ã‚‹ãŸã‚ã®ã‚¯ãƒ©ã‚¹ãƒ­ãƒ¼ãƒ??ã§ã™ã?
034     *
035     * AutoCompile機è?ã¯ã€ã‚¯ãƒ©ã‚¹ã®å‹•的コンパイルを行ã„ã¾ã™ã?
036     * AutoCompile機è?を有効ã«ã™ã‚‹ã«ã¯ã€ã‚³ãƒ³ã‚¹ãƒˆãƒ©ã‚¯ã‚¿ã§ä¸Žãˆã‚‰ã‚Œã‚‹HybsLoaderConfigオブジェクトã§ã€?
037     * AutoCompileフラグをtrueã«ã—ã¦ãŠãå¿?¦ãŒã‚りã¾ã™ã?
038     *
039     * HotDeploy機è?ã¯ã€ã‚¯ãƒ©ã‚¹ã®å‹•的ロードを行ã„ã¾ã™ã?
040     * HotDeploy機è?を有効ã«ã™ã‚‹ã«ã¯ã€ã‚³ãƒ³ã‚¹ãƒˆãƒ©ã‚¯ã‚¿ã§ä¸Žãˆã‚‰ã‚Œã‚‹HybsLoaderConfigオブジェクトã§ã€?
041     * HotDeployフラグをtrueã«ã—ã¦ãŠãå¿?¦ãŒã‚りã¾ã™ã?
042     *
043     * (1)クラスã®å‹•的コンパイル
044     *  {@link #loadClass(String)}メソãƒ?ƒ‰ãŒå‘¼ã°ã‚ŒãŸå ´åˆã«ã€ã‚½ãƒ¼ã‚¹ãƒ?‚£ãƒ¬ã‚¯ãƒˆã‚ˆã‚Šã?対象ã¨ãªã‚‹ã‚½ãƒ¼ã‚¹ãƒ•ァイルã‚?
045     *  検索ã—ã?クラスã®ã‚³ãƒ³ãƒ‘イルを行ã„ã¾ã™ã?
046     *  コンパイルãŒè¡Œã‚れるæ¡ä»¶ã¯ã€ã?クラスファイルãŒå­˜åœ¨ã—ãªã??ã¾ãŸã?「クラスファイルã®ã‚¿ã‚¤ãƒ?‚¹ã‚¿ãƒ³ãƒ—ãŒã‚½ãƒ¼ã‚¹ãƒ•ァイルよりå¤ã??ã§ã™ã?
047     *
048     *  コンパイルを行ã†ã«ã¯ã€JDKã«å«ã¾ã‚Œã‚‹tools.jarãŒå­˜åœ¨ã—ã¦ã?‚‹å¿?¦ãŒã‚りã¾ã™ã?
049     *  tools.jarãŒè¦‹ã¤ã‹ã‚‰ãªã??åˆã?エラーã¨ãªã‚Šã¾ã™ã?
050     *
051     *  ã¾ãŸã?コンパイルã®ã‚¿ã‚¹ã‚¯ã®ã‚¯ãƒ©ã‚¹(オブジェクãƒ?ã¯ã€JVMã®ã‚·ã‚¹ãƒ?ƒ ã‚¯ãƒ©ã‚¹ãƒ­ãƒ¼ãƒ??上ã?クラスã«å­˜åœ¨ã—ã¦ã?¾ã™ã?
052     *  ã“ã?ãŸã‚ã€ã‚µãƒ¼ãƒ–レãƒ?ƒˆã‚³ãƒ³ãƒ?ƒŠã§ã€??常読ã¿è¾¼ã¾ã‚Œã‚‹WEB-INF/classes,WEB-INF/lib以下ã?クラスファイルもã?
053     *  ãã?ã¾ã¾ã§ã¯å‚ç?ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã€?
054     *  ã“れらã?クラスをå‚ç…§ã™ã‚‹å ´åˆã?ã€HybsLoaderConfigオブジェクトã«å¯¾ã—ã¦ã‚¯ãƒ©ã‚¹ãƒ‘スを設定ã—ã¦ãŠãå¿?¦ãŒã‚りã¾ã™ã?
055     *
056     * (2)クラスロー�
057     *  クラスã®å‹•的ロードã?ã€ã‚¯ãƒ©ã‚¹ãƒ­ãƒ¼ãƒ??ã®å…¥ã‚Œæ›¿ãˆã«ã‚ˆã£ã¦å®Ÿç¾ã—ã¦ã?¾ã™ã?
058     *  HotDeploy機è?を有効ã«ã—ãŸå ´åˆã?読ã¿è¾¼ã‚?‚¯ãƒ©ã‚¹å˜ä½ã«URLClassLoaderを生æˆã—ã¦ã?¾ã™ã?
059     *  クラスロードを行ã†éš›ã«ã€URLClassLoaderã‚’æ–°ã—ã生æ?ã™ã‚‹ã“ã¨ã§ã€ã‚¯ãƒ©ã‚¹ã®å†ãƒ­ãƒ¼ãƒ‰ã‚’行ã£ã¦ã?¾ã™ã?
060     *  ã¤ã¾ã‚Šã?HotDeployã«ã‚ˆã‚Šèª­ã¿è¾¼ã¾ã‚Œã‚‹ãれãžã‚Œã®ã‚¯ãƒ©ã‚¹ã¯ã€ãŠäº’ã„ã«ç‹¬ç«‹ã—ã?平行ãªä½ç½®ã«å­˜åœ¨ã™ã‚‹)関係ã«
061     *  ãªã‚Šã¾ã™ã?
062     *  ã“ã?ãŸã‚ã€ã‚ã‚‹HotDeployã«ã‚ˆã‚Šãƒ­ãƒ¼ãƒ‰ã•れãŸã‚¯ãƒ©ã‚¹Aã‹ã‚‰ã€åŒã˜ãHotDeployã«ã‚ˆã‚Šãƒ­ãƒ¼ãƒ‰ã•れãŸã‚¯ãƒ©ã‚¹Bを直接å‚ç?
063     *  ã™ã‚‹ã“ã¨ãŒã§ãã¾ã›ã‚“ã€?
064     *  ã“ã?å ´åˆã?ã€ã‚¯ãƒ©ã‚¹Bã®ã‚¤ãƒ³ã‚¿ãƒ¼ãƒ•ェースをé™çš?ªã‚¯ãƒ©ã‚¹ãƒ­ãƒ¼ãƒ??(クラスAã‹ã‚‰å‚ç?ã§ãã‚‹ä½ç½®)ã«é…ç½®ã™ã‚‹ã“ã¨ã§ã€ã‚¯ãƒ©ã‚¹B
065     *  ã®ã‚ªãƒ–ジェクトã«ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã?
066     *
067     * @og.rev 5.1.1.0 (2009/12/01) æ–°è¦ä½œæ?
068     * @og.group 業務ロジãƒ?‚¯
069     *
070     * @version 5.0
071     * @author Hiroki Nakamura
072     * @since JDK1.6,
073     */
074    public class HybsLoader {
075            private static final String CR = System.getProperty("line.separator");
076    
077            // HotDeploy機è?を使用ã—ãªã??åˆã?URLClossLoaderã®ã‚­ãƒ£ãƒ?‚·ãƒ¥ã‚­ãƒ¼
078            private static final String CONST_LOADER_KEY = "CONST_LOADER_KEY";
079    
080            private static final JavaCompiler COMPILER = ToolProvider.getSystemJavaCompiler();
081            private static final StandardJavaFileManager FILE_MANAGER = COMPILER.getStandardFileManager(null, null, null);
082    
083            private final Map<String, HybsURLClassLoader> loaderMap = new WeakHashMap<String, HybsURLClassLoader>();
084            private final Map<String, String> clsNameMap = new WeakHashMap<String, String>();
085            private final String srcDir;
086            private final String classDir;
087            private final boolean isHotDeploy;
088            private final boolean isAutoCompile;
089            private final String classPath;
090    
091            /**
092             * HybsLoaderOptionを使用ã—ã¦HybsLoaderオブジェクトを生æ?ã—ã¾ã™ã?
093             *
094             * @param option HybsLoaderを構築ã™ã‚‹ãŸã‚ã?設定情報
095             */
096            public HybsLoader( final HybsLoaderConfig option ) {
097                    srcDir = option.getSrcDir();
098                    classDir = option.getClassDir();
099                    isHotDeploy = option.isHotDeploy();
100                    isAutoCompile = option.isAutoCompile();
101                    classPath = option.getClassPath();
102            }
103    
104            /**
105             * æŒ?®šã•れãŸã‚¯ãƒ©ã‚¹åã?クラスをロードã—ã¾ã™ã?
106             * クラスåã«ã¤ã?¦ã¯ã€ã‚¯ãƒ©ã‚¹è‡ªèº«ã®åç§°ã®ã¿ã‚’指定ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã?
107             * (パッケージåã‚’å«ã‚ãŸå®Œå?ãªå½¢ã®ã‚¯ãƒ©ã‚¹åã‚’æŒ?®šã™ã‚‹ã“ã¨ã‚‚ã§ãã¾ã?
108             *
109             * @param clsNm クラスå?
110             *
111             * @return クラス
112             */
113            public Class<?> load( final String clsNm ) {
114                    String clsName = getQualifiedName( clsNm );
115                    if( isAutoCompile ) {
116                            compileClass( clsName );
117                    }
118                    Class<?> cls = loadClass( clsName );
119    
120                    return cls;
121            }
122    
123            /**
124             * æŒ?®šã•れãŸã‚¯ãƒ©ã‚¹åã?クラスをロードã—ã€ãƒ‡ãƒ•ォルトコンストラクターを使用ã—ã¦
125             * インスタンスを生æˆã—ã¾ã™ã?
126             *
127             * @og.rev 5.1.8.0 (2010/07/01) Exceptionã®ã‚¨ãƒ©ãƒ¼ãƒ¡ãƒ?‚»ãƒ¼ã‚¸ã®ä¿®æ­£(状態ã?出åŠ?
128             *
129             * @param clsName クラスå?Qualified Name)
130             *
131             * @return インスタンス
132             */
133            public Object newInstance( final String clsName ) {
134                    Class<?> cls = load( clsName );
135                    Object obj = null;
136                    try {
137                            obj = cls.newInstance();
138                    }
139                    catch( InstantiationException ex ) {
140    //                      throw new RuntimeException( "インスタンスã®ç”Ÿæ?ã«å¤±æ•—ã—ã¾ã—ãŸ" );
141                            String errMsg = "インスタンスã®ç”Ÿæ?ã«å¤±æ•—ã—ã¾ã—ãŸã€?"  + clsName + "]" ;
142                            throw new RuntimeException( errMsg , ex );
143                    }
144                    catch( IllegalAccessException ex ) {
145    //                      throw new RuntimeException( "ã‚¢ã‚¯ã‚»ã‚¹ãŒæ‹’å¦ã•れã¾ã—ãŸ" );
146                            String errMsg = "ã‚¢ã‚¯ã‚»ã‚¹ãŒæ‹’å¦ã•れã¾ã—ãŸã€?"  + clsName + "]" ;
147                            throw new RuntimeException( errMsg , ex );
148                    }
149                    return obj;
150            }
151    
152            /**
153             * クラスåより完å?クラスåを検索ã—ã¾ã™ã?
154             *
155             * @og.rev 5.2.1.0 (2010/10/01) クラスファイルãŒå­˜åœ¨ã™ã‚‹å ´åˆã?ã€ã‚¨ãƒ©ãƒ¼ã«ã—ãªã??
156             *
157             * @param clsNm クラスå?
158             *
159             * @return 完å?クラスå?
160             */
161            private String getQualifiedName( final String clsNm ) {
162                    String clsName = null;
163                    if( clsNm.indexOf( '.' ) >= 0 ) {
164                            clsName = clsNm;
165                    }
166                    else {
167                            synchronized( clsNameMap ) {
168                                    clsName = clsNameMap.get( clsNm );
169                                    if( clsName == null ) {
170                                            clsName = findFile( "", clsNm );
171                                    }
172                                    if( clsName == null ) {
173                                            clsName = findFileByCls( "", clsNm );
174                                    }
175                                    clsNameMap.put( clsNm, clsName );
176                            }
177                            if( clsName == null ) {
178                                    throw new RuntimeException( "ソースファイルãŒå­˜åœ¨ã—ã¾ã›ã‚“。ファイル=[" + clsNm + "]" );
179                            }
180                    }
181                    return clsName;
182            }
183    
184            /**
185             * クラスåã«å¯¾å¿œã™ã‚‹Javaファイルをå?帰çš?«æ¤œç´¢ã—ã¾ã™ã?
186             *
187             * @param path 既定パス
188             * @param nm クラスå?
189             *
190             * @return 完å?クラスå?
191             */
192            private String findFile( final String path, final String nm ) {
193                    String tmpSrcPath = srcDir + path;
194                    File[] files = (new File( tmpSrcPath )).listFiles();
195                    if( files != null && files.length > 0 ) {
196                            for( int i=0; i<files.length; i++ ) {
197                                    if ( files[i].isDirectory() ) {
198                                            String rtn = findFile( path + files[i].getName() + File.separator, nm );
199                                            if( rtn != null && rtn.length() > 0 ) {
200                                                    return rtn;
201                                            }
202                                    }
203                                    else if( ( nm + ".java" ).equals( files[i].getName() ) ) {
204                                            return path.replace( File.separatorChar, '.' ) + nm;
205                                    }
206                            }
207                    }
208                    return null;
209            }
210    
211            /**
212             * クラスåã«å¯¾å¿œã™ã‚‹Javaファイルをå?帰çš?«æ¤œç´¢ã—ã¾ã™ã?
213             *
214             * @og.rev 5.2.1.0 (2010/10/01) クラスファイルãŒå­˜åœ¨ã™ã‚‹å ´åˆã?ã€ã‚¨ãƒ©ãƒ¼ã«ã—ãªã??
215             *
216             * @param path 既定パス
217             * @param nm クラスå?
218             *
219             * @return 完å?クラスå?
220             */
221            private String findFileByCls( final String path, final String nm ) {
222                    String tmpSrcPath = classDir + path;
223                    File[] files = (new File( tmpSrcPath )).listFiles();
224                    if( files != null && files.length > 0 ) {
225                            for( int i=0; i<files.length; i++ ) {
226                                    if ( files[i].isDirectory() ) {
227                                            String rtn = findFile( path + files[i].getName() + File.separator, nm );
228                                            if( rtn != null && rtn.length() > 0 ) {
229                                                    return rtn;
230                                            }
231                                    }
232                                    else if( ( nm + ".class" ).equals( files[i].getName() ) ) {
233                                            return path.replace( File.separatorChar, '.' ) + nm;
234                                    }
235                            }
236                    }
237                    return null;
238            }
239    
240            /**
241             * クラスをコンパイルã—ã¾ã™ã?
242             *
243             * @og.rev 5.1.8.0 (2010/07/01) ソースファイルã®ã‚¨ãƒ³ã‚³ãƒ¼ãƒ‰ã?ã€UTF-8ã«ã™ã‚‹ã€?
244             * @og.rev 5.2.1.0 (2010/10/01) クラスファイルãŒå­˜åœ¨ã™ã‚‹å ´åˆã?ã€ã‚¨ãƒ©ãƒ¼ã«ã—ãªã??
245             *
246             * @param clsNm クラスå?
247             */
248            private void compileClass( final String clsNm ) {
249                    if( COMPILER == null ) {
250                            throw new RuntimeException( "コンパイラクラスãŒå®šç¾©ã•れã¦ã?¾ã›ã‚“。tools.jarãŒå­˜åœ¨ã—ãªã?¯èƒ½æ€§ãŒã‚りã¾ã? );
251                    }
252    
253                    String srcFqn = srcDir + clsNm.replace( ".", File.separator ) + ".java";
254                    File srcFile = new File( srcFqn );
255                    String classFqn =  classDir + clsNm.replace( ".", File.separator ) + ".class";
256                    File clsFile = new File ( classFqn );
257    
258                    // クラスファイルãŒå­˜åœ¨ã™ã‚‹å ´åˆã?ã€ã‚¨ãƒ©ãƒ¼ã«ã—ãªã??
259                    if( !srcFile.exists() ) {
260                            if( clsFile.exists() ) {
261                                    return;
262                            }
263                            throw new RuntimeException( "ソースファイルãŒå­˜åœ¨ã—ã¾ã›ã‚“。ファイル=[" + srcFqn + "]" );
264                    }
265    
266                    if( clsFile.exists() && srcFile.lastModified() <= clsFile.lastModified() ) {
267                            return;
268                    }
269    
270                    if( !clsFile.getParentFile().exists() ) {
271                            if( !clsFile.getParentFile().mkdirs() ) {
272                                    throw new RuntimeException( "ãƒ?‚£ãƒ¬ã‚¯ãƒˆãƒªãŒä½œæ?ã§ãã¾ã›ã‚“ã§ã—ãŸã€‚ファイル=[" + clsFile + "]" );
273                            }
274                    }
275    
276                    StringWriter sw = new StringWriter();
277                    File[] sourceFiles = { new File( srcFqn ) };
278                    // 5.1.8.0 (2010/07/01) ソースファイルã®ã‚¨ãƒ³ã‚³ãƒ¼ãƒ‰ã?ã€UTF-8ã«ã™ã‚‹ã€?
279    //              String[] cpOpts = new String[]{ "-d", classDir, "-classpath", classPath };
280                    String[] cpOpts = new String[]{ "-d", classDir, "-classpath", classPath, "-encoding", "UTF-8" };
281    
282                    CompilationTask task = COMPILER.getTask(sw, FILE_MANAGER, null, Arrays
283                                    .asList(cpOpts), null, FILE_MANAGER
284                                    .getJavaFileObjects(sourceFiles));
285    
286                    boolean isOk = false;
287                    // lockã—ã¦ãŠã‹ãªã?¨ã€java.lang.IllegalStateExceptionãŒç™ºç”Ÿã™ã‚‹ã“ã¨ãŒã‚ã‚?
288                    synchronized( this ) {
289                            isOk = task.call();
290                    }
291                    if( !isOk ) {
292                            throw new RuntimeException( "コンパイルã«å¤±æ•—ã—ã¾ã—ãŸã€? + CR + sw.toString() );
293                    }
294            }
295    
296            /**
297             * クラスをロードã—ã¾ã™ã?
298             *
299             * @param       clsNm クラスå?
300             *
301             * @return      ロードã—ãŸã‚¯ãƒ©ã‚¹ã‚ªãƒ–ジェクãƒ?
302             */
303            private Class<?> loadClass( final String clsNm ) {
304                    String key = isHotDeploy ? clsNm : CONST_LOADER_KEY;
305    
306                    String classFqn =  classDir + clsNm.replace( ".", File.separator ) + ".class";
307                    File clsFile = new File( classFqn );
308                    if( !clsFile.exists() ) {
309                            throw new RuntimeException( "クラスファイルãŒå­˜åœ¨ã—ã¾ã›ã‚“。ファイル=[" + classFqn + "]" );
310                    }
311                    long lastClassModifyTime = clsFile.lastModified();
312    
313                    HybsURLClassLoader loader = null;
314                    synchronized( loaderMap ) {
315                            loader = loaderMap.get( key );
316                            if( loader == null || lastClassModifyTime > loader.getCreationTime() ) {
317                                    try {
318                                            loader = new HybsURLClassLoader( new URL[] { ( new File( classDir ).toURI().toURL() ) }, this.getClass().getClassLoader() );
319                                    }
320                                    catch( MalformedURLException ex ) {
321                                            throw new RuntimeException( "クラスロードã?URL変æ›ã«å¤±æ•—ã—ã¾ã—ãŸã€‚ファイル=[" + classFqn + "]", ex );
322                                    }
323                                    loaderMap.put( key, loader );
324                            }
325                    }
326    
327                    Class<?> cls;
328                    try {
329                            cls = loader.loadClass( clsNm );
330                    }
331                    catch( ClassNotFoundException ex ) {
332    //                      throw new RuntimeException( "クラスãŒå­˜åœ¨ã—ã¾ã›ã‚“。ファイル=[" + classFqn + "]" );
333                            String errMsg = "クラスãŒå­˜åœ¨ã—ã¾ã›ã‚“。ファイル=[" + classFqn + "]"  ;
334                            throw new RuntimeException( errMsg , ex );
335                    }
336                    return cls;
337            }
338    
339            /**
340             * URLClassLoaderã‚’æ‹¡å¼µã—ã?クラスローãƒ??ã®ç”Ÿæ?時間を管ç?§ãるよã†ã«ã—ã¦ã?¾ã™ã?
341             */
342            private static class HybsURLClassLoader {
343                    final URLClassLoader loader;
344                    final long creationTime;
345    
346                    HybsURLClassLoader( final URL[] urls, final ClassLoader clsLd ) {
347                            loader = new URLClassLoader( urls, clsLd );
348                            creationTime = System.currentTimeMillis();
349                    }
350    
351                    HybsURLClassLoader( final URL[] urls ) {
352                            this( urls, null );
353                    }
354    
355                    Class<?> loadClass( final String clsName ) throws ClassNotFoundException {
356                            return loader.loadClass( clsName );
357                    }
358    
359                    long getCreationTime() {
360                            return creationTime;
361                    }
362            }
363    }