| /* |
| * 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); |
| } |
| } |
| } |
| }); |
| } |
| } |
| } |
| } |