blob: c0cf2c6266fe8f31dd00a9ef06d37cafd7bd2b9c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.java.j2semodule;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.lang.model.element.TypeElement;
import javax.swing.SwingUtilities;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.JavaClassPathConstants;
import org.netbeans.api.java.platform.JavaPlatformManager;
import org.netbeans.api.java.source.*;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.modules.java.api.common.SourceRoots;
import org.netbeans.modules.java.api.common.ant.UpdateHelper;
import org.netbeans.modules.java.api.common.project.ProjectProperties;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
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.filesystems.FileUtil;
import org.openide.loaders.DataObject;
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;
/**
*
* @author Tomas Zezula
*/
final class MainClassUpdater extends FileChangeAdapter implements PropertyChangeListener {
private static final RequestProcessor RP = new RequestProcessor ("main-class-updater",1); //NOI18N
private static final Logger LOG = Logger.getLogger(MainClassUpdater.class.getName());
private static final int NEW = 0;
private static final int STARTED = 1;
private static final int FINISHED = 2;
private final Project project;
private final PropertyEvaluator eval;
private final UpdateHelper helper;
private final SourceRoots sourceRoots;
private final String mainClassPropName;
private final AtomicInteger state;
//@GuardedBy("this")
private FileObject currentFo;
//@GuardedBy("this")
private DataObject currentDo;
//@GuardedBy("this")
private FileChangeListener foListener;
//@GuardedBy("this")
private PropertyChangeListener doListener;
//@GuardedBy("this")
private long lc = 0;
/** Creates a new instance of MainClassUpdater */
MainClassUpdater(
@NonNull final Project project,
@NonNull final PropertyEvaluator eval,
@NonNull final UpdateHelper helper,
@NonNull final SourceRoots sourceRoots,
@NonNull final String mainClassPropName) {
assert project != null;
assert eval != null;
assert helper != null;
assert sourceRoots != null;
assert mainClassPropName != null;
this.project = project;
this.eval = eval;
this.helper = helper;
this.sourceRoots = sourceRoots;
this.mainClassPropName = mainClassPropName;
this.state = new AtomicInteger(NEW);
}
void start () {
RP.submit(() -> {
if (state.compareAndSet(NEW, STARTED)) {
eval.addPropertyChangeListener(MainClassUpdater.this);
addFileChangeListener ();
} else {
throw new IllegalStateException("Current State: " + state.get()); //NOI18N
}
});
}
public void stop() {
RP.submit(() -> {
if (state.compareAndSet(STARTED, FINISHED)) {
synchronized (MainClassUpdater.this) {
if (currentFo != null && foListener != null) {
currentFo.removeFileChangeListener(foListener);
}
if (currentDo != null && doListener != null) {
currentDo.removePropertyChangeListener(doListener);
}
}
} else {
throw new IllegalStateException("Current State: " + state.get()); //NOI18N
}
});
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (DataObject.PROP_PRIMARY_FILE.equals(evt.getPropertyName())) {
final FileObject newFile = (FileObject) evt.getNewValue();
final FileObject oldFile = (FileObject) evt.getOldValue();
handleMainClassMoved(oldFile, newFile);
} else if (this.mainClassPropName.equals(evt.getPropertyName())) {
//Go out of the ProjectManager.MUTEX, see #118722
RP.post(MainClassUpdater.this::addFileChangeListener);
}
}
@Override
public void fileRenamed (final FileRenameEvent evt) {
handleMainClassMoved(evt.getFile(), evt.getFile());
}
private void handleMainClassMoved(final FileObject oldFile, final FileObject newFile) {
if (!project.getProjectDirectory().isValid()) {
return;
}
final FileObject _current;
synchronized (this) {
_current = this.currentFo;
}
if (oldFile == _current) {
Runnable r = () -> {
try {
final String oldMainClass = ProjectManager.mutex().readAccess((Mutex.ExceptionAction<String>) () -> eval.getProperty(mainClassPropName));
Collection<ElementHandle<TypeElement>> main = SourceUtils.getMainClasses(newFile);
String newMainClass = null;
if (!main.isEmpty()) {
ElementHandle<TypeElement> 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(ProjectProperties.PROP_PROJECT_CONFIGURATION_CONFIG) == null) {
final String newMainClassFinal = newMainClass;
ProjectManager.mutex().writeAccess((Mutex.ExceptionAction<Void>) () -> {
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 | MutexException e) {
Exceptions.printStackTrace(e);
}
};
if (SwingUtilities.isEventDispatchThread()) {
r.run();
} else {
SwingUtilities.invokeLater(r);
}
}
}
private void addFileChangeListener () {
final long clc;
synchronized (MainClassUpdater.this) {
if (currentFo != null && foListener != null) {
currentFo.removeFileChangeListener(foListener);
foListener = null;
currentFo = null;
}
if (currentDo != null && doListener != null) {
currentDo.removePropertyChangeListener(doListener);
doListener = null;
currentDo = null;
}
clc = ++lc;
}
final String mainClassName = eval.getProperty(mainClassPropName);
if (mainClassName != null) {
FileObject[] roots = sourceRoots.getRoots();
if (roots.length>0) {
final ClassPath bootCp = Optional.ofNullable(ClassPath.getClassPath(roots[0], ClassPath.BOOT))
.orElse(JavaPlatformManager.getDefault().getDefaultPlatform().getBootstrapLibraries());
final ClassPath compileCp = Optional.ofNullable(ClassPath.getClassPath(roots[0], ClassPath.COMPILE))
.orElse(ClassPath.EMPTY);
final ClassPath systemModules = ClassPath.getClassPath(roots[0], JavaClassPathConstants.MODULE_BOOT_PATH);
final ClassPath modulePath = ClassPath.getClassPath(roots[0], JavaClassPathConstants.MODULE_COMPILE_PATH);
final ClassPath allUnnamed = ClassPath.getClassPath(roots[0], JavaClassPathConstants.MODULE_CLASS_PATH);
final ClassPath moduleSourcePath = ClassPath.getClassPath(roots[0], JavaClassPathConstants.MODULE_SOURCE_PATH);
final ClasspathInfo cpInfo = new ClasspathInfo.Builder(bootCp)
.setClassPath(compileCp)
.setModuleBootPath(systemModules)
.setModuleCompilePath(modulePath)
.setModuleClassPath(allUnnamed)
.setModuleSourcePath(moduleSourcePath)
.build();
final JavaSource js = JavaSource.create(cpInfo);
// execute immediately, or delay if cannot find main class
ScanUtils.postUserActionTask(js, (CompilationController c) -> {
c.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
TypeElement te = ScanUtils.checkElement(c, c.getElements().getTypeElement(mainClassName));
if (te != null) {
final ClassPath allSources = ClassPathSupport.createClassPath(roots);
final FileObject fo = SourceUtils.getFile(
te,
new ClasspathInfo.Builder(bootCp)
.setClassPath(compileCp)
.setSourcePath(allSources)
.setModuleBootPath(systemModules)
.setModuleCompilePath(modulePath)
.setModuleClassPath(allUnnamed)
.setModuleSourcePath(moduleSourcePath)
.build());
final boolean owned = allSources.contains(fo);
synchronized (MainClassUpdater.this) {
if (lc == clc && fo != null && owned) {
currentFo = fo;
foListener = WeakListeners.create(FileChangeListener.class, MainClassUpdater.this, currentFo);
currentFo.addFileChangeListener(foListener);
currentDo = DataObject.find(currentFo);
doListener = org.openide.util.WeakListeners.propertyChange(MainClassUpdater.this, currentDo);
currentDo.addPropertyChangeListener(doListener);
}
}
}
});
}
}
}
}