org.vaadin.netbeans.impl.VaadinSupportImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.vaadin.netbeans.impl.VaadinSupportImpl.java

Source

/*
 * Copyright 2000-2013 Vaadin Ltd.
 *
 * Licensed 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.vaadin.netbeans.impl;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.project.MavenProject;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.source.ClassIndexListener;
import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.api.java.source.ClasspathInfo.PathKind;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.RootsEvent;
import org.netbeans.api.java.source.Task;
import org.netbeans.api.java.source.TypesEvent;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.model.Utilities;
import org.netbeans.modules.maven.model.pom.POMComponent;
import org.netbeans.modules.maven.model.pom.POMExtensibilityElement;
import org.netbeans.modules.maven.model.pom.POMModel;
import org.netbeans.spi.java.classpath.ClassPathProvider;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.netbeans.spi.project.ui.ProjectOpenedHook;
import org.openide.execution.ExecutorTask;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.RequestProcessor;
import org.openide.util.TaskListener;
import org.vaadin.netbeans.VaadinSupport;
import org.vaadin.netbeans.maven.editor.completion.AddOnProvider;
import org.vaadin.netbeans.maven.project.VaadinVersions;
import org.vaadin.netbeans.model.ModelOperation;
import org.vaadin.netbeans.model.SourceDescendantsStrategy;
import org.vaadin.netbeans.utils.JavaUtils;
import org.vaadin.netbeans.utils.POMUtils;

/**
 * @author denis
 */
abstract class VaadinSupportImpl extends ProjectOpenedHook implements VaadinSupport {

    static RequestProcessor REQUEST_PROCESSOR = new RequestProcessor(VaadinSupportImpl.class);

    private static final String VAADIN_CHECK_CLASS = "com.vaadin.server.VaadinRequest";// NOI18N

    private static final String VAADIN_REQUEST_FQN = VAADIN_CHECK_CLASS.replace('.', '/');

    static final Logger LOG = Logger.getLogger(VaadinSupportImpl.class.getName());

    private static final int MAX_SOURCE_CLASSES = 500;

    private static final String VAADIN_CLIENT_COMPILER = "vaadin-client-compiler"; // NOI18N

    protected VaadinSupportImpl(Project project) {
        myProject = project;
        isEnabled = new AtomicReference<>();
        myResourcesListener = new ResourcesListener(this);
        myActions = new ConcurrentHashMap<>();
        myModel = new VaadinModelImpl(project);
        myDownloadListener = new ReloadProjectListener();
        myTypesCount = new AtomicInteger();
        myStrategy = new AtomicReference<SourceDescendantsStrategy>(new EmptyStrategy());
        myIndexListener = new ClassIndexListenerImpl();
    }

    @Override
    public boolean isReady() {
        return isInitialized;
    }

    @Override
    public List<String> getAddonWidgetsets() {
        if (isWeb()) {
            return null;
        } else {
            return getPomWidgetsets(getAddOnConfigFile());
        }
    }

    @Override
    public void setAddonWidgetsets(List<String> widgetsets) {
        if (!isWeb()) {
            String widgetsetValue = null;
            if (widgetsets != null) {
                StringBuilder builder = new StringBuilder();
                for (String widgetset : widgetsets) {
                    if (widgetset != null && widgetset.length() > 0) {
                        builder.append(widgetset);
                        builder.append(',');
                    }
                }
                if (builder.length() > 0) {
                    widgetsetValue = builder.substring(0, builder.length() - 1);
                } else {
                    widgetsetValue = builder.toString();
                }
            }
            final String widgetset = widgetsetValue;
            org.netbeans.modules.maven.model.ModelOperation<POMModel> operation = new org.netbeans.modules.maven.model.ModelOperation<POMModel>() {

                @Override
                public void performOperation(POMModel model) {
                    POMExtensibilityElement widgetsets = POMUtils.getWidgetsets(model);
                    if (widgetsets == null) {
                        if (widgetset != null) {
                            POMUtils.createWidgetset(model, widgetset);
                        }
                    } else {
                        if (widgetset == null) {
                            POMComponent parent = widgetsets.getParent();
                            parent.removeExtensibilityElement(widgetsets);
                        } else {
                            POMUtils.setWidgetset(widgetsets, widgetset);
                        }
                    }
                }
            };
            Utilities.performPOMModelOperations(getAddOnConfigFile(), Collections.singletonList(operation));
        }
    }

    @Override
    public FileObject getAddOnConfigFile() {
        if (isWeb()) {
            return null;
        }
        return getPom(getProject());
    }

    @Override
    public boolean isWeb() {
        return NbMavenProject.TYPE_WAR.equals(getPackagingType());
    }

    @Override
    public boolean isWidgetsetActionsAware() {
        return hasClientCompilerDependency(getProject());
    }

    @Override
    public void addAction(final Action action, ExecutorTask task) {
        TaskListener listener = new TaskListener() {

            @Override
            public void taskFinished(org.openide.util.Task task) {
                task.removeTaskListener(this);
                Set<ExecutorTask> set = myActions.get(action);
                if (set != null) {
                    set.remove(task);
                }
                myResourcesListener.removeOutputResources();
            }

        };
        task.addTaskListener(listener);
        if (task.isFinished()) {
            task.removeTaskListener(listener);
            myResourcesListener.removeOutputResources();
        }
        Set<ExecutorTask> set = myActions.get(action);
        if (set == null) {
            set = new CopyOnWriteArraySet<>();
            Set<ExecutorTask> result = myActions.putIfAbsent(action, set);
            if (result != null) {
                set = result;
            }
        }
        set.add(task);
    }

    @Override
    public Collection<ExecutorTask> getTasks(Action action) {
        Set<ExecutorTask> set = myActions.get(action);
        if (set == null) {
            return Collections.emptyList();
        }
        return set;
    }

    @Override
    public boolean isEnabled() {
        if (!isWeb() && !NbMavenProject.TYPE_JAR.equals(getPackagingType())) {
            return false;
        }
        // if projectOpened() still in progress or project is closing. Fix for
        // #12555,#18662
        if (myClasspathInfo == null) {
            return false;
        }
        Boolean enabled = isEnabled.get();
        if (enabled == null) {
            ClassPath classPath = myClasspathInfo.getClassPath(PathKind.COMPILE);
            // if projectOpened() still in progress. Fix for #12555
            ClasspathInfo info = getClassPathInfo();
            if (info == null) {
                return false;
            }
            if (classPath.findResource(VAADIN_REQUEST_FQN + ".class") != null) { // NOI18N
                return true;
            }
            classPath = info.getClassPath(PathKind.SOURCE);
            return classPath.findResource(VAADIN_REQUEST_FQN + ".java") != null;
        }
        return enabled;
    }

    @Override
    public void runModelOperation(final ModelOperation operation) throws IOException {
        Future<Void> future = invoke(new Task<CompilationController>() {

            @Override
            public void run(CompilationController controller) throws Exception {
                controller.toPhase(Phase.ELEMENTS_RESOLVED);
                operation.run(myModel);
            }
        });
        if (future != null) {
            try {
                future.get();
            } catch (InterruptedException | ExecutionException e) {
                LOG.log(Level.INFO, null, e);
            }
        }
    }

    @Override
    public String getVaadinVersion() {
        NbMavenProject mvnProject = getProject().getLookup().lookup(NbMavenProject.class);
        MavenProject mavenProject = mvnProject.getMavenProject();
        FileObject pom = FileUtil.toFileObject(FileUtil.normalizeFile(mavenProject.getFile()));
        String version = getVaadinVersion(pom);
        if (version == null) {
            version = getVaadinVersion(FileUtil.toFileObject(FileUtil.normalizeFile(mavenProject.getParentFile())));
        }
        if (version == null) {
            LOG.severe(
                    "Unknown project structure: cannot get version " + "neither from the project nor from parents");
        }
        return version;
    }

    @Override
    public ClasspathInfo getClassPathInfo() {
        return myClasspathInfo;
    }

    @Override
    public SourceDescendantsStrategy getDescendantStrategy() {
        return myStrategy.get();
    }

    @Override
    public Project getWidgetsetProject() {
        return getProject();
    }

    @Override
    protected void projectClosed() {
        removeFileSystemListener();
        NbMavenProject mvnProject = getProject().getLookup().lookup(NbMavenProject.class);
        mvnProject.removePropertyChangeListener(myDownloadListener);

        myClasspathInfo = null;
        myModel.cleanup(false);
    }

    @Override
    protected void projectOpened() {
        NbMavenProject mvnProject = getProject().getLookup().lookup(NbMavenProject.class);
        mvnProject.addPropertyChangeListener(myDownloadListener);

        ProjectUtils.getSources(myProject).addChangeListener(myIndexListener);
        initializeClassIndex(true);

        setTypesCount();

        // init versions and add-ons related data
        AddOnProvider.getInstance();
        VaadinVersions.getInstance();
    }

    protected ClasspathInfo createClasspathInfo() {
        return ClasspathInfo.create(getClassPath(getProject(), ClassPath.BOOT),
                getClassPath(getProject(), ClassPath.COMPILE), getClassPath(getProject(), ClassPath.SOURCE));
    }

    protected void updateModel(CompilationController controller, TypeElement element) {
    }

    protected void remove(CompilationController controller, ElementHandle<TypeElement> element) {
    }

    protected void initClassModel(CompilationController controller) throws InterruptedException {
    }

    protected void removeFileSystemListener() {
        doRemoveListener(getProject());
    }

    protected void initializeFileSystemListener() {
        doAddListener(getProject());
    }

    protected void doRemoveListener(Project project) {
        removeListener(JavaUtils.getJavaSourceGroups(project));
        removeListener(JavaUtils.getResourcesSourceGroups(project));
    }

    protected void doAddListener(Project project) {
        addListener(JavaUtils.getJavaSourceGroups(project));
        addListener(JavaUtils.getResourcesSourceGroups(project));
    }

    protected boolean sourceRootsAffected(RootsEvent event) {
        return sourceRootsAffected(event, null);
    }

    protected void initializeClassIndex(boolean reinitResourceListener) {
        try {
            ClasspathInfo info = createClasspathInfo();
            info.getClassIndex().addClassIndexListener(myIndexListener);
            synchronized (myIndexListener) {
                if (myClasspathInfo != null) {
                    myClasspathInfo.getClassIndex().removeClassIndexListener(myIndexListener);
                }
                myClasspathInfo = info;
            }
            invoke(new InitTask(reinitResourceListener));
        } catch (IOException e) {
            LOG.log(Level.INFO, null, e);
        }
    }

    protected boolean hasClientCompilerDependency(Project project) {
        for (Artifact artifact : getDependecies(project)) {
            if (POMUtils.VAADIN_GROUP_ID.equals(artifact.getGroupId())
                    && VAADIN_CLIENT_COMPILER.equals(artifact.getArtifactId())) {
                return true;
            }
        }
        return false;
    }

    void addListener(FileObject fileObject) {
        if (fileObject.isFolder()) {
            fileObject.addFileChangeListener(myResourcesListener);
            FileObject[] children = fileObject.getChildren();
            for (FileObject child : children) {
                addListener(child);
            }
        }
    }

    void removeListener(FileObject fileObject) {
        if (fileObject.isFolder()) {
            fileObject.removeFileChangeListener(myResourcesListener);
            FileObject[] children = fileObject.getChildren();
            for (FileObject child : children) {
                removeListener(child);
            }
        }
    }

    Future<Void> invoke(final Task<CompilationController> task, boolean waitScan) throws IOException {
        // myClasspathInfo could be null in the process of project closing
        ClasspathInfo info = getClassPathInfo();
        if (info == null) {
            return null;
        }
        JavaSource javaSource = JavaSource.create(info);
        if (javaSource == null) {
            return null;
        }
        if (waitScan) {
            return javaSource.runWhenScanFinished(task, true);
        } else {
            javaSource.runUserActionTask(task, true);
            return null;
        }
    }

    VaadinModelImpl getModel() {
        return myModel;
    }

    Project getProject() {
        return myProject;
    }

    private String getPackagingType() {
        NbMavenProject mvnProject = getProject().getLookup().lookup(NbMavenProject.class);
        return mvnProject.getPackagingType();
    }

    private void updateSubclassesStrategy() {
        myStrategy.set(new RecursiveStrategy());
        if (myTypesCount.get() > MAX_SOURCE_CLASSES) {
            myStrategy.set(new RecursiveStrategy());
        } else {
            myStrategy.set(new AllClassesStrategy());
        }
    }

    private boolean sourceRootsAffected(RootsEvent event, Project subject) {
        Iterable<? extends URL> roots = event.getRoots();
        for (URL url : roots) {
            try {
                Project project = FileOwnerQuery.getOwner(url.toURI());
                if (project != null) {
                    if (subject == null) {
                        return true;
                    } else if (project.equals(subject)) {
                        return true;
                    }
                }
            } catch (URISyntaxException e) {
                LOG.log(Level.INFO, null, e);
            }
        }
        return false;
    }

    private void addListener(SourceGroup[] sourceGroups) {
        for (SourceGroup sourceGroup : sourceGroups) {
            FileObject root = sourceGroup.getRootFolder();
            addListener(root);
        }
    }

    private void removeListener(SourceGroup[] sourceGroups) {
        for (SourceGroup sourceGroup : sourceGroups) {
            FileObject root = sourceGroup.getRootFolder();
            removeListener(root);
        }
    }

    private void setTypesCount() {
        try {
            invoke(new CountProjectClasses());
        } catch (IOException e) {
            LOG.log(Level.INFO, null, e);
        }
    }

    private Future<Void> invoke(final Task<CompilationController> task) throws IOException {
        return invoke(task, !isReady());
    }

    private String getVaadinVersion(FileObject pom) {
        final String[] version = new String[1];
        org.netbeans.modules.maven.model.ModelOperation<POMModel> operation = new org.netbeans.modules.maven.model.ModelOperation<POMModel>() {

            @Override
            public void performOperation(POMModel model) {
                version[0] = POMUtils.getVaadinVersion(model);
            }
        };
        Utilities.performPOMModelOperations(pom, Collections.singletonList(operation));
        return version[0];
    }

    protected static ClassPath getClassPath(Project project, String type) {
        ClassPathProvider provider = project.getLookup().lookup(ClassPathProvider.class);
        SourceGroup[] sourceGroups = JavaUtils.getJavaSourceGroups(project);
        List<ClassPath> classPaths = new ArrayList<>(sourceGroups.length);
        for (SourceGroup sourceGroup : sourceGroups) {
            FileObject rootFolder = sourceGroup.getRootFolder();
            ClassPath path = provider.findClassPath(rootFolder, type);
            classPaths.add(path);
        }
        return ClassPathSupport.createProxyClassPath(classPaths.toArray(new ClassPath[classPaths.size()]));
    }

    static FileObject getPom(Project project) {
        NbMavenProject mvnProject = project.getLookup().lookup(NbMavenProject.class);
        File file = mvnProject.getMavenProject().getFile();
        return FileUtil.toFileObject(FileUtil.normalizeFile(file));
    }

    static List<String> getPomWidgetsets(FileObject pom) {
        final String[] widgetset = new String[1];
        org.netbeans.modules.maven.model.ModelOperation<POMModel> operation = new org.netbeans.modules.maven.model.ModelOperation<POMModel>() {

            @Override
            public void performOperation(POMModel model) {
                POMExtensibilityElement widgetsets = POMUtils.getWidgetsets(model);
                if (widgetsets != null) {
                    widgetset[0] = widgetsets.getElementText().trim();
                }
            }
        };
        Utilities.performPOMModelOperations(pom, Collections.singletonList(operation));
        if (widgetset[0] == null) {
            return Collections.emptyList();
        } else {
            StringTokenizer tokenizer = new StringTokenizer(widgetset[0], ",");
            List<String> result = new LinkedList<>();
            while (tokenizer.hasMoreTokens()) {
                String nextWidgetset = tokenizer.nextToken().trim();
                if (nextWidgetset.length() > 0) {
                    result.add(nextWidgetset);
                }
            }
            return result;
        }
    }

    static Set<Artifact> getDependecies(Project project) {
        NbMavenProject nbMvnProject = project.getLookup().lookup(NbMavenProject.class);
        MavenProject mavenProject = nbMvnProject.getMavenProject();
        return mavenProject.getArtifacts();
    }

    private final class InitTask implements Task<CompilationController> {

        private final boolean initResourceListener;

        private InitTask(boolean reinitResourceListener) {
            initResourceListener = reinitResourceListener;
        }

        @Override
        public void run(CompilationController controller) throws Exception {
            controller.toPhase(Phase.ELEMENTS_RESOLVED);

            if (controller.getElements().getTypeElement(VAADIN_CHECK_CLASS) == null) {
                isEnabled.set(false);
                myModel.cleanup(false);
                REQUEST_PROCESSOR.execute(new Runnable() {

                    @Override
                    public void run() {
                        removeFileSystemListener();
                    }
                });
                return;
            }
            isEnabled.set(true);

            if (initResourceListener) {
                REQUEST_PROCESSOR.execute(new Runnable() {

                    @Override
                    public void run() {
                        removeFileSystemListener();
                        initializeFileSystemListener();
                    }
                });
            }

            myModel.cleanup(true);

            initClassModel(controller);
            isInitialized = true;
        }
    }

    private final class CountProjectClasses implements Runnable, Task<CompilationController> {

        @Override
        public void run(CompilationController controller) throws Exception {
            controller.toPhase(Phase.ELEMENTS_RESOLVED);

            Set<TypeElement> allTypes = AllClassesStrategy.findAllTypes(controller,
                    EnumSet.allOf(ElementKind.class));
            myTypesCount.set(allTypes.size());
            updateSubclassesStrategy();
        }

        @Override
        public void run() {
            REQUEST_PROCESSOR.post(this);
        }

    }

    private final class ReloadProjectListener implements PropertyChangeListener {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if (NbMavenProject.PROP_PROJECT.equals(evt.getPropertyName())) {
                initializeClassIndex(false);
            }
        }

    }

    private final class ClassIndexListenerImpl implements ClassIndexListener, ChangeListener {

        @Override
        public void typesAdded(final TypesEvent event) {
            try {
                invoke(new Task<CompilationController>() {

                    @Override
                    public void run(CompilationController controller) throws Exception {
                        controller.toPhase(Phase.ELEMENTS_RESOLVED);
                        updateModel(event, controller, true);
                    }
                });
            } catch (IOException e) {
                LOG.log(Level.INFO, null, e);
            }
        }

        @Override
        public void typesRemoved(final TypesEvent event) {
            try {
                invoke(new Task<CompilationController>() {

                    @Override
                    public void run(CompilationController controller) throws Exception {
                        controller.toPhase(Phase.ELEMENTS_RESOLVED);
                        Iterable<? extends ElementHandle<TypeElement>> types = event.getTypes();
                        int count = 0;
                        for (ElementHandle<TypeElement> elementHandle : types) {
                            count++;
                            remove(controller, elementHandle);
                        }
                        myTypesCount.addAndGet(-count);
                    }
                });
            } catch (IOException e) {
                LOG.log(Level.INFO, null, e);
            }
        }

        @Override
        public void typesChanged(final TypesEvent event) {
            try {
                invoke(new Task<CompilationController>() {

                    @Override
                    public void run(CompilationController controller) throws Exception {
                        controller.toPhase(Phase.ELEMENTS_RESOLVED);
                        updateModel(event, controller, false);
                    }
                });
            } catch (IOException e) {
                LOG.log(Level.INFO, null, e);
            }
        }

        @Override
        public void stateChanged(ChangeEvent e) {
            ClasspathInfo newInfo = createClasspathInfo();
            if (!newInfo.equals(getClassPathInfo())) {
                rootsChanged(true);
            }
        }

        @Override
        public void rootsAdded(RootsEvent event) {
            rootsChanged(sourceRootsAffected(event, getProject()));
        }

        @Override
        public void rootsRemoved(RootsEvent event) {
            rootsChanged(sourceRootsAffected(event, getProject()));
        }

        private void updateModel(TypesEvent event, CompilationController controller, boolean added) {
            Iterable<? extends ElementHandle<TypeElement>> types = event.getTypes();
            int count = 0;
            for (ElementHandle<TypeElement> elementHandle : types) {
                TypeElement element = elementHandle.resolve(controller);
                if (element != null) {
                    VaadinSupportImpl.this.updateModel(controller, element);
                    count++;
                }
            }
            if (added) {
                myTypesCount.addAndGet(count);
            }
            updateSubclassesStrategy();
        }

        private void rootsChanged(boolean reinitResourceListener) {
            initializeClassIndex(reinitResourceListener);
            if (reinitResourceListener) {
                setTypesCount();
            }
        }

    }

    private final Project myProject;

    private final VaadinModelImpl myModel;

    private volatile boolean isInitialized;

    private volatile ClasspathInfo myClasspathInfo;

    private final AtomicReference<Boolean> isEnabled;

    private final AtomicInteger myTypesCount;

    private final AtomicReference<SourceDescendantsStrategy> myStrategy;

    private final ResourcesListener myResourcesListener;

    private final ConcurrentHashMap<Action, Set<ExecutorTask>> myActions;

    private final PropertyChangeListener myDownloadListener;

    private final ClassIndexListenerImpl myIndexListener;

}