/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
* Microsystems, Inc. All Rights Reserved.
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
package org.netbeans.modules.scala.project;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import javax.lang.model.element.TypeElement;
import javax.swing.SwingUtilities;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.modules.csl.api.ElementKind;
import org.netbeans.modules.java.api.common.ant.UpdateHelper;
import org.netbeans.modules.parsing.api.ParserManager;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.api.UserTask;
import org.netbeans.modules.parsing.spi.ParseException;
import org.netbeans.spi.project.support.ant.AntProjectHelper;
import org.netbeans.spi.project.support.ant.EditableProperties;
import org.netbeans.spi.project.support.ant.PropertyEvaluator;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileRenameEvent;
import org.openide.util.Exceptions;
import org.openide.util.Mutex;
import org.openide.util.MutexException;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
import org.netbeans.api.language.util.ast.AstDfn;
import org.netbeans.modules.scala.core.ScalaParserResult;
import org.netbeans.modules.scala.core.ast.ScalaRootScope;
/**
*
* @author Tomas Zezula
*/
public class MainClassUpdater extends FileChangeAdapter implements PropertyChangeListener {
private static final int NEW = 0;
private static final int STARTED = NEW + 1;
private static final int FINISHED = STARTED + 1;
private static final RequestProcessor RP = new RequestProcessor("main-class-updater", 1); //NOI18N
private final Project project;
private final PropertyEvaluator eval;
private final UpdateHelper helper;
private final ClassPath sourcePath;
private final String mainClassPropName;
private final AtomicInteger state;
//@GuardedBy("this")
private FileObject current;
//@GuardedBy("this")
private FileChangeListener listener;
/**
* Creates a new instance of MainClassUpdater
*/
public MainClassUpdater(final Project project, final PropertyEvaluator eval,
final UpdateHelper helper, final ClassPath sourcePath, final String mainClassPropName) {
assert project != null;
assert eval != null;
assert helper != null;
assert sourcePath != null;
assert mainClassPropName != null;
this.project = project;
this.eval = eval;
this.helper = helper;
this.sourcePath = sourcePath;
this.mainClassPropName = mainClassPropName;
this.state = new AtomicInteger(NEW);
}
void start() {
RP.submit(new Runnable() {
@Override
public void run() {
if (state.compareAndSet(NEW, STARTED)) {
eval.addPropertyChangeListener(MainClassUpdater.this);
addFileChangeListener();
} else {
throw new IllegalStateException("Current State: " + state.get()); //NOI18N
}
}
});
}
void stop() {
RP.submit(new Runnable() {
@Override
public void run() {
if (state.compareAndSet(STARTED, FINISHED)) {
synchronized (MainClassUpdater.this) {
if (current != null && listener != null) {
current.removeFileChangeListener(listener);
}
}
} else {
throw new IllegalStateException("Current State: " + state.get()); //NOI18N
}
}
});
}
public void propertyChange(PropertyChangeEvent evt) {
if (this.mainClassPropName.equals(evt.getPropertyName())) {
//Go out of the ProjectManager.MUTEX, see #118722
RP.post(new Runnable() {
public void run() {
MainClassUpdater.this.addFileChangeListener();
}
});
}
}
@Override
public void fileRenamed(final FileRenameEvent evt) {
if (!project.getProjectDirectory().isValid()) {
return;
}
final FileObject _current;
synchronized (this) {
_current = this.current;
}
if (evt.getFile() == _current) {
Runnable r = new Runnable() {
public void run() {
try {
final String oldMainClass = ProjectManager.mutex().readAccess(new Mutex.ExceptionAction<String>() {
public String run() throws Exception {
return eval.getProperty(mainClassPropName);
}
});
Collection<ElementHandle<TypeElement>> main = SourceUtils.getMainClasses(_current);
String newMainClass = null;
if (!main.isEmpty()) {
ElementHandle mainHandle = main.iterator().next();
newMainClass = mainHandle.getQualifiedName();
}
if (newMainClass != null && !newMainClass.equals(oldMainClass) && helper.requestUpdate() && // XXX ##84806: ideally should update nbproject/configs/*.properties in this case:
eval.getProperty(J2SEConfigurationProvider.PROP_CONFIG) == null) {
final String newMainClassFinal = newMainClass;
ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction<Void>() {
public Void run() throws Exception {
EditableProperties props = helper.getProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH);
props.put(mainClassPropName, newMainClassFinal);
helper.putProperties(AntProjectHelper.PROJECT_PROPERTIES_PATH, props);
ProjectManager.getDefault().saveProject(project);
return null;
}
});
}
} catch (IOException e) {
Exceptions.printStackTrace(e);
} catch (MutexException e) {
Exceptions.printStackTrace(e);
}
}
};
if (SwingUtilities.isEventDispatchThread()) {
r.run();
} else {
SwingUtilities.invokeLater(r);
}
}
}
private void addFileChangeListener() {
synchronized (MainClassUpdater.this) {
if (current != null && listener != null) {
current.removeFileChangeListener(listener);
current = null;
listener = null;
}
}
final String mainClassName = MainClassUpdater.this.eval.getProperty(mainClassPropName);
if (mainClassName != null) {
try {
FileObject[] roots = sourcePath.getRoots();
if (roots.length > 0) {
/**
* @TODO ugly hacking to find mainClass's fo, this hacking
* requirs main class name is in the same name .scala file
*/
String[] paths = mainClassName.split("\\.");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < paths.length; i++) {
sb.append(paths[i]);
if (i < paths.length - 1) {
sb.append(File.separator);
}
}
sb.append(".scala");
String mainClassFoPath = sb.toString();
FileObject mainClassFo = null;
for (FileObject root : roots) {
mainClassFo = root.getFileObject(mainClassFoPath);
if (mainClassFo != null) {
break;
}
}
if (mainClassFo == null) {
return;
}
Source source = Source.create(mainClassFo);
final FileObject sourceFo = mainClassFo;
ParserManager.parse(Collections.singleton(source), new UserTask() {
@Override
public void run(ResultIterator resultIterator) throws Exception {
ScalaParserResult pResult = (ScalaParserResult) resultIterator.getParserResult();
if (pResult == null) {
return;
}
ScalaRootScope rootScope = pResult.rootScope();
if (rootScope == null) {
return;
}
scala.collection.Seq<AstDfn> objs = null;
scala.collection.Iterator<AstDfn> itr = rootScope.visibleDfns(ElementKind.PACKAGE).iterator();
while (itr.hasNext()) {
AstDfn packaging = itr.next();
objs = packaging.bindingScope().visibleDfns(ElementKind.CLASS);
break;
}
if (objs == null) {
objs = rootScope.visibleDfns(ElementKind.CLASS);
}
AstDfn mainClass = null;
itr = objs.iterator();
while (itr.hasNext()) {
AstDfn obj = itr.next();
if (obj.qualifiedName().equals(mainClassName)) {
mainClass = obj;
break;
}
}
if (mainClass != null) {
synchronized (MainClassUpdater.this) {
current = sourceFo;
listener = WeakListeners.create(FileChangeListener.class, MainClassUpdater.this, current);
if (current
!= null && sourcePath.contains(current)) {
current.addFileChangeListener(listener);
}
}
}
}
});
}
} catch (ParseException ex) {
}
}
}
}