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.listeners;
019    
020    import java.io.File;
021    import java.util.ArrayList;
022    import java.util.Collection;
023    
024    import org.apache.commons.jci.compilers.CompilationResult;
025    import org.apache.commons.jci.compilers.JavaCompiler;
026    import org.apache.commons.jci.compilers.JavaCompilerFactory;
027    import org.apache.commons.jci.monitor.FilesystemAlterationObserver;
028    import org.apache.commons.jci.problems.CompilationProblem;
029    import org.apache.commons.jci.readers.FileResourceReader;
030    import org.apache.commons.jci.readers.ResourceReader;
031    import org.apache.commons.jci.stores.MemoryResourceStore;
032    import org.apache.commons.jci.stores.ResourceStore;
033    import org.apache.commons.jci.stores.TransactionalResourceStore;
034    import org.apache.commons.jci.utils.ConversionUtils;
035    import org.apache.commons.logging.Log;
036    import org.apache.commons.logging.LogFactory;
037    
038    /**
039     * A CompilingListener is an improved version of the ReloadingListener.
040     * It even compiles the classes from source before doing the reloading.
041     * 
042     * @author tcurdt
043     */
044    public class CompilingListener extends ReloadingListener {
045    
046        private final Log log = LogFactory.getLog(CompilingListener.class);
047        
048        private final JavaCompiler compiler;
049        private final TransactionalResourceStore transactionalStore;
050        private ResourceReader reader;
051        private CompilationResult lastResult;
052        
053        public CompilingListener() {
054            this(new JavaCompilerFactory().createCompiler("eclipse"));
055        }
056    
057        public CompilingListener( final JavaCompiler pCompiler ) {
058            this(pCompiler, new TransactionalResourceStore(new MemoryResourceStore()));
059        }
060        
061        public CompilingListener( final JavaCompiler pCompiler, final TransactionalResourceStore pTransactionalStore ) {
062            super(pTransactionalStore);
063            compiler = pCompiler;
064            transactionalStore = pTransactionalStore;
065            lastResult = null;
066        }
067        
068        public JavaCompiler getCompiler() {
069            return compiler;
070        }
071        
072        public String getSourceFileExtension() {
073            return ".java";
074        }
075    
076        public ResourceReader getReader( final FilesystemAlterationObserver pObserver ) {
077            return new FileResourceReader(pObserver.getRootDirectory());
078        }
079    
080        public String getSourceNameFromFile( final FilesystemAlterationObserver pObserver, final File pFile ) {
081            return ConversionUtils.stripExtension(ConversionUtils.getResourceNameFromFileName(ConversionUtils.relative(pObserver.getRootDirectory(), pFile))) + getSourceFileExtension();
082        }
083        
084        @Override
085        public ResourceStore getStore() {
086            return transactionalStore;
087        }
088    
089        public synchronized CompilationResult getCompilationResult() {
090            return lastResult;
091        }
092        
093        @Override
094        public void onStart( final FilesystemAlterationObserver pObserver ) {
095            super.onStart(pObserver);
096    
097            reader = getReader(pObserver);
098    
099            transactionalStore.onStart();
100        }
101    
102        public String[] getResourcesToCompile( final FilesystemAlterationObserver pObserver ) {
103            final Collection<File> created = getCreatedFiles();
104            final Collection<File> changed = getChangedFiles();
105    
106            final Collection<String> resourceNames = new ArrayList<String>();
107            
108            for (File createdFile : created) {
109                if (createdFile.getName().endsWith(getSourceFileExtension())) {
110                    resourceNames.add(getSourceNameFromFile(pObserver, createdFile));
111                }
112            }
113            
114            for (File changedFile : changed) {
115                if (changedFile.getName().endsWith(getSourceFileExtension())) {
116                    resourceNames.add(getSourceNameFromFile(pObserver, changedFile));
117                }
118            }
119    
120            final String[] result = new String[resourceNames.size()];
121            resourceNames.toArray(result);
122            return result;
123        }
124        
125        @Override
126        public boolean isReloadRequired( final FilesystemAlterationObserver pObserver ) {
127            boolean reload = false;
128    
129            final Collection<File> created = getCreatedFiles();
130            final Collection<File> changed = getChangedFiles();
131            final Collection<File> deleted = getDeletedFiles();
132            
133            log.debug("created:" + created.size() + " changed:" + changed.size() + " deleted:" + deleted.size() + " resources");
134    
135            if (deleted.size() > 0) {
136                for (File deletedFile : deleted) {
137                    final String resourceName = ConversionUtils.getResourceNameFromFileName(ConversionUtils.relative(pObserver.getRootDirectory(), deletedFile));
138                    
139                    if (resourceName.endsWith(getSourceFileExtension())) {
140                        // if source resource got removed delete the corresponding class 
141                        transactionalStore.remove(ConversionUtils.stripExtension(resourceName) + ".class");
142                    } else {
143                        // ordinary resource to be removed
144                        transactionalStore.remove(resourceName);
145                    }
146                    
147                    // FIXME: does not remove nested classes
148                    
149                }
150                reload = true;
151            }
152                                    
153            final String[] resourcesToCompile = getResourcesToCompile(pObserver);
154    
155            if (resourcesToCompile.length > 0) {
156    
157                log.debug(resourcesToCompile.length + " classes to compile");
158                
159                final CompilationResult result = compiler.compile(resourcesToCompile, reader, transactionalStore);
160                
161                synchronized(this) {
162                    lastResult = result;
163                }
164                
165                final CompilationProblem[] errors = result.getErrors();
166                final CompilationProblem[] warnings = result.getWarnings();
167                
168                log.debug(errors.length + " errors, " + warnings.length + " warnings");
169            
170                if (errors.length > 0) {
171                    // FIXME: they need to be marked for re-compilation
172                    // and then added as compileables again
173                    for (int j = 0; j < resourcesToCompile.length; j++) {
174                        transactionalStore.remove(resourcesToCompile[j]);
175                    }
176                }
177                
178                reload = true;
179            }
180            
181            return reload;
182        }    
183    }