Implements Important files for python
authorJulien Enselme <jenselme@netbeans.org>
Thu, 14 Jan 2016 18:37:20 +0100
changeset 18361049ffcf2a3aa
parent 18360 56e496c94d5d
child 18362 a444ba39f969
Implements Important files for python
python.project2/src/org/netbeans/modules/python/project2/ImportantFilesImplementation.java
python.project2/src/org/netbeans/modules/python/project2/ImportantFilesNode.java
python.project2/src/org/netbeans/modules/python/project2/ImportantFilesSupport.java
python.project2/src/org/netbeans/modules/python/project2/PythonImportantFilesNodeFactory.java
python.project2/src/org/netbeans/modules/python/project2/ui/ImportantFiles.java
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/python.project2/src/org/netbeans/modules/python/project2/ImportantFilesImplementation.java	Thu Jan 14 18:37:20 2016 +0100
     1.3 @@ -0,0 +1,176 @@
     1.4 +/*
     1.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     1.6 + *
     1.7 + * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
     1.8 + *
     1.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    1.10 + * Other names may be trademarks of their respective owners.
    1.11 + *
    1.12 + * The contents of this file are subject to the terms of either the GNU
    1.13 + * General Public License Version 2 only ("GPL") or the Common
    1.14 + * Development and Distribution License("CDDL") (collectively, the
    1.15 + * "License"). You may not use this file except in compliance with the
    1.16 + * License. You can obtain a copy of the License at
    1.17 + * http://www.netbeans.org/cddl-gplv2.html
    1.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    1.19 + * specific language governing permissions and limitations under the
    1.20 + * License.  When distributing the software, include this License Header
    1.21 + * Notice in each file and include the License file at
    1.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    1.23 + * particular file as subject to the "Classpath" exception as provided
    1.24 + * by Oracle in the GPL Version 2 section of the License file that
    1.25 + * accompanied this code. If applicable, add the following below the
    1.26 + * License Header, with the fields enclosed by brackets [] replaced by
    1.27 + * your own identifying information:
    1.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    1.29 + *
    1.30 + * If you wish your version of this file to be governed by only the CDDL
    1.31 + * or only the GPL Version 2, indicate your decision by adding
    1.32 + * "[Contributor] elects to include this software in this distribution
    1.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    1.34 + * single choice of license, a recipient has the option to distribute
    1.35 + * your version of this file under either the CDDL, the GPL Version 2 or
    1.36 + * to extend the choice of license to its licensees as provided above.
    1.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    1.38 + * Version 2 license, then the option applies only if the new code is
    1.39 + * made subject to such option by the copyright holder.
    1.40 + *
    1.41 + * Contributor(s):
    1.42 + *
    1.43 + * Portions Copyrighted 2016 Sun Microsystems, Inc.
    1.44 + */
    1.45 +package org.netbeans.modules.python.project2;
    1.46 +
    1.47 +import java.util.Collection;
    1.48 +import java.util.Objects;
    1.49 +import javax.swing.event.ChangeListener;
    1.50 +import org.netbeans.api.annotations.common.CheckForNull;
    1.51 +import org.netbeans.api.annotations.common.NullAllowed;
    1.52 +import org.openide.filesystems.FileObject;
    1.53 +import org.openide.util.Parameters;
    1.54 +
    1.55 +/**
    1.56 + *
    1.57 + * @author jenselme
    1.58 + */
    1.59 +/**
    1.60 + * Information about important files.
    1.61 + * <p>
    1.62 + * Implementations are expected to be found in project's lookup.
    1.63 + * @since 1.73
    1.64 + * @see org.netbeans.modules.web.common.spi.ImportantFilesImplementation;
    1.65 + */
    1.66 +public interface ImportantFilesImplementation {
    1.67 +
    1.68 +    /**
    1.69 +     * Gets information about all important files.
    1.70 +     * @return information about all important files; can be empty but never {@code null}
    1.71 +     */
    1.72 +    Collection<FileInfo> getFiles();
    1.73 +
    1.74 +    /**
    1.75 +     * Adds listener to be notified when important files change.
    1.76 +     * @param listener listener to be notified when important files change
    1.77 +     */
    1.78 +    void addChangeListener(ChangeListener listener);
    1.79 +
    1.80 +    /**
    1.81 +     * Removes listener.
    1.82 +     * @param listener listener
    1.83 +     */
    1.84 +    void removeChangeListener(ChangeListener listener);
    1.85 +
    1.86 +    //~ Inner classes
    1.87 +
    1.88 +    /**
    1.89 +     * Information about important file.
    1.90 +     */
    1.91 +    final class FileInfo {
    1.92 +
    1.93 +        private final FileObject file;
    1.94 +        private final String displayName;
    1.95 +        private final String description;
    1.96 +
    1.97 +
    1.98 +        /**
    1.99 +         * Creates information for the given file.
   1.100 +         * @param file file, cannot be a directory
   1.101 +         */
   1.102 +        public FileInfo(FileObject file) {
   1.103 +            this(file, null, null);
   1.104 +        }
   1.105 +
   1.106 +        /**
   1.107 +         * Creates information for the given file with custom display name
   1.108 +         * and/or custom description.
   1.109 +         * @param file file, cannot be a directory
   1.110 +         * @param displayName custom display name, can be {@code null}
   1.111 +         * @param description custom description, can be {@code null}
   1.112 +         */
   1.113 +        public FileInfo(FileObject file, @NullAllowed String displayName, @NullAllowed String description) {
   1.114 +            Parameters.notNull("file", file); // NOI18N
   1.115 +            if (file.isFolder()) {
   1.116 +                throw new IllegalArgumentException("File cannot be a directory");
   1.117 +            }
   1.118 +            this.file = file;
   1.119 +            this.displayName = displayName;
   1.120 +            this.description = description;
   1.121 +        }
   1.122 +
   1.123 +        /**
   1.124 +         * Gets file.
   1.125 +         * @return file
   1.126 +         */
   1.127 +        public FileObject getFile() {
   1.128 +            return file;
   1.129 +        }
   1.130 +
   1.131 +        /**
   1.132 +         * Gets display name, can be {@code null}.
   1.133 +         * @return display name, can be {@code null}
   1.134 +         */
   1.135 +        @CheckForNull
   1.136 +        public String getDisplayName() {
   1.137 +            return displayName;
   1.138 +        }
   1.139 +
   1.140 +        /**
   1.141 +         * Gets description, can be {@code null}.
   1.142 +         * @return description, can be {@code null}
   1.143 +         */
   1.144 +        @CheckForNull
   1.145 +        public String getDescription() {
   1.146 +            return description;
   1.147 +        }
   1.148 +
   1.149 +        @Override
   1.150 +        public String toString() {
   1.151 +            return "FileInfo{" + "file=" + file + ", displayName=" + displayName + ", description=" + description + '}'; // NOI18N
   1.152 +        }
   1.153 +
   1.154 +        @Override
   1.155 +        public int hashCode() {
   1.156 +            int hash = 3;
   1.157 +            hash = 71 * hash + Objects.hashCode(this.file);
   1.158 +            return hash;
   1.159 +        }
   1.160 +
   1.161 +        @Override
   1.162 +        public boolean equals(Object obj) {
   1.163 +            if (obj == null) {
   1.164 +                return false;
   1.165 +            }
   1.166 +            if (getClass() != obj.getClass()) {
   1.167 +                return false;
   1.168 +            }
   1.169 +            final FileInfo other = (FileInfo) obj;
   1.170 +            if (!Objects.equals(this.file, other.file)) {
   1.171 +                return false;
   1.172 +            }
   1.173 +            return true;
   1.174 +        }
   1.175 +
   1.176 +    }
   1.177 +
   1.178 +}
   1.179 +
     2.1 --- a/python.project2/src/org/netbeans/modules/python/project2/ImportantFilesNode.java	Sun Jan 10 15:02:08 2016 +0100
     2.2 +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.3 @@ -1,87 +0,0 @@
     2.4 -/*
     2.5 - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     2.6 - *
     2.7 - * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
     2.8 - *
     2.9 - * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    2.10 - * Other names may be trademarks of their respective owners.
    2.11 - *
    2.12 - * The contents of this file are subject to the terms of either the GNU
    2.13 - * General Public License Version 2 only ("GPL") or the Common
    2.14 - * Development and Distribution License("CDDL") (collectively, the
    2.15 - * "License"). You may not use this file except in compliance with the
    2.16 - * License. You can obtain a copy of the License at
    2.17 - * http://www.netbeans.org/cddl-gplv2.html
    2.18 - * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    2.19 - * specific language governing permissions and limitations under the
    2.20 - * License.  When distributing the software, include this License Header
    2.21 - * Notice in each file and include the License file at
    2.22 - * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    2.23 - * particular file as subject to the "Classpath" exception as provided
    2.24 - * by Oracle in the GPL Version 2 section of the License file that
    2.25 - * accompanied this code. If applicable, add the following below the
    2.26 - * License Header, with the fields enclosed by brackets [] replaced by
    2.27 - * your own identifying information:
    2.28 - * "Portions Copyrighted [year] [name of copyright owner]"
    2.29 - *
    2.30 - * If you wish your version of this file to be governed by only the CDDL
    2.31 - * or only the GPL Version 2, indicate your decision by adding
    2.32 - * "[Contributor] elects to include this software in this distribution
    2.33 - * under the [CDDL or GPL Version 2] license." If you do not indicate a
    2.34 - * single choice of license, a recipient has the option to distribute
    2.35 - * your version of this file under either the CDDL, the GPL Version 2 or
    2.36 - * to extend the choice of license to its licensees as provided above.
    2.37 - * However, if you add GPL Version 2 code and therefore, elected the GPL
    2.38 - * Version 2 license, then the option applies only if the new code is
    2.39 - * made subject to such option by the copyright holder.
    2.40 - *
    2.41 - * Contributor(s):
    2.42 - *
    2.43 - * Portions Copyrighted 2016 Sun Microsystems, Inc.
    2.44 - */
    2.45 -package org.netbeans.modules.python.project2;
    2.46 -
    2.47 -import java.awt.Image;
    2.48 -import org.netbeans.api.annotations.common.StaticResource;
    2.49 -import org.netbeans.api.project.Project;
    2.50 -import org.openide.filesystems.FileUtil;
    2.51 -import org.openide.loaders.DataFolder;
    2.52 -import org.openide.loaders.DataObject;
    2.53 -import org.openide.loaders.DataObjectNotFoundException;
    2.54 -import org.openide.nodes.FilterNode;
    2.55 -import org.openide.util.ImageUtilities;
    2.56 -
    2.57 -/**
    2.58 - *
    2.59 - * @author jenselme
    2.60 - */
    2.61 -class ImportantFilesNode extends FilterNode {
    2.62 -    @StaticResource
    2.63 -    private static final String IMAGE = "org/netbeans/modules/"
    2.64 -            + "python/project2/resources/brokenProjectBadge.gif";
    2.65 -
    2.66 -    public ImportantFilesNode(Project proj) throws DataObjectNotFoundException {
    2.67 -        super(DataObject.find(proj.getProjectDirectory()).getNodeDelegate(), new ImportantFilesChildren());
    2.68 -        proj.getProjectDirectory().getChildren(false);
    2.69 -    }
    2.70 -
    2.71 -    @Override
    2.72 -    public String getDisplayName() {
    2.73 -        return "Important Files";
    2.74 -    }
    2.75 -
    2.76 -    @Override
    2.77 -    public Image getIcon(int type) {
    2.78 -        DataFolder root = DataFolder.findFolder(FileUtil.getConfigRoot());
    2.79 -        Image original = root.getNodeDelegate().getIcon(type);
    2.80 -        return ImageUtilities.mergeImages(original,
    2.81 -                ImageUtilities.loadImage(IMAGE), 7, 7);
    2.82 -    }
    2.83 -    @Override
    2.84 -    public Image getOpenedIcon(int type) {
    2.85 -        DataFolder root = DataFolder.findFolder(FileUtil.getConfigRoot());
    2.86 -        Image original = root.getNodeDelegate().getIcon(type);
    2.87 -        return ImageUtilities.mergeImages(original,
    2.88 -                ImageUtilities.loadImage(IMAGE), 7, 7);
    2.89 -    }
    2.90 -}
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/python.project2/src/org/netbeans/modules/python/project2/ImportantFilesSupport.java	Thu Jan 14 18:37:20 2016 +0100
     3.3 @@ -0,0 +1,193 @@
     3.4 +/*
     3.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3.6 + *
     3.7 + * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
     3.8 + *
     3.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    3.10 + * Other names may be trademarks of their respective owners.
    3.11 + *
    3.12 + * The contents of this file are subject to the terms of either the GNU
    3.13 + * General Public License Version 2 only ("GPL") or the Common
    3.14 + * Development and Distribution License("CDDL") (collectively, the
    3.15 + * "License"). You may not use this file except in compliance with the
    3.16 + * License. You can obtain a copy of the License at
    3.17 + * http://www.netbeans.org/cddl-gplv2.html
    3.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    3.19 + * specific language governing permissions and limitations under the
    3.20 + * License.  When distributing the software, include this License Header
    3.21 + * Notice in each file and include the License file at
    3.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    3.23 + * particular file as subject to the "Classpath" exception as provided
    3.24 + * by Oracle in the GPL Version 2 section of the License file that
    3.25 + * accompanied this code. If applicable, add the following below the
    3.26 + * License Header, with the fields enclosed by brackets [] replaced by
    3.27 + * your own identifying information:
    3.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    3.29 + *
    3.30 + * If you wish your version of this file to be governed by only the CDDL
    3.31 + * or only the GPL Version 2, indicate your decision by adding
    3.32 + * "[Contributor] elects to include this software in this distribution
    3.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    3.34 + * single choice of license, a recipient has the option to distribute
    3.35 + * your version of this file under either the CDDL, the GPL Version 2 or
    3.36 + * to extend the choice of license to its licensees as provided above.
    3.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    3.38 + * Version 2 license, then the option applies only if the new code is
    3.39 + * made subject to such option by the copyright holder.
    3.40 + *
    3.41 + * Contributor(s):
    3.42 + *
    3.43 + * Portions Copyrighted 2016 Sun Microsystems, Inc.
    3.44 + */
    3.45 +package org.netbeans.modules.python.project2;
    3.46 +
    3.47 +import java.util.ArrayList;
    3.48 +import java.util.Collection;
    3.49 +import java.util.Collections;
    3.50 +import java.util.List;
    3.51 +import java.util.concurrent.CopyOnWriteArrayList;
    3.52 +import javax.swing.event.ChangeListener;
    3.53 +import org.netbeans.api.annotations.common.CheckForNull;
    3.54 +import org.netbeans.api.annotations.common.NullAllowed;
    3.55 +import org.openide.filesystems.FileChangeAdapter;
    3.56 +import org.openide.filesystems.FileChangeListener;
    3.57 +import org.openide.filesystems.FileEvent;
    3.58 +import org.openide.filesystems.FileObject;
    3.59 +import org.openide.filesystems.FileRenameEvent;
    3.60 +import org.openide.util.ChangeSupport;
    3.61 +import org.openide.util.Parameters;
    3.62 +import org.openide.util.WeakListeners;
    3.63 +
    3.64 +/**
    3.65 + *
    3.66 + * @author jenselme
    3.67 + * @see org.netbeans.modules.web.common.spi.ImportantFilesSupport
    3.68 + */
    3.69 +class ImportantFilesSupport {
    3.70 +    private final FileObject directory;
    3.71 +    final List<String> fileNames;
    3.72 +    final FileChangeListener fileChangeListener = new FilesListener();
    3.73 +    private final ChangeSupport changeSupport = new ChangeSupport(this);
    3.74 +
    3.75 +    private ImportantFilesSupport(FileObject directory, String... fileNames) {
    3.76 +        assert directory != null;
    3.77 +        assert fileNames != null;
    3.78 +        this.directory = directory;
    3.79 +        this.fileNames = new CopyOnWriteArrayList<>(fileNames);
    3.80 +    }
    3.81 +
    3.82 +    /**
    3.83 +     * Creates new support for the given directory and file name(s).
    3.84 +     *
    3.85 +     * @param directory directory
    3.86 +     * @param fileNames file name(s)
    3.87 +     * @return new support
    3.88 +     */
    3.89 +    public static ImportantFilesSupport create(FileObject directory, String... fileNames) {
    3.90 +        Parameters.notNull("directory", directory); // NOI18N
    3.91 +        Parameters.notNull("fileNames", fileNames); // NOI18N
    3.92 +        ImportantFilesSupport support = new ImportantFilesSupport(directory, fileNames);
    3.93 +        directory.addFileChangeListener(WeakListeners.create(FileChangeListener.class, support.fileChangeListener, directory));
    3.94 +        return support;
    3.95 +    }
    3.96 +
    3.97 +    /**
    3.98 +     * Gets information about all important files.
    3.99 +     *
   3.100 +     * @param fileInfoCreator custom {@link FileInfoCreator}, can be
   3.101 +     * {@code null} (in such case,
   3.102 +     * {@link ImportantFilesImplementation.FileInfo#FileInfo(FileObject)} is
   3.103 +     * used)
   3.104 +     * @return information about all important files; can be empty but never
   3.105 +     * {@code null}
   3.106 +     */
   3.107 +    public Collection<ImportantFilesImplementation.FileInfo> getFiles(@NullAllowed FileInfoCreator fileInfoCreator) {
   3.108 +        List<ImportantFilesImplementation.FileInfo> files = new ArrayList<>();
   3.109 +        for (String name : fileNames) {
   3.110 +            FileObject fo = directory.getFileObject(name);
   3.111 +            if (fo != null) {
   3.112 +                ImportantFilesImplementation.FileInfo info = null;
   3.113 +                if (fileInfoCreator != null) {
   3.114 +                    info = fileInfoCreator.create(fo);
   3.115 +                }
   3.116 +                if (info == null) {
   3.117 +                    info = new ImportantFilesImplementation.FileInfo(fo);
   3.118 +                }
   3.119 +                files.add(info);
   3.120 +            }
   3.121 +        }
   3.122 +        if (files.isEmpty()) {
   3.123 +            return Collections.emptyList();
   3.124 +        }
   3.125 +        return files;
   3.126 +    }
   3.127 +
   3.128 +    /**
   3.129 +     * Adds listener to be notified when important files change.
   3.130 +     *
   3.131 +     * @param listener listener to be notified when important files change
   3.132 +     */
   3.133 +    public void addChangeListener(ChangeListener listener) {
   3.134 +        changeSupport.addChangeListener(listener);
   3.135 +    }
   3.136 +
   3.137 +    /**
   3.138 +     * Removes listener.
   3.139 +     *
   3.140 +     * @param listener listener
   3.141 +     */
   3.142 +    public void removeChangeListener(ChangeListener listener) {
   3.143 +        changeSupport.removeChangeListener(listener);
   3.144 +    }
   3.145 +
   3.146 +    void fireChange() {
   3.147 +        changeSupport.fireChange();
   3.148 +    }
   3.149 +
   3.150 +    //~ Inner classes
   3.151 +    /**
   3.152 +     * {@link ImportantFilesImplementation.FileInfo} creator for the given
   3.153 +     * {@link FileObject}.
   3.154 +     */
   3.155 +    public interface FileInfoCreator {
   3.156 +
   3.157 +        /**
   3.158 +         * Creates {@link ImportantFilesImplementation.FileInfo} for the given
   3.159 +         * {@link FileObject}.
   3.160 +         *
   3.161 +         * @param fileObject FileObject to be used
   3.162 +         * @return {@link ImportantFilesImplementation.FileInfo} for the given
   3.163 +         * {@link FileObject}, can be {
   3.164 +         * @null}
   3.165 +         */
   3.166 +        @CheckForNull
   3.167 +        ImportantFilesImplementation.FileInfo create(FileObject fileObject);
   3.168 +
   3.169 +    }
   3.170 +
   3.171 +    private final class FilesListener extends FileChangeAdapter {
   3.172 +
   3.173 +        @Override
   3.174 +        public void fileRenamed(FileRenameEvent fe) {
   3.175 +            check(fe.getFile().getNameExt());
   3.176 +            check(fe.getName() + "." + fe.getExt()); // NOI18N
   3.177 +        }
   3.178 +
   3.179 +        @Override
   3.180 +        public void fileDeleted(FileEvent fe) {
   3.181 +            check(fe.getFile().getNameExt());
   3.182 +        }
   3.183 +
   3.184 +        @Override
   3.185 +        public void fileDataCreated(FileEvent fe) {
   3.186 +            check(fe.getFile().getNameExt());
   3.187 +        }
   3.188 +
   3.189 +        private void check(String filename) {
   3.190 +            if (fileNames.contains(filename)) {
   3.191 +                fireChange();
   3.192 +            }
   3.193 +        }
   3.194 +
   3.195 +    }
   3.196 +}
     4.1 --- a/python.project2/src/org/netbeans/modules/python/project2/PythonImportantFilesNodeFactory.java	Sun Jan 10 15:02:08 2016 +0100
     4.2 +++ b/python.project2/src/org/netbeans/modules/python/project2/PythonImportantFilesNodeFactory.java	Thu Jan 14 18:37:20 2016 +0100
     4.3 @@ -42,11 +42,9 @@
     4.4  package org.netbeans.modules.python.project2;
     4.5  
     4.6  import org.netbeans.api.project.Project;
     4.7 +import org.netbeans.modules.python.project2.ui.ImportantFiles;
     4.8  import org.netbeans.spi.project.ui.support.NodeFactory;
     4.9 -import org.netbeans.spi.project.ui.support.NodeFactorySupport;
    4.10  import org.netbeans.spi.project.ui.support.NodeList;
    4.11 -import org.openide.loaders.DataObjectNotFoundException;
    4.12 -import org.openide.util.Exceptions;
    4.13  
    4.14  /**
    4.15   *
    4.16 @@ -56,19 +54,7 @@
    4.17  public class PythonImportantFilesNodeFactory implements NodeFactory {
    4.18      @Override
    4.19      public NodeList createNodes(Project project) {
    4.20 -        //Optionally, only return a new node
    4.21 -        //if some item is in the project's lookup:
    4.22 -        //MyCoolLookupItem item = project.getLookup().lookup(MyCoolLookupItem.class);
    4.23 -        //if (item != null) {
    4.24 -        try {
    4.25 -            ImportantFilesNode nd = new ImportantFilesNode(project);
    4.26 -            return NodeFactorySupport.fixedNodeList(nd);
    4.27 -        } catch (DataObjectNotFoundException ex) {
    4.28 -            Exceptions.printStackTrace(ex);
    4.29 -        }
    4.30 -        //If the above try/catch fails, e.g.,
    4.31 -        //our item isn't in the lookup,
    4.32 -        //then return an empty list of nodes:
    4.33 -        return NodeFactorySupport.fixedNodeList();
    4.34 +        NodeFactory nd = ImportantFiles.forPython2Project();
    4.35 +        return nd.createNodes(project);
    4.36      }
    4.37  }
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/python.project2/src/org/netbeans/modules/python/project2/ui/ImportantFiles.java	Thu Jan 14 18:37:20 2016 +0100
     5.3 @@ -0,0 +1,402 @@
     5.4 +/*
     5.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     5.6 + *
     5.7 + * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
     5.8 + *
     5.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    5.10 + * Other names may be trademarks of their respective owners.
    5.11 + *
    5.12 + * The contents of this file are subject to the terms of either the GNU
    5.13 + * General Public License Version 2 only ("GPL") or the Common
    5.14 + * Development and Distribution License("CDDL") (collectively, the
    5.15 + * "License"). You may not use this file except in compliance with the
    5.16 + * License. You can obtain a copy of the License at
    5.17 + * http://www.netbeans.org/cddl-gplv2.html
    5.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    5.19 + * specific language governing permissions and limitations under the
    5.20 + * License.  When distributing the software, include this License Header
    5.21 + * Notice in each file and include the License file at
    5.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    5.23 + * particular file as subject to the "Classpath" exception as provided
    5.24 + * by Oracle in the GPL Version 2 section of the License file that
    5.25 + * accompanied this code. If applicable, add the following below the
    5.26 + * License Header, with the fields enclosed by brackets [] replaced by
    5.27 + * your own identifying information:
    5.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    5.29 + *
    5.30 + * If you wish your version of this file to be governed by only the CDDL
    5.31 + * or only the GPL Version 2, indicate your decision by adding
    5.32 + * "[Contributor] elects to include this software in this distribution
    5.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    5.34 + * single choice of license, a recipient has the option to distribute
    5.35 + * your version of this file under either the CDDL, the GPL Version 2 or
    5.36 + * to extend the choice of license to its licensees as provided above.
    5.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    5.38 + * Version 2 license, then the option applies only if the new code is
    5.39 + * made subject to such option by the copyright holder.
    5.40 + *
    5.41 + * Contributor(s):
    5.42 + *
    5.43 + * Portions Copyrighted 2016 Sun Microsystems, Inc.
    5.44 + */
    5.45 +package org.netbeans.modules.python.project2.ui;
    5.46 +
    5.47 +import java.awt.EventQueue;
    5.48 +import java.awt.Image;
    5.49 +import java.beans.PropertyChangeEvent;
    5.50 +import java.beans.PropertyChangeListener;
    5.51 +import java.beans.PropertyVetoException;
    5.52 +import java.beans.VetoableChangeListener;
    5.53 +import java.util.ArrayList;
    5.54 +import java.util.Collections;
    5.55 +import java.util.Comparator;
    5.56 +import java.util.LinkedHashSet;
    5.57 +import java.util.List;
    5.58 +import java.util.Set;
    5.59 +import java.util.logging.Level;
    5.60 +import java.util.logging.Logger;
    5.61 +import javax.swing.Action;
    5.62 +import javax.swing.event.ChangeEvent;
    5.63 +import javax.swing.event.ChangeListener;
    5.64 +import org.netbeans.api.annotations.common.CheckForNull;
    5.65 +import org.netbeans.api.annotations.common.NonNull;
    5.66 +import org.netbeans.api.annotations.common.StaticResource;
    5.67 +import org.netbeans.api.project.Project;
    5.68 +import org.netbeans.modules.python.project2.ImportantFilesImplementation;
    5.69 +import org.netbeans.spi.project.ui.support.NodeFactory;
    5.70 +import org.netbeans.spi.project.ui.support.NodeList;
    5.71 +import org.openide.filesystems.FileAttributeEvent;
    5.72 +import org.openide.filesystems.FileChangeListener;
    5.73 +import org.openide.filesystems.FileEvent;
    5.74 +import org.openide.filesystems.FileObject;
    5.75 +import org.openide.filesystems.FileRenameEvent;
    5.76 +import org.openide.filesystems.FileStateInvalidException;
    5.77 +import org.openide.filesystems.FileUtil;
    5.78 +import org.openide.filesystems.StatusDecorator;
    5.79 +import org.openide.loaders.DataFolder;
    5.80 +import org.openide.loaders.DataObject;
    5.81 +import org.openide.loaders.DataObjectNotFoundException;
    5.82 +import org.openide.nodes.AbstractNode;
    5.83 +import org.openide.nodes.Children;
    5.84 +import org.openide.nodes.FilterNode;
    5.85 +import org.openide.nodes.Node;
    5.86 +import org.openide.util.ChangeSupport;
    5.87 +import org.openide.util.Exceptions;
    5.88 +import org.openide.util.ImageUtilities;
    5.89 +import org.openide.util.Lookup;
    5.90 +import org.openide.util.LookupEvent;
    5.91 +import org.openide.util.LookupListener;
    5.92 +import org.openide.util.WeakListeners;
    5.93 +
    5.94 +/**
    5.95 + *
    5.96 + * @author jenselme
    5.97 + * @see org.netbeans.modules.web.common.ui.ImportantFiles
    5.98 + */
    5.99 +public class ImportantFiles {
   5.100 +    static final Logger LOGGER = Logger.getLogger(ImportantFiles.class.getName());
   5.101 +    static final String[] PYTHON_IMPORTANT_FILES = {
   5.102 +        "setup.py", // NOI18N
   5.103 +        "development.ini", // NOI18N, Pyramid config file
   5.104 +        "production.ini", // NOI18N, Pyramid config file
   5.105 +        ".hgignore", // NOI18N
   5.106 +        ".gitignore", // NOI18N
   5.107 +        "MANIFEST", // NOI18N
   5.108 +        "MANIFEST.in", // NOI18N
   5.109 +        "README", // NOI18N
   5.110 +        "README.md", // NOI18N
   5.111 +        "README.rst", // NOI18N
   5.112 +        "LICENSE", // NOI18N
   5.113 +        "COPYING", // NOI18N
   5.114 +    };
   5.115 +
   5.116 +    private ImportantFiles() {
   5.117 +    }
   5.118 +
   5.119 +    public static NodeFactory forPython2Project() {
   5.120 +        return new ImportantFilesNodeFactory();
   5.121 +    }
   5.122 +
   5.123 +    public static class ImportantFilesNodeFactory implements NodeFactory {
   5.124 +        @Override
   5.125 +        public NodeList<?> createNodes(@NonNull Project project) {
   5.126 +            return new ImportantFilesNodeList(project);
   5.127 +        }
   5.128 +    }
   5.129 +
   5.130 +    private static class ImportantFilesNodeList implements NodeList<Object>, LookupListener, ChangeListener {
   5.131 +        private static final Object IMPORTANT_FILES_KEY = new Object();
   5.132 +
   5.133 +        private final Lookup.Result<ImportantFilesImplementation> lookupResult;
   5.134 +        private final ImportantFilesChildren importantFilesChildren;
   5.135 +        final ChangeSupport changeSupport = new ChangeSupport(this);
   5.136 +
   5.137 +        private Node importantFilesNode;
   5.138 +
   5.139 +        ImportantFilesNodeList(@NonNull Project project) {
   5.140 +            importantFilesChildren = new ImportantFilesChildren(project, changeSupport);
   5.141 +            lookupResult = project.getLookup().lookupResult(ImportantFilesImplementation.class);
   5.142 +        }
   5.143 +
   5.144 +        @Override
   5.145 +        public List<Object> keys() {
   5.146 +            if (!importantFilesChildren.hasImportantFiles()) {
   5.147 +                return Collections.emptyList();
   5.148 +            }
   5.149 +            return Collections.singletonList(IMPORTANT_FILES_KEY);
   5.150 +        }
   5.151 +
   5.152 +        @Override
   5.153 +        public void addChangeListener(ChangeListener listener) {
   5.154 +            changeSupport.addChangeListener(listener);
   5.155 +        }
   5.156 +
   5.157 +        @Override
   5.158 +        public void removeChangeListener(ChangeListener listener) {
   5.159 +            changeSupport.removeChangeListener(listener);
   5.160 +        }
   5.161 +
   5.162 +        @Override
   5.163 +        public synchronized Node node(Object key) {
   5.164 +            assert key == IMPORTANT_FILES_KEY : "Unexpected key " + key;//NOI18N
   5.165 +            if (importantFilesNode == null) {
   5.166 +                importantFilesNode = new ImportantFilesNode(importantFilesChildren);
   5.167 +            }
   5.168 +            return importantFilesNode;
   5.169 +        }
   5.170 +
   5.171 +        @Override
   5.172 +        public void addNotify() {
   5.173 +            lookupResult.addLookupListener(WeakListeners.create(LookupListener.class, this, lookupResult));
   5.174 +            for (ImportantFilesImplementation provider : lookupResult.allInstances()) {
   5.175 +                provider.addChangeListener(WeakListeners.change(this, provider));
   5.176 +            }
   5.177 +        }
   5.178 +
   5.179 +        @Override
   5.180 +        public void removeNotify() {
   5.181 +        }
   5.182 +
   5.183 +        @Override
   5.184 +        public void resultChanged(LookupEvent ev) {
   5.185 +            fireChange();
   5.186 +        }
   5.187 +
   5.188 +        @Override
   5.189 +        public void stateChanged(ChangeEvent e) {
   5.190 +            importantFilesChildren.refreshImportantFiles();
   5.191 +            fireChange();
   5.192 +        }
   5.193 +
   5.194 +        private void fireChange() {
   5.195 +            EventQueue.invokeLater(new Runnable() {
   5.196 +                @Override
   5.197 +                public void run() {
   5.198 +                    changeSupport.fireChange();
   5.199 +                }
   5.200 +            });
   5.201 +        }
   5.202 +    }
   5.203 +
   5.204 +    private static final class ImportantFilesNode extends AbstractNode {
   5.205 +
   5.206 +        @StaticResource
   5.207 +        private static final String BADGE = "org/netbeans/modules/python/project2/resources/py_25_16.png"; // NOI18N
   5.208 +
   5.209 +        private final Node iconDelegate;
   5.210 +
   5.211 +        ImportantFilesNode(Children children) {
   5.212 +            super(children);
   5.213 +            iconDelegate = DataFolder.findFolder(FileUtil.getConfigRoot()).getNodeDelegate();
   5.214 +        }
   5.215 +
   5.216 +        @Override
   5.217 +        public String getDisplayName() {
   5.218 +            return "Important Files";
   5.219 +        }
   5.220 +
   5.221 +        @Override
   5.222 +        public Image getIcon(int type) {
   5.223 +            return ImageUtilities.mergeImages(iconDelegate.getIcon(type), ImageUtilities.loadImage(BADGE), 7, 7);
   5.224 +        }
   5.225 +
   5.226 +        @Override
   5.227 +        public Image getOpenedIcon(int type) {
   5.228 +            return getIcon(type);
   5.229 +        }
   5.230 +
   5.231 +        @Override
   5.232 +        public Action[] getActions(boolean context) {
   5.233 +            return new Action[0];
   5.234 +        }
   5.235 +
   5.236 +    }
   5.237 +
   5.238 +    private static final class ImportantFilesChildren extends Children.Keys<ImportantFilesImplementation.FileInfo> {
   5.239 +
   5.240 +        private static final Logger LOGGER = Logger.getLogger(ImportantFilesChildren.class.getName());
   5.241 +        private final FileObject projectDirectory;
   5.242 +        private final ChangeSupport changeSupport;
   5.243 +
   5.244 +        ImportantFilesChildren(@NonNull Project project, ChangeSupport changeSupport) {
   5.245 +            super(true);
   5.246 +            this.projectDirectory = project.getProjectDirectory();
   5.247 +            this.changeSupport = changeSupport;
   5.248 +
   5.249 +            FileUtil.addFileChangeListener(new FileChangeListener() {
   5.250 +                @Override
   5.251 +                public void fileFolderCreated(FileEvent fe) {
   5.252 +                }
   5.253 +
   5.254 +                @Override
   5.255 +                public void fileDataCreated(FileEvent fe) {
   5.256 +                    refreshImportantFiles();
   5.257 +                    fireChange();
   5.258 +                }
   5.259 +
   5.260 +                @Override
   5.261 +                public void fileChanged(FileEvent fe) {
   5.262 +                }
   5.263 +
   5.264 +                @Override
   5.265 +                public void fileDeleted(FileEvent fe) {
   5.266 +                    refreshImportantFiles();
   5.267 +                    fireChange();
   5.268 +                }
   5.269 +
   5.270 +                @Override
   5.271 +                public void fileRenamed(FileRenameEvent fe) {
   5.272 +                    refreshImportantFiles();
   5.273 +                    fireChange();
   5.274 +                }
   5.275 +
   5.276 +                @Override
   5.277 +                public void fileAttributeChanged(FileAttributeEvent fe) {
   5.278 +                }
   5.279 +            });
   5.280 +        }
   5.281 +
   5.282 +        public boolean hasImportantFiles() {
   5.283 +            return !getImportantFiles().isEmpty();
   5.284 +        }
   5.285 +
   5.286 +        private void refreshImportantFiles() {
   5.287 +            setKeys();
   5.288 +        }
   5.289 +
   5.290 +        private void fireChange() {
   5.291 +            EventQueue.invokeLater(new Runnable() {
   5.292 +                @Override
   5.293 +                public void run() {
   5.294 +                    changeSupport.fireChange();
   5.295 +                }
   5.296 +            });
   5.297 +        }
   5.298 +
   5.299 +        @Override
   5.300 +        protected Node[] createNodes(ImportantFilesImplementation.FileInfo key) {
   5.301 +            assert key != null;
   5.302 +            try {
   5.303 +                return new Node[]{new ImportantFileNode(key)};
   5.304 +            } catch (DataObjectNotFoundException ex) {
   5.305 +                LOGGER.log(Level.WARNING, null, ex);
   5.306 +            }
   5.307 +            return new Node[0];
   5.308 +        }
   5.309 +
   5.310 +        @Override
   5.311 +        protected void addNotify() {
   5.312 +            setKeys();
   5.313 +        }
   5.314 +
   5.315 +        @Override
   5.316 +        protected void removeNotify() {
   5.317 +            setKeys(Collections.<ImportantFilesImplementation.FileInfo>emptyList());
   5.318 +        }
   5.319 +
   5.320 +        private void setKeys() {
   5.321 +            List<ImportantFilesImplementation.FileInfo> importantFiles = getImportantFiles();
   5.322 +            Collections.sort(importantFiles, new FileInfoComparator());
   5.323 +            setKeys(importantFiles);
   5.324 +        }
   5.325 +
   5.326 +        private List<ImportantFilesImplementation.FileInfo> getImportantFiles() {
   5.327 +            Set<ImportantFilesImplementation.FileInfo> importantFiles = new LinkedHashSet<>();
   5.328 +            for (String fileName : PYTHON_IMPORTANT_FILES) {
   5.329 +                final FileObject file = projectDirectory.getFileObject(fileName);
   5.330 +                if (file != null) {
   5.331 +                    importantFiles.add(new ImportantFilesImplementation.FileInfo(file));
   5.332 +                }
   5.333 +            }
   5.334 +
   5.335 +            return new ArrayList<>(importantFiles);
   5.336 +        }
   5.337 +
   5.338 +    }
   5.339 +
   5.340 +    private static final class ImportantFileNode extends FilterNode {
   5.341 +
   5.342 +        private final ImportantFilesImplementation.FileInfo fileInfo;
   5.343 +
   5.344 +        ImportantFileNode(ImportantFilesImplementation.FileInfo fileInfo) throws DataObjectNotFoundException {
   5.345 +            super(DataObject.find(fileInfo.getFile()).getNodeDelegate());
   5.346 +            this.fileInfo = fileInfo;
   5.347 +        }
   5.348 +
   5.349 +        @Override
   5.350 +        public String getDisplayName() {
   5.351 +            String displayName = fileInfo.getDisplayName();
   5.352 +            if (displayName != null) {
   5.353 +                return displayName;
   5.354 +            }
   5.355 +            return super.getDisplayName();
   5.356 +        }
   5.357 +
   5.358 +        @Override
   5.359 +        public String getHtmlDisplayName() {
   5.360 +            String displayName = getDisplayName();
   5.361 +            assert displayName != null : fileInfo;
   5.362 +            StatusDecorator statusDecorator = getStatusDecorator();
   5.363 +            if (statusDecorator != null) {
   5.364 +                return statusDecorator.annotateNameHtml(displayName, Collections.singleton(fileInfo.getFile()));
   5.365 +            }
   5.366 +            return displayName;
   5.367 +        }
   5.368 +
   5.369 +        @Override
   5.370 +        public String getShortDescription() {
   5.371 +            String description = fileInfo.getDescription();
   5.372 +            if (description != null) {
   5.373 +                return description;
   5.374 +            }
   5.375 +            return super.getShortDescription();
   5.376 +        }
   5.377 +
   5.378 +        @CheckForNull
   5.379 +        private StatusDecorator getStatusDecorator() {
   5.380 +            try {
   5.381 +                return fileInfo.getFile().getFileSystem().getDecorator();
   5.382 +            } catch (FileStateInvalidException ex) {
   5.383 +                LOGGER.log(Level.INFO, null, ex);
   5.384 +            }
   5.385 +            return null;
   5.386 +        }
   5.387 +
   5.388 +    }
   5.389 +
   5.390 +    private static final class FileInfoComparator implements Comparator<ImportantFilesImplementation.FileInfo> {
   5.391 +
   5.392 +        @Override
   5.393 +        public int compare(ImportantFilesImplementation.FileInfo fileInfo1, ImportantFilesImplementation.FileInfo fileInfo2) {
   5.394 +            FileObject file1 = fileInfo1.getFile();
   5.395 +            FileObject file2 = fileInfo2.getFile();
   5.396 +            try {
   5.397 +                return DataFolder.SortMode.FOLDER_NAMES.compare(DataObject.find(file1), DataObject.find(file2));
   5.398 +            } catch (DataObjectNotFoundException ex) {
   5.399 +                return file1.getNameExt().compareToIgnoreCase(file2.getNameExt());
   5.400 +            }
   5.401 +        }
   5.402 +
   5.403 +    }
   5.404 +
   5.405 +}