001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.jci.compilers;
019    
020    import java.io.ByteArrayInputStream;
021    import java.io.FileNotFoundException;
022    import java.io.IOException;
023    import java.io.InputStreamReader;
024    import java.io.Reader;
025    import java.util.ArrayList;
026    import java.util.Collection;
027    import java.util.List;
028    
029    import org.apache.commons.jci.problems.CompilationProblem;
030    import org.apache.commons.jci.readers.ResourceReader;
031    import org.apache.commons.jci.stores.ResourceStore;
032    import org.apache.commons.jci.utils.ConversionUtils;
033    import org.apache.commons.logging.Log;
034    import org.apache.commons.logging.LogFactory;
035    import org.mozilla.javascript.CompilerEnvirons;
036    import org.mozilla.javascript.Context;
037    import org.mozilla.javascript.ErrorReporter;
038    import org.mozilla.javascript.EvaluatorException;
039    import org.mozilla.javascript.GeneratedClassLoader;
040    import org.mozilla.javascript.ImporterTopLevel;
041    import org.mozilla.javascript.JavaScriptException;
042    import org.mozilla.javascript.NativeArray;
043    import org.mozilla.javascript.Scriptable;
044    import org.mozilla.javascript.ScriptableObject;
045    import org.mozilla.javascript.optimizer.ClassCompiler;
046    
047    /**
048     * @author tcurdt
049     */
050    public final class RhinoJavaCompiler extends AbstractJavaCompiler {
051    
052        private final Log log = LogFactory.getLog(RhinoJavaCompiler.class);
053    
054        private final JavaCompilerSettings defaultSettings;
055        
056        
057        public RhinoJavaCompiler() {
058            defaultSettings = new RhinoJavaCompilerSettings();
059        }
060        
061        /**
062         * based on code from dev.helma.org
063         * http://dev.helma.org/source/file/helma/branches/rhinoloader/src/org/helma/javascript/RhinoLoader.java/?revision=95
064         */
065        private final class RhinoCompilingClassLoader extends ClassLoader {
066    
067            private final ScriptableObject scope;
068            private final ResourceReader reader;
069            private final ResourceStore store;
070    
071            private final Collection<CompilationProblem> problems = new ArrayList<CompilationProblem>();
072            
073            private final class ProblemCollector implements ErrorReporter {
074    
075                public void error(String pMessage, String pFileName, int pLine, String pScript, int pColumn) {
076    
077                    final CompilationProblem problem = new RhinoCompilationProblem(pMessage, pFileName, pLine, pScript, pColumn, true); 
078    
079                    if (problemHandler != null) {
080                        problemHandler.handle(problem);
081                    }
082    
083                    problems.add(problem); 
084                }
085    
086                public void warning(String pMessage, String pFileName, int pLine, String pScript, int pColumn) {
087    
088                    final CompilationProblem problem = new RhinoCompilationProblem(pMessage, pFileName, pLine, pScript, pColumn, false); 
089    
090                    if (problemHandler != null) {
091                        problemHandler.handle(problem);
092                    }
093    
094                    problems.add(problem); 
095                }
096    
097                public EvaluatorException runtimeError(String pMessage, String pFileName, int pLine, String pScript, int pColumn) {
098                    return new EvaluatorException(pMessage, pFileName, pLine, pScript, pColumn);
099                }
100            }
101    
102            public RhinoCompilingClassLoader( final ResourceReader pReader, final ResourceStore pStore, final ClassLoader pClassLoader) {
103                super(pClassLoader);
104    
105                reader = pReader;
106                store = pStore;
107    
108                final Context context = Context.enter();
109                scope = new ImporterTopLevel(context);
110                Context.exit();
111            }
112    
113            public Collection<CompilationProblem> getProblems() {
114                return problems;
115            }
116    
117            @Override
118            protected Class<?> findClass( final String pName ) throws ClassNotFoundException {
119                final Context context = Context.enter();
120                context.setErrorReporter(new ProblemCollector());
121    
122                try {
123                    return compileClass(context, pName);
124                } catch( EvaluatorException e ) {
125                    throw new ClassNotFoundException(e.getMessage(), e);
126                } catch (IOException e) {
127                    throw new ClassNotFoundException(e.getMessage(), e);
128                } finally {
129                    Context.exit();
130                }
131            }
132    
133    
134            private Class<?> compileClass( final Context pContext, final String pClassName) throws IOException, ClassNotFoundException {
135    
136                Class<?> superclass = null;
137    
138                final String pSourceName = pClassName.replace('.', '/') + ".js";
139    
140                final Scriptable target = evaluate(pContext, pSourceName);
141    
142                final Object baseClassName = ScriptableObject.getProperty(target, "__extends__");
143    
144                if (baseClassName instanceof String) {
145                    superclass = Class.forName((String) baseClassName);
146                }
147    
148                final List<Class<?>> interfaceClasses = new ArrayList<Class<?>>();
149    
150                final Object interfaceNames = ScriptableObject.getProperty(target, "__implements__");
151    
152                if (interfaceNames instanceof NativeArray) {
153    
154                    final NativeArray interfaceNameArray = (NativeArray) interfaceNames;
155    
156                    for (int i=0; i<interfaceNameArray.getLength(); i++) {
157    
158                        final Object obj = interfaceNameArray.get(i, interfaceNameArray);
159    
160                        if (obj instanceof String) {
161                            interfaceClasses.add(Class.forName((String) obj));
162                        }
163                    }
164    
165                } else if (interfaceNames instanceof String) {
166    
167                    interfaceClasses.add(Class.forName((String) interfaceNames));
168    
169                }
170    
171                final Class<?>[] interfaces;
172    
173                if (!interfaceClasses.isEmpty()) {
174                    interfaces = new Class[interfaceClasses.size()];
175                    interfaceClasses.toArray(interfaces);
176                } else {
177                    // FIXME: hm ...really no empty array good enough?
178                    interfaces = null;
179                }
180    
181                return compileClass(pContext, pSourceName, pClassName, superclass, interfaces);
182    
183            }
184    
185    
186            private Class<?> compileClass( final Context pContext, final String pSourceName, final String pClassName, final Class<?> pSuperClass, final Class<?>[] pInterfaces) {
187    
188                final CompilerEnvirons environments = new CompilerEnvirons();
189                environments.initFromContext(pContext);
190                final ClassCompiler compiler = new ClassCompiler(environments);
191    
192                if (pSuperClass != null) {
193                    compiler.setTargetExtends(pSuperClass);
194                }
195    
196                if (pInterfaces != null) {
197                    compiler.setTargetImplements(pInterfaces);
198                }
199    
200                final byte[] sourceBytes = reader.getBytes(pSourceName);
201    
202                final Object[] classes = compiler.compileToClassFiles(new String(sourceBytes), getName(pSourceName), 1, pClassName);
203    
204                final GeneratedClassLoader loader = pContext.createClassLoader(pContext.getApplicationClassLoader());
205    
206                Class<?> clazz = null;
207    
208                for (int i = 0; i < classes.length; i += 2) {
209    
210                    final String clazzName = (String) classes[i];
211                    final byte[] clazzBytes = (byte[]) classes[i+1];
212    
213                    store.write(clazzName.replace('.', '/') + ".class", clazzBytes);
214    
215                    Class<?> c = loader.defineClass(clazzName, clazzBytes);
216                    loader.linkClass(c);
217    
218                    if (i == 0) {
219                        clazz = c;
220                    }
221    
222                }
223    
224                return clazz;
225            }
226    
227            private String getName(String s) {
228                final int i = s.lastIndexOf('/');
229                if (i < 0) {
230                    return s;
231                }
232    
233                return s.substring(i + 1);
234            }
235    
236            private Scriptable evaluate( final Context pContext, final String pSourceName) throws JavaScriptException, IOException {
237    
238                if (!reader.isAvailable(pSourceName)) {
239                    throw new FileNotFoundException("File " + pSourceName + " not found");
240                }
241    
242                final Scriptable target = pContext.newObject(scope);
243    
244                final byte[] sourceBytes = reader.getBytes(pSourceName);
245    
246                final Reader reader = new InputStreamReader(new ByteArrayInputStream(sourceBytes));
247    
248                pContext.evaluateReader(target, reader, getName(pSourceName), 1, null);
249    
250                return target;
251            }
252    
253        }
254        
255        
256        public CompilationResult compile( final String[] pResourcePaths, final ResourceReader pReader, final ResourceStore pStore, final ClassLoader pClassLoader, final JavaCompilerSettings pSettings ) {
257    
258            final RhinoCompilingClassLoader cl = new RhinoCompilingClassLoader(pReader, pStore, pClassLoader);
259    
260            for (int i = 0; i < pResourcePaths.length; i++) {
261                log.debug("compiling " + pResourcePaths[i]);
262                
263                final String clazzName = ConversionUtils.convertResourceToClassName(pResourcePaths[i]);
264                try {
265                    cl.loadClass(clazzName);
266                } catch (ClassNotFoundException e) {
267                }
268            }
269    
270            final Collection<CompilationProblem> problems = cl.getProblems();
271            final CompilationProblem[] result = new CompilationProblem[problems.size()];
272            problems.toArray(result);
273            return new CompilationResult(result);
274        }
275    
276    
277        public JavaCompilerSettings createDefaultSettings() {
278            return defaultSettings;
279        }
280    
281    }