/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.
 *
 * 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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]"
 *
 * 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.
 *
 * Contributor(s):
 *
 * Portions Copyrighted 2008 Sun Microsystems, Inc.
 */

package org.netbeans.modules.maven.nodes;

import java.awt.Component;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenuItem;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DependencyManagement;
import org.apache.maven.model.DistributionManagement;
import org.apache.maven.model.Profile;
import org.apache.maven.model.Relocation;
import org.apache.maven.model.building.ModelBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.project.ProjectBuildingResult;
import org.codehaus.plexus.util.FileUtils;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.java.queries.JavadocForBinaryQuery;
import org.netbeans.api.progress.aggregate.AggregateProgressFactory;
import org.netbeans.api.progress.aggregate.AggregateProgressHandle;
import org.netbeans.api.progress.aggregate.ProgressContributor;
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.api.project.ui.OpenProjects;
import org.netbeans.modules.maven.NbArtifactFixer;
import org.netbeans.modules.maven.NbMavenProjectImpl;
import org.netbeans.modules.maven.api.CommonArtifactActions;
import org.netbeans.modules.maven.api.NbMavenProject;
import org.netbeans.modules.maven.dependencies.DependencyExcludeNodeVisitor;
import org.netbeans.modules.maven.dependencies.ExcludeDependencyPanel;
import org.netbeans.modules.maven.embedder.DependencyTreeFactory;
import org.netbeans.modules.maven.embedder.EmbedderFactory;
import org.netbeans.modules.maven.embedder.MavenEmbedder;
import org.netbeans.modules.maven.embedder.exec.ProgressTransferListener;
import org.netbeans.modules.maven.indexer.api.NBVersionInfo;
import org.netbeans.modules.maven.indexer.api.QueryField;
import org.netbeans.modules.maven.indexer.api.RepositoryQueries;
import org.netbeans.modules.maven.model.ModelOperation;
import org.netbeans.modules.maven.model.Utilities;
import org.netbeans.modules.maven.model.pom.Exclusion;
import org.netbeans.modules.maven.model.pom.POMModel;
import org.netbeans.spi.project.ui.PathFinder;
import static org.netbeans.modules.maven.nodes.Bundle.*;
import org.netbeans.modules.maven.queries.MavenFileOwnerQueryImpl;
import org.netbeans.modules.maven.queries.RepositoryForBinaryQueryImpl;
import org.netbeans.modules.maven.queries.RepositoryForBinaryQueryImpl.Coordinates;
import org.netbeans.modules.maven.spi.IconResources;
import org.netbeans.modules.maven.spi.nodes.DependencyTypeIconBadge;
import org.netbeans.modules.maven.spi.queries.ForeignClassBundler;
import org.netbeans.spi.java.project.support.ui.PackageView;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.actions.PropertiesAction;
import org.openide.awt.HtmlBrowser;
import org.openide.awt.StatusDisplayer;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.URLMapper;
import org.openide.loaders.DataObject;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.nodes.PropertySupport;
import org.openide.nodes.Sheet;
import org.openide.util.ContextAwareAction;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
import org.openide.util.actions.Presenter;
import org.openide.util.lookup.Lookups;

/**
 * node representing a dependency
 * @author  Milos Kleint 
 */
public class DependencyNode extends AbstractNode implements PreferenceChangeListener {

    
    private boolean longLiving;
    private PropertyChangeListener listener;
    private ChangeListener listener2;
    final Data data;
    
    private volatile String iconBase = IconResources.DEPENDENCY_ICON;
    
    private static final String toolTipJavadoc = "<img src=\"" + DependencyNode.class.getClassLoader().getResource(IconResources.JAVADOC_BADGE_ICON) + "\">&nbsp;" //NOI18N
            + NbBundle.getMessage(DependencyNode.class, "ICON_JavadocBadge");//NOI18N
    private static final String toolTipSource = "<img src=\"" + DependencyNode.class.getClassLoader().getResource(IconResources.SOURCE_BADGE_ICON) + "\">&nbsp;" //NOI18N
            + NbBundle.getMessage(DependencyNode.class, "ICON_SourceBadge");//NOI18N
    private static final String toolTipMissing = "<img src=\"" + DependencyNode.class.getClassLoader().getResource(IconResources.BROKEN_PROJECT_BADGE_ICON) + "\">&nbsp;" //NOI18N
            + NbBundle.getMessage(DependencyNode.class, "ICON_MissingBadge");//NOI18N
    private static final String toolTipManaged = "<img src=\"" + DependencyNode.class.getClassLoader().getResource(IconResources.MANAGED_BADGE_ICON) + "\">&nbsp;" //NOI18N
            + NbBundle.getMessage(DependencyNode.class, "ICON_ManagedBadge");//NOI18N
    private static final String toolTipForeignBundler = "<img src=\"" + DependencyNode.class.getClassLoader().getResource(IconResources.BROKEN_PROJECT_BADGE_ICON) + "\">&nbsp;" //NOI18N
            + NbBundle.getMessage(DependencyNode.class, "ICON_PreferSourcesBadge");//NOI18N
    
    private static final RequestProcessor RP = new RequestProcessor(DependencyNode.class);

    private static Children createChildren(@NullAllowed Node nodeDelegate) {
        return nodeDelegate == null ? Children.LEAF : new JarContentFilterChildren(nodeDelegate);
    }

    @NonNull
    private static Lookup createLookup(@NonNull final Project project, @NonNull final Artifact art, @NullAllowed final Node nodeDelegate) {
        final PathFinder pathFinderDelegate = nodeDelegate == null ? null : nodeDelegate.getLookup().lookup(PathFinder.class);
        final FileObject fo = nodeDelegate == null ? null : nodeDelegate.getLookup().lookup(FileObject.class);
        if (fo != null) {
            return Lookups.fixed(new Data(art, project, fo), project, art, PathFinders.createDelegatingPathFinder(pathFinderDelegate), fo);
        } else {
            return Lookups.fixed(new Data(art, project, null), project, art, PathFinders.createDelegatingPathFinder(pathFinderDelegate));
        }
    }

    @CheckForNull
    static Node createNodeDelegate(@NonNull final Artifact art, FileObject fo, final boolean longLiving) {
        if (!longLiving) {
            return null;
        }
        //artifact.getFile() should be eagerly normalized
        if (fo != null && FileUtil.isArchiveFile(fo)) {
            return PackageView.createPackageView(new ArtifactSourceGroup(art));
        }
        return null;        
    }

    public DependencyNode(
            NbMavenProjectImpl project,
            final Artifact art,
            final FileObject fo,
            boolean isLongLiving,
            Node nodeDelegate) {
        super(createChildren(nodeDelegate), createLookup(project, art, nodeDelegate));
        this.data = getLookup().lookup(Data.class);
        data.fileObject.set(fo);
        longLiving = isLongLiving;

        if (longLiving) {
            listener = new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if (NbMavenProject.PROP_PROJECT.equals(evt.getPropertyName())) {
                        refreshNode();
                    }
                }
            };
            NbMavenProject.addPropertyChangeListener(project, WeakListeners.propertyChange(listener, project.getProjectWatcher()));
            listener2 = new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent event) {
                    if (event instanceof MavenFileOwnerQueryImpl.GAVCHangeEvent) {
                        MavenFileOwnerQueryImpl.GAVCHangeEvent ev = (MavenFileOwnerQueryImpl.GAVCHangeEvent) event;
                        if (ev.matches(art)) {
                            refreshNode();
                        }
                    } else {
                        refreshNode();
                    }
                }
            };
            //TODO check if this one is a performance bottleneck.
            MavenFileOwnerQueryImpl.getInstance().addChangeListener(
                    WeakListeners.change(listener2,
                    MavenFileOwnerQueryImpl.getInstance()));
            DependenciesNode.prefs().addPreferenceChangeListener(WeakListeners.create(PreferenceChangeListener.class, this, DependenciesNode.prefs()));
        }
        setDisplayName(createName(false));
        setIconBase(false);
        if (longLiving) {
            RP.post(new Runnable() {
                @Override
                public void run() {
                    refreshNode();
                }
            });
        }
        data.setNode(this);
    }

    

    private void setIconBase(boolean longLiving) {
        String base;
        if (longLiving && data.isDependencyProjectAvailable()) {
            if (data.isTransitive()) {
                base = IconResources.TRANSITIVE_MAVEN_ICON;
            } else {
                base = IconResources.MAVEN_ICON;
            }
        } else if (data.isTransitive()) {
            if (data.isAddedToCP()) {
                base = IconResources.TRANSITIVE_DEPENDENCY_ICON;
            } else {
                base = IconResources.TRANSITIVE_ARTIFACT_ICON;
            }
        } else if (data.isAddedToCP()) {
            base = IconResources.DEPENDENCY_ICON;
        } else {
            base = IconResources.ARTIFACT_ICON;
        }
        this.iconBase = base;
        setIconBaseWithExtension(base);
    }

    @Messages({
        "DESC_Dep1=GroupId:",
        "DESC_Dep2=ArtifactId:",
        "DESC_Dep3=Version:",
        "DESC_Dep4=Type:",
        "DESC_Dep5=Classifier:",
        "DESC_scope=Scope:",
        "DESC_via=Via:"
    })
    @Override public String getShortDescription() {
        StringBuilder buf = new StringBuilder();
        buf.append("<html><i>").append(DESC_Dep1()).append("</i><b> ").append(data.art.getGroupId()).append("</b><br><i>"); //NOI18N
        buf.append(DESC_Dep2()).append("</i><b> ").append(data.art.getArtifactId()).append("</b><br><i>");//NOI18N
        buf.append(DESC_Dep3()).append("</i><b> ").append(data.art.getVersion()).append("</b><br><i>");//NOI18N
        buf.append(DESC_Dep4()).append("</i><b> ").append(data.art.getType()).append("</b><br>");//NOI18N
        if (data.art.getClassifier() != null) {
            buf.append("<i>").append(DESC_Dep5()).append("</i><b> ").append(data.art.getClassifier()).append("</b><br>");//NOI18N
        }
        buf.append("<i>").append(DESC_scope()).append("</i><b> ").append(data.art.getScope()).append("</b><br>");
        List<String> trail = data.art.getDependencyTrail();
        for (int i = trail.size() - 2; i > 0 && /* just to be safe */ i < trail.size(); i--) {
            String[] id = trail.get(i).split(":"); // g:a:t[:c]:v
            buf.append("<i>").append(DESC_via()).append("</i> ").append(id[1]).append("<br>");
        }
        // it seems that with ending </html> tag the icon descriptions are not added.
//        buf.append("</html>");//NOI18N
        return buf.toString();
    }

    

    
    private void refreshNode() {
        assert !SwingUtilities.isEventDispatchThread();
        FileObject fo = FileUtil.toFileObject(data.art.getFile());
        
        data.sourceExists.set(data.getSourceFile().exists());
        data.javadocExists.set(data.getJavadocFile().exists());
        data.fileObject.set(fo);
        setDisplayName(createName(longLiving));
        setIconBase(longLiving);
        fireIconChange();
        fireDisplayNameChange(null, getDisplayName());
//        if (longLiving) {
//            ((DependencyChildren)getChildren()).doRefresh();
//        }
        //#142784
        if (longLiving) {
            if (Children.LEAF == getChildren()) {
                final Node nodeDelegate = createNodeDelegate(data.art, fo, true);
                Children childs = createChildren(nodeDelegate);
                if (childs != Children.LEAF) {
                    setChildren(childs);
                    PathFinders.updateDelegate(getLookup().lookup(PathFinder.class), nodeDelegate.getLookup().lookup(PathFinder.class));
                }
            }
        }
    }

    @Override
    public String getHtmlDisplayName() {
        StringBuilder n = new StringBuilder("<html>");
        n.append(getDisplayName());
        if (ArtifactUtils.isSnapshot(data.art.getVersion()) && data.art.getVersion().indexOf("SNAPSHOT") < 0) { //NOI18N
            n.append(" <b>[").append(data.art.getVersion()).append("]</b>");
        }
        if (!data.art.getArtifactHandler().isAddedToClasspath() && !Artifact.SCOPE_COMPILE.equals(data.art.getScope())) {
            n.append("  <i>[").append(data.art.getScope()).append("]</i>");
        }
        n.append("</html>");
        return n.toString();
    }

    private String createName(boolean longLiving) {
        if (longLiving) {
            //TODO when the name changes on the other end (dep project) we have no way of knowing..
            Project prj = data.getDependencyProject();
            if (prj != null) {
                return ProjectUtils.getInformation(prj).getDisplayName();
            }
        }
        if (NbArtifactFixer.isFallbackFile(data.art.getFile())) {
            return data.art.getArtifactId() + "-" + data.art.getBaseVersion() + (data.art.getClassifier() != null ? "-" + data.art.getClassifier() : "") + "." + data.art.getArtifactHandler().getExtension();
        }
        return data.art.getFile().getName();
    }

    @Override
    public Action[] getActions(boolean context) {
        Collection<Action> acts = new ArrayList<Action>();
        if (longLiving && data.isDependencyProjectAvailable()) {
            acts.add(OpenProjectAction.SINGLETON);
        }
        boolean local = data.isLocal();
        if (data.isAddedToCP()) {
            InstallLocalArtifactAction act = new InstallLocalArtifactAction();
            acts.add(act);
            if (!local) {
                act.setEnabled(true);
            }
        }
        if (local) {
            acts.add(new CopyLocationAction());
        }

//        acts.add(new EditAction());
//        acts.add(RemoveDepAction.get(RemoveDepAction.class));
        if (!data.hasJavadocInRepository()) {
            acts.add(DOWNLOAD_JAVADOC_ACTION);
            if (data.isAddedToCP()) {
                acts.add(new InstallLocalJavadocAction());
            }
        }
        if (!data.hasSourceInRepository()) {
            acts.add(DOWNLOAD_SOURCE_ACTION);
            if (data.isAddedToCP()) {
                acts.add(new InstallLocalSourcesAction());
            }
        }
        if (data.isTransitive()) {
            acts.add(new ExcludeTransitiveAction());
            acts.add(SETINCURRENTINSTANCE);
        } else {
            acts.add(REMOVEDEPINSTANCE);
        }
        acts.add(null);
        acts.add(CommonArtifactActions.createViewArtifactDetails(data.art, data.getMavenProject().getRemoteArtifactRepositories()));
        acts.add(CommonArtifactActions.createFindUsages(data.art));
        acts.add(null);
        acts.add(CommonArtifactActions.createViewJavadocAction(data.art));
        /* #164992: disabled
        acts.add(CommonArtifactActions.createViewProjectHomeAction(art, project.getOriginalMavenProject().getRemoteArtifactRepositories()));
        acts.add(CommonArtifactActions.createViewBugTrackerAction(art, project.getOriginalMavenProject().getRemoteArtifactRepositories()));
        acts.add(CommonArtifactActions.createSCMActions(art, project.getOriginalMavenProject().getRemoteArtifactRepositories()));
         */
        acts.add(null);
        acts.add(PropertiesAction.get(PropertiesAction.class));
        return acts.toArray(new Action[acts.size()]);
    }

    

    @Override
    public boolean canDestroy() {
        return !data.isTransitive();
    }
    @Override
    public void destroy() throws IOException {
        REMOVEDEPINSTANCE.createContextAwareInstance(getLookup()).actionPerformed(null);
    }

    @Override
    public boolean canRename() {
        return false;
    }

    
    @Override
    public boolean canCut() {
        return false;
    }

    @Override
    public boolean canCopy() {
        return false;
    }

    @Override
    public boolean hasCustomizer() {
        return false;
    }

    void downloadJavadocSources(ProgressContributor progress, boolean isjavadoc) {
        downloadJavadocSources(progress, isjavadoc, data.art, data.project);
        refreshNode();
    }

    /**
     * 
     * @param progress
     * @param isjavadoc
     * @param artifact
     * @param prj
     * @return the sources/javadoc artifact
     */
    public static Artifact downloadJavadocSources(ProgressContributor progress, boolean isjavadoc, Artifact artifact, Project prj) {
        MavenEmbedder online = EmbedderFactory.getOnlineEmbedder();
        progress.start(2);
        if ( Artifact.SCOPE_SYSTEM.equals(artifact.getScope())) {
            progress.finish();
            return null;
        }
        NbMavenProjectImpl prjimpl = prj.getLookup().lookup(NbMavenProjectImpl.class);
        Artifact sources = null;
        try {
            String classifier;
            String baseClassifier;
            String bundleName;
            if (isjavadoc) {
                if (artifact.getClassifier() != null) {
                    if ("tests".equals(artifact.getClassifier())) {
                        classifier = "test-javadoc";
                    } else {
                        classifier = artifact.getClassifier() + "-javadoc";
                    }
                } else {
                    classifier = "javadoc";
                }
                baseClassifier = "javadoc";
                bundleName = "MSG_Checking_Javadoc";
            } else {
                baseClassifier = "sources";
                if (artifact.getClassifier() != null) {
                    if ("tests".equals(artifact.getClassifier())) {
                        classifier = "test-sources";
                    } else {
                        classifier = artifact.getClassifier() + "-sources";
                    }
                } else {
                    classifier = "sources";
                }
                bundleName = "MSG_Checking_Sources";
            }
            
            sources = prjimpl.getEmbedder().createArtifactWithClassifier(
                artifact.getGroupId(),
                artifact.getArtifactId(),
                artifact.getVersion(),
                artifact.getType(),
                classifier); 
            progress.progress(org.openide.util.NbBundle.getMessage(DependencyNode.class, bundleName,artifact.getId()), 1);
            online.resolve(sources, prjimpl.getOriginalMavenProject().getRemoteArtifactRepositories(), prjimpl.getEmbedder().getLocalRepository());
            if (artifact.getFile() != null && artifact.getFile().exists()) {
                List<Coordinates> coordinates = RepositoryForBinaryQueryImpl.getJarMetadataCoordinates(artifact.getFile());
                if (coordinates != null) {
                    for (Coordinates coordinate : coordinates) {
                        sources = prjimpl.getEmbedder().createArtifactWithClassifier(
                            coordinate.groupId,
                            coordinate.artifactId,
                            coordinate.version,
                            "jar",
                            baseClassifier);
                        progress.progress(org.openide.util.NbBundle.getMessage(DependencyNode.class, bundleName, artifact.getId()), 1);
                        online.resolve(sources, prjimpl.getOriginalMavenProject().getRemoteArtifactRepositories(), prjimpl.getEmbedder().getLocalRepository());
                    }
                }
            }
        } catch (ArtifactNotFoundException ex) {
            // just ignore..ex.printStackTrace();
        } catch (ArtifactResolutionException ex) {
            // just ignore..ex.printStackTrace();
        } finally {
            progress.finish();
        }
        return sources;
    }


    
    @Override
    public Image getIcon(int param) {
        return badge(super.getIcon(param));
    }
    
    private boolean isIconProjectBased() {
        String base = iconBase;
        return IconResources.TRANSITIVE_MAVEN_ICON.equals(base) || IconResources.MAVEN_ICON.equals(base);
    }

    private Image badge(Image retValue) {
        if (data.isLocal()) {
            if (!isIconProjectBased()) {
                if (data.hasJavadocInRepository()) {
                    Image ann = ImageUtilities.loadImage(IconResources.JAVADOC_BADGE_ICON); //NOI18N
                    ann = ImageUtilities.addToolTipToImage(ann, toolTipJavadoc);
                    retValue = ImageUtilities.mergeImages(retValue, ann, 12, 0);//NOI18N
                }
                if (data.hasSourceInRepository()) {
                    Image ann = ImageUtilities.loadImage(IconResources.SOURCE_BADGE_ICON); //NOI18N
                    ann = ImageUtilities.addToolTipToImage(ann, toolTipSource);
                    retValue = ImageUtilities.mergeImages(retValue, ann, 12, 8);//NOI18N
                }
            } else {
                Project p = data.getDependencyProject();
                if (p != null) {
                    ForeignClassBundler fcb = p.getLookup().lookup(ForeignClassBundler.class);
                    if (fcb != null && !fcb.preferSources()) {
                        Image ann = ImageUtilities.loadImage(IconResources.BROKEN_PROJECT_BADGE_ICON); //NOI18N
                        ann = ImageUtilities.addToolTipToImage(ann, toolTipForeignBundler);
                        retValue =  ImageUtilities.mergeImages(retValue, ann, 0, 0);//NOI18N
                    }
                }
            }
            if (showManagedState() && data.isManaged()) {
                Image ann = ImageUtilities.loadImage(IconResources.MANAGED_BADGE_ICON); //NOI18N
                ann = ImageUtilities.addToolTipToImage(ann, toolTipManaged);
                retValue = ImageUtilities.mergeImages(retValue, ann, 0, 8);//NOI18N
            }
            FileObject fo = data.fileObject.get();
            if (fo != null && fo.isValid()) {
                for (DependencyTypeIconBadge badge : Lookup.getDefault().lookupAll(DependencyTypeIconBadge.class)) {
                    Image ann = badge.getBadgeIcon(fo, data.art);
                    if (ann != null) {
                        retValue = ImageUtilities.mergeImages(retValue, ann, 0, 0);//NOI18N
                        break;
                    }
                }
            }
            return retValue;
        } else if (!isIconProjectBased()) {
            Image ann = ImageUtilities.loadImage(IconResources.BROKEN_PROJECT_BADGE_ICON); //NOI18N
            ann = ImageUtilities.addToolTipToImage(ann, toolTipMissing);
            return ImageUtilities.mergeImages(retValue, ann, 0, 0);//NOI18N
        } else {
            Project p = data.getDependencyProject();
            if (p != null) {
                ForeignClassBundler fcb = p.getLookup().lookup(ForeignClassBundler.class);
                if (fcb != null && !fcb.preferSources()) {
                    Image ann = ImageUtilities.loadImage(IconResources.BROKEN_PROJECT_BADGE_ICON); //NOI18N
                    ann = ImageUtilities.addToolTipToImage(ann, toolTipForeignBundler);
                    retValue = ImageUtilities.mergeImages(retValue, ann, 0, 0);//NOI18N
                }
            }
            return retValue;
        }
    }

    @Override
    public Image getOpenedIcon(int type) {
        return badge(super.getOpenedIcon(type));
    }

    @Override
    public Component getCustomizer() {
        return null;
    }

    @Override
    protected Sheet createSheet() {
        Sheet sheet = Sheet.createDefault();
        Sheet.Set basicProps = sheet.get(Sheet.PROPERTIES);
        try {
            PropertySupport.Reflection artifactId = new PropertySupport.Reflection<String>(data.art, String.class, "getArtifactId", null); //NOI18N
            artifactId.setName("artifactId"); //NOI18N
            artifactId.setDisplayName(org.openide.util.NbBundle.getMessage(DependencyNode.class, "PROP_Artifact"));
            artifactId.setShortDescription(""); //NOI18N
            PropertySupport.Reflection groupId = new PropertySupport.Reflection<String>(data.art, String.class, "getGroupId", null); //NOI18N
            groupId.setName("groupId"); //NOI18N
            groupId.setDisplayName(org.openide.util.NbBundle.getMessage(DependencyNode.class, "PROP_Group"));
            groupId.setShortDescription(""); //NOI18N
            PropertySupport.Reflection version = new PropertySupport.Reflection<String>(data.art, String.class, "getVersion", null); //NOI18N
            version.setName("version"); //NOI18N
            version.setDisplayName(org.openide.util.NbBundle.getMessage(DependencyNode.class, "PROP_Version"));
            version.setShortDescription(org.openide.util.NbBundle.getMessage(DependencyNode.class, "HINT_Version"));
            PropertySupport.Reflection type = new PropertySupport.Reflection<String>(data.art, String.class, "getType", null); //NOI18N
            type.setName("type"); //NOI18N
            type.setDisplayName(org.openide.util.NbBundle.getMessage(DependencyNode.class, "PROP_Type"));
            PropertySupport.Reflection scope = new PropertySupport.Reflection<String>(data.art, String.class, "getScope", null); //NOI18N
            scope.setName("scope"); //NOI18N
            scope.setDisplayName(org.openide.util.NbBundle.getMessage(DependencyNode.class, "PROP_Scope"));
            PropertySupport.Reflection classifier = new PropertySupport.Reflection<String>(data.art, String.class, "getClassifier", null); //NOI18N
            classifier.setName("classifier"); //NOI18N
            classifier.setDisplayName(org.openide.util.NbBundle.getMessage(DependencyNode.class, "PROP_Classifier"));
            PropertySupport.Reflection hasJavadoc = new PropertySupport.Reflection<Boolean>(data, Boolean.TYPE, "hasJavadocInRepository", null); //NOI18N
            hasJavadoc.setName("javadoc"); //NOI18N
            hasJavadoc.setDisplayName(org.openide.util.NbBundle.getMessage(DependencyNode.class, "PROP_Javadoc_Locally"));
            PropertySupport.Reflection hasSources = new PropertySupport.Reflection<Boolean>(data, Boolean.TYPE, "hasSourceInRepository", null); //NOI18N
            hasSources.setName("sources"); //NOI18N
            hasSources.setDisplayName(org.openide.util.NbBundle.getMessage(DependencyNode.class, "PROP_Sources_Locally"));
            PropertySupport.Reflection transitive = new PropertySupport.Reflection<Boolean>(data, Boolean.TYPE, "isTransitive", null); //NOI18N
            transitive.setName("transitive"); //NOI18N
            transitive.setDisplayName(org.openide.util.NbBundle.getMessage(DependencyNode.class, "PROP_Transitive"));
            
            PropertySupport.Reflection path = new PropertySupport.Reflection<File>(data.art, File.class, "getFile", null); //NOI18N
            path.setName("path"); //NOI18N
            path.setDisplayName("Path");
            path.setShortDescription("Absolute path to the artifact in the local filesystem.");
            
            PropertySupport.Reflection repositorypath = new PropertySupport.Reflection<String>(data, String.class, "getArtifactRepositoryPath", null); //NOI18N
            repositorypath.setName("repositorypath"); //NOI18N
            repositorypath.setDisplayName("Repository Path");
            repositorypath.setShortDescription("Relative path within the local maven repository.");
            

            basicProps.put(new Node.Property[] {
                artifactId, groupId, version, type, scope, classifier, transitive, hasJavadoc, hasSources, path, repositorypath
            });
        } catch (NoSuchMethodException exc) {
            exc.printStackTrace();
        }
        return sheet;
    }
    
    
    
//    private class DownloadJavadocAndSourcesAction extends AbstractAction implements Runnable {
//        public DownloadJavadocAndSourcesAction() {
//            putValue(Action.NAME, "Download Javadoc & Source");
//        }
//
//        public void actionPerformed(ActionEvent event) {
//            RP.post(this);
//        }
//
//        public void run() {
//            ProgressContributor contrib = AggregateProgressFactory.createProgressContributor("single"); //NOI18N
//            AggregateProgressHandle handle = AggregateProgressFactory.createHandle("Download Javadoc and Sources", new ProgressContributor[] {contrib}, null, null);
//            handle.start();
//            downloadJavadocSources(EmbedderFactory.getOnlineEmbedder(), contrib);
//            handle.finish();
//        }
//    }

    //extract the model out of the node class
    public static class Data {

        private Artifact art;
        private Project project;
        private java.util.concurrent.atomic.AtomicReference<FileObject> fileObject;
        private final java.util.concurrent.atomic.AtomicBoolean sourceExists = new AtomicBoolean(false);
        private final java.util.concurrent.atomic.AtomicBoolean javadocExists = new AtomicBoolean(false);
        private DependencyNode node;

        public Data(Artifact art, Project project, FileObject fileObject) {
            this.art = art;
            this.project = project;
            this.fileObject = new AtomicReference<FileObject>(fileObject);
        }

        public String getArtifactRepositoryPath() {
            return EmbedderFactory.getProjectEmbedder().getLocalRepository().pathOf(art);
        }
        
        public boolean isLocal() {
            FileObject fo = fileObject.get();
            return fo != null && fo.isValid() && !NbArtifactFixer.isFallbackFile(art.getFile());
        }

        public boolean hasJavadocInRepository() {
            return javadocExists.get() && (!Artifact.SCOPE_SYSTEM.equals(art.getScope()));
        }

        //normalized
        public File getJavadocFile() {
            File artifact = art.getFile();
            String version = artifact.getParentFile().getName();
            String artifactId = artifact.getParentFile().getParentFile().getName();
            return new File(artifact.getParentFile(), artifactId + "-" + version + (art.getClassifier() != null ? ("tests".equals(art.getClassifier()) ? "-test" : "-" + art.getClassifier()) : "") + "-javadoc.jar"); //NOI18N
        }

        //normalized
        public File getSourceFile() {
            File artifact = art.getFile();
            String version = artifact.getParentFile().getName();
            String artifactId = artifact.getParentFile().getParentFile().getName();
            return new File(artifact.getParentFile(), artifactId + "-" + version + (art.getClassifier() != null ? ("tests".equals(art.getClassifier()) ? "-test" : "-" + art.getClassifier()) : "") + "-sources.jar"); //NOI18N
        }

        public boolean hasSourceInRepository() {
            return sourceExists.get() && (!Artifact.SCOPE_SYSTEM.equals(art.getScope()));
        }
        
        MavenProject getMavenProject() {
            return project.getLookup().lookup(NbMavenProject.class).getMavenProject();
        }

        /**
         * public because of the property sheet
         */
        public boolean isTransitive() {
            List trail = art.getDependencyTrail();
            return trail != null && trail.size() > 2;
        }

        public boolean isManaged() {
            DependencyManagement dm = project.getLookup().lookup(NbMavenProject.class).getMavenProject().getDependencyManagement();
            if (dm != null) {
                List<Dependency> dmList = dm.getDependencies();
                for (Dependency d : dmList) {
                    if (art.getGroupId().equals(d.getGroupId())
                            && art.getArtifactId().equals(d.getArtifactId())) {
                        return true;
                    }
                }
            }
            return false;
        }

        private boolean isAddedToCP() {
            return art.getArtifactHandler().isAddedToClasspath();
        }

        /**
         * this call is slow
         *
         * @return
         */
        private boolean isDependencyProjectAvailable() {
            return getDependencyProject() != null;
        }

        /**
         * this call is slow
         *
         * @return
         */
        private Project getDependencyProject() {
            if (Artifact.SCOPE_SYSTEM.equals(art.getScope())) {
                return null;
            }
            URI uri = org.openide.util.Utilities.toURI(art.getFile());
            return FileOwnerQuery.getOwner(uri);
        }

        private void setNode(DependencyNode aThis) {
            node = aThis;
        }

        public DependencyNode getNode() {
            return node;
        }
        
    }

    
    //why oh why do we have to suffer through this??
    private static final RemoveDependencyAction REMOVEDEPINSTANCE = new RemoveDependencyAction(Lookup.EMPTY);

    @Messages("BTN_Remove_Dependency=Remove Dependency")
    private static class RemoveDependencyAction extends AbstractAction implements ContextAwareAction {

        private final Lookup lkp;

        RemoveDependencyAction(Lookup look) {
            putValue(Action.NAME, BTN_Remove_Dependency());
            lkp = look;
            Collection<? extends NbMavenProjectImpl> res = lkp.lookupAll(NbMavenProjectImpl.class);
            Set<NbMavenProjectImpl> prjs = new HashSet<NbMavenProjectImpl>(res);
            if (prjs.size() != 1) {
                setEnabled(false);
            }
        }

        @Override
        public Action createContextAwareInstance(Lookup actionContext) {
            return new RemoveDependencyAction(actionContext);
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            final Collection<? extends Artifact> artifacts = lkp.lookupAll(Artifact.class);
            if (artifacts.isEmpty()) {
                return;
            }
            Collection<? extends NbMavenProjectImpl> res = lkp.lookupAll(NbMavenProjectImpl.class);
            Set<NbMavenProjectImpl> prjs = new HashSet<NbMavenProjectImpl>(res);
            if (prjs.size() != 1) {
                return;
            }

            final NbMavenProjectImpl project = prjs.iterator().next();
            final List<Artifact> unremoved = new ArrayList<Artifact>();

            final ModelOperation<POMModel> operation = new ModelOperation<POMModel>() {
                @Override
                public void performOperation(POMModel model) {
                    for (Artifact art : artifacts) {
                        org.netbeans.modules.maven.model.pom.Dependency dep =
                                model.getProject().findDependencyById(art.getGroupId(), art.getArtifactId(), null);
                        if (dep != null) {
                            model.getProject().removeDependency(dep);
                        } else {
                            unremoved.add(art);
                        }
                    }
                }
            };
            RP.post(new Runnable() {
                @Override
                public void run() {
                    FileObject fo = FileUtil.toFileObject(project.getPOMFile());
                    Utilities.performPOMModelOperations(fo, Collections.singletonList(operation));
                    if (unremoved.size() > 0) {
                        StatusDisplayer.getDefault().setStatusText(NbBundle.getMessage(DependencyNode.class, "MSG_Located_In_Parent", unremoved.size()), Integer.MAX_VALUE);
                    }
                }
            });
        }
    }

    private static final String SHOW_MANAGED_DEPENDENCIES = "show.managed.dependencies";

    private static boolean showManagedState() {
        return DependenciesNode.prefs().getBoolean(SHOW_MANAGED_DEPENDENCIES, false);
    }

    @Override public void preferenceChange(PreferenceChangeEvent evt) {
        if (evt.getKey().equals(SHOW_MANAGED_DEPENDENCIES)) {
            refreshNode();
        }
    }

    static class ShowManagedStateAction extends AbstractAction implements Presenter.Popup {

        @NbBundle.Messages("LBL_ShowManagedState=Show Managed State for dependencies")
        ShowManagedStateAction() {
            String s = LBL_ShowManagedState();
            putValue(Action.NAME, s);
        }

        @Override public void actionPerformed(ActionEvent e) {
            DependenciesNode.prefs().putBoolean(SHOW_MANAGED_DEPENDENCIES, !showManagedState());
        }

        @Override public JMenuItem getPopupPresenter() {
            JCheckBoxMenuItem mi = new JCheckBoxMenuItem(this);
            mi.setSelected(showManagedState());
            return mi;
        }

    }
    @Messages({"BTN_Exclude_Dependency=Exclude Dependency",
              "TIT_Exclude=Exclude Transitive Dependency"})
    private class ExcludeTransitiveAction extends AbstractAction {

        public ExcludeTransitiveAction() {
            putValue(Action.NAME, BTN_Exclude_Dependency());
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            RP.post(new Runnable() {
                @Override
                public void run() {
                    org.apache.maven.shared.dependency.tree.DependencyNode rootnode = DependencyTreeFactory.createDependencyTree(data.getMavenProject(), EmbedderFactory.getOnlineEmbedder(), Artifact.SCOPE_TEST);
                    DependencyExcludeNodeVisitor nv = new DependencyExcludeNodeVisitor(data.art.getGroupId(), data.art.getArtifactId(), data.art.getType());
                    rootnode.accept(nv);
                    final Set<org.apache.maven.shared.dependency.tree.DependencyNode> nds = nv.getDirectDependencies();
                    Collection<org.apache.maven.shared.dependency.tree.DependencyNode> directs;
                    if (nds.size() > 1) {
                        final ExcludeDependencyPanel pnl = new ExcludeDependencyPanel(data.getMavenProject(), data.art, nds, rootnode);
                        DialogDescriptor dd = new DialogDescriptor(pnl, TIT_Exclude());
                        Object ret = DialogDisplayer.getDefault().notify(dd);
                        if (ret == DialogDescriptor.OK_OPTION) {
                            directs = pnl.getDependencyExcludes().get(data.art);
                        } else {
                            return;
                        }
                    } else {
                        directs = nds;
                    }
                    runModifyExclusions(data.art, directs);
                }
            });
        }

        private void runModifyExclusions(final Artifact art, final Collection<org.apache.maven.shared.dependency.tree.DependencyNode> nds) {
            ModelOperation<POMModel> operation = new ModelOperation<POMModel>() {
                @Override
                public void performOperation(POMModel model) {
                    for (org.apache.maven.shared.dependency.tree.DependencyNode nd : nds) {
                        Artifact directArt = nd.getArtifact();
                        org.netbeans.modules.maven.model.pom.Dependency dep = model.getProject().findDependencyById(directArt.getGroupId(), directArt.getArtifactId(), null);
                        if (dep == null) {
                            // now check the active profiles for the dependency..
                            List<String> profileNames = new ArrayList<String>();
                            Iterator it = data.getMavenProject().getActiveProfiles().iterator();
                            while (it.hasNext()) {
                                Profile prof = (Profile) it.next();
                                profileNames.add(prof.getId());
                            }
                            for (String profileId : profileNames) {
                                org.netbeans.modules.maven.model.pom.Profile modProf = model.getProject().findProfileById(profileId);
                                if (modProf != null) {
                                    dep = modProf.findDependencyById(directArt.getGroupId(), directArt.getArtifactId(), null);
                                    if (dep != null) {
                                        break;
                                    }
                                }
                            }
                        }
                        if (dep == null) {
                            // relocation maybe?
                            List<org.netbeans.modules.maven.model.pom.Dependency> deps = model.getProject().getDependencies();
                            for (org.netbeans.modules.maven.model.pom.Dependency d : deps) {
                                Relocation rel = getRelocation(d);
                                if(rel != null && 
                                   rel.getArtifactId().equals(directArt.getArtifactId()) &&
                                   rel.getGroupId().equals(directArt.getGroupId()) &&
                                   rel.getVersion().equals(directArt.getVersion())) 
                                {
                                    dep = d;
                                    break;    
                                }
                            }                            
                        }                        
                        if (dep == null) {
                            dep = model.getFactory().createDependency();
                            dep.setGroupId(directArt.getGroupId());
                            dep.setArtifactId(directArt.getArtifactId());
                            dep.setVersion(directArt.getVersion());
                            if (!"jar".equals(directArt.getType())) {
                                dep.setType(directArt.getType());
                            }
                            model.getProject().addDependency(dep);
                            //mkleint: TODO why is the dependency being added? i forgot already..
                        }
                        Exclusion ex = dep.findExclusionById(art.getGroupId(), art.getArtifactId());
                        if (ex == null) {
                            Exclusion exclude = model.getFactory().createExclusion();
                            exclude.setGroupId(art.getGroupId());
                            exclude.setArtifactId(art.getArtifactId());
                            dep.addExclusion(exclude);
                        }
                    }
                }
            };
            FileObject fo = FileUtil.toFileObject(data.project.getLookup().lookup(NbMavenProjectImpl.class).getPOMFile());
            org.netbeans.modules.maven.model.Utilities.performPOMModelOperations(fo, Collections.singletonList(operation));
        }
    }
    
    private Relocation getRelocation(org.netbeans.modules.maven.model.pom.Dependency d) {
        ProjectBuildingRequest dpbr = EmbedderFactory.getProjectEmbedder().createMavenExecutionRequest().getProjectBuildingRequest();
        dpbr.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL);
        dpbr.setProcessPlugins(false);
        dpbr.setResolveDependencies(false);
        ArrayList<ArtifactRepository> remoteRepos = new ArrayList<>();
        dpbr.setRemoteRepositories(remoteRepos);
        String groupId = d.getGroupId();
        String artifactId = d.getArtifactId();
        String version = d.getVersion();
        if(groupId != null && !"".equals(groupId.trim()) &&
           artifactId != null && !"".equals(artifactId.trim()) &&
           version != null && !"".equals(version.trim())) 
        {           
            MavenEmbedder embedder = EmbedderFactory.getProjectEmbedder();
            Artifact a = embedder.createProjectArtifact(groupId, artifactId, version);
            try {
                ProjectBuildingResult r = embedder.buildProject(a, dpbr);
                DistributionManagement dm = r.getProject().getDistributionManagement();
                return dm != null ? dm.getRelocation() : null;
            } catch (ProjectBuildingException ex) {
                // just log and hope for the best ...
                Logger.getLogger(DependencyNode.class.getName()).log(Level.INFO, version, ex);                
            }
        }
        return null;
    }    
    
    private static Action DOWNLOAD_JAVADOC_ACTION = new DownloadJavadocSrcAction(true);
    private static Action DOWNLOAD_SOURCE_ACTION = new DownloadJavadocSrcAction(false);
    
    
    @SuppressWarnings("serial")
    private static class DownloadJavadocSrcAction extends AbstractAction implements ContextAwareAction {
        private final boolean javadoc;
        private final Lookup actionContext;
        public DownloadJavadocSrcAction(boolean javadoc) {
            this(javadoc, Lookup.EMPTY);
        }

        private DownloadJavadocSrcAction(boolean javadoc, Lookup actionContext) {
            putValue(Action.NAME, javadoc ? LBL_Download_Javadoc() : LBL_Download__Sources());
            this.javadoc = javadoc;
            this.actionContext = actionContext;
        }

        @Override
        public Action createContextAwareInstance(Lookup actionContext) {
            
            return new DownloadJavadocSrcAction(javadoc, actionContext);
        }       
        
        @Override
        public void actionPerformed(ActionEvent evnt) {
            if (actionContext == null) {
                return;
            }
            RP.post(new Runnable() {
                public @Override void run() {
                    ProgressContributor contributor =AggregateProgressFactory.createProgressContributor("multi-1");
                   
                    String label = javadoc ? Progress_Javadoc() : Progress_Source();
                    AggregateProgressHandle handle = AggregateProgressFactory.createHandle(label, 
                            new ProgressContributor [] {contributor}, ProgressTransferListener.cancellable(), null);
                    handle.start();
                    try {
                        ProgressTransferListener.setAggregateHandle(handle);
                        for (Data data : actionContext.lookupAll(Data.class)) {
                            ProgressContributor contributor2 = AggregateProgressFactory.createProgressContributor("multi-1");
                            handle.addContributor(contributor2);
                            if (javadoc && !data.hasJavadocInRepository()) {
                                data.getNode().downloadJavadocSources(contributor2, javadoc);
                            } else if (!javadoc && !data.hasSourceInRepository()) {
                                data.getNode().downloadJavadocSources(contributor2, javadoc);
                            } else {
                                contributor2.finish();
                            }
                        }
                        
                    } catch (ThreadDeath d) { // download interrupted
                    } catch (IllegalStateException ise) { //download interrupted in dependent thread. #213812
                        if (!(ise.getCause() instanceof ThreadDeath)) {
                            throw ise;
                        }
                    } finally {
                        handle.finish();
                        ProgressTransferListener.clearAggregateHandle();
                    }
                }
            });
        }
    } 
    
    //why oh why do we have to suffer through this??
    private static final SetInCurrentAction SETINCURRENTINSTANCE = new SetInCurrentAction(Lookup.EMPTY);

    @Messages("BTN_Set_Dependency=Declare as Direct Dependency")
    private static class SetInCurrentAction extends AbstractAction  implements ContextAwareAction {
        private final Lookup lkp;

        SetInCurrentAction(Lookup lookup) {
            putValue(Action.NAME, BTN_Set_Dependency());
            lkp = lookup;
            Collection<? extends NbMavenProjectImpl> res = lkp.lookupAll(NbMavenProjectImpl.class);
            Set<NbMavenProjectImpl> prjs = new HashSet<NbMavenProjectImpl>(res);
            if (prjs.size() != 1) {
                setEnabled(false);
            }
        }
        
        @Override
        public Action createContextAwareInstance(Lookup actionContext) {
            return new SetInCurrentAction(actionContext);
        }


        @Override
        public void actionPerformed(ActionEvent event) {
            final Collection<? extends Artifact> artifacts = lkp.lookupAll(Artifact.class);
            if (artifacts.isEmpty()) {
                return;
            }
            Collection<? extends NbMavenProjectImpl> res = lkp.lookupAll(NbMavenProjectImpl.class);
            Set<NbMavenProjectImpl> prjs = new HashSet<NbMavenProjectImpl>(res);
            if (prjs.size() != 1) {
                return;
            }
            final NbMavenProjectImpl project = prjs.iterator().next();

            final ModelOperation<POMModel> operation = new ModelOperation<POMModel>() {
                @Override
                public void performOperation(POMModel model) {
                    for (Artifact art : artifacts) {
                        org.netbeans.modules.maven.model.pom.Dependency dep = model.getProject().findDependencyById(art.getGroupId(), art.getArtifactId(), null);
                        if (dep == null) {
                            // now check the active profiles for the dependency..
                            List<String> profileNames = new ArrayList<String>();
                            Iterator it = project.getOriginalMavenProject().getActiveProfiles().iterator();
                            while (it.hasNext()) {
                                Profile prof = (Profile) it.next();
                                profileNames.add(prof.getId());
                            }
                            for (String profileId : profileNames) {
                                org.netbeans.modules.maven.model.pom.Profile modProf = model.getProject().findProfileById(profileId);
                                if (modProf != null) {
                                    dep = modProf.findDependencyById(art.getGroupId(), art.getArtifactId(), null);
                                    if (dep != null) {
                                        break;
                                    }
                                }
                            }
                        }
                        if (dep == null) {
                            dep = model.getFactory().createDependency();
                            dep.setGroupId(art.getGroupId());
                            dep.setArtifactId(art.getArtifactId());
                            dep.setVersion(art.getVersion());
                            if (!"jar".equals(art.getType())) {
                                dep.setType(art.getType());
                            }
                            if (!Artifact.SCOPE_COMPILE.equals(art.getScope())) {
                                dep.setScope(art.getScope());
                            }
                            if (art.getClassifier() != null) {
                                dep.setClassifier(art.getClassifier());
                            }
                            model.getProject().addDependency(dep);
                        }
                    }
                }
            };
            RP.post(new Runnable() {
                @Override
                public void run() {
                    FileObject fo = FileUtil.toFileObject(project.getPOMFile());
                    org.netbeans.modules.maven.model.Utilities.performPOMModelOperations(fo, Collections.singletonList(operation));
                }
            });
        }

    }


    @Messages("BTN_Manually_install=Manually install artifact")
    private class InstallLocalArtifactAction extends AbstractAction {

        public InstallLocalArtifactAction() {
            putValue(Action.NAME, BTN_Manually_install());
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            File fil = InstallPanel.showInstallDialog(data.art);
            if (fil != null) {
                InstallPanel.runInstallGoal(data.project.getLookup().lookup(NbMavenProjectImpl.class), fil, data.art);
            }
        }
    }

    private class CopyLocationAction extends AbstractAction {

        @Messages("CopyLocationAction.name=Copy Location")
        CopyLocationAction() {
            super(CopyLocationAction_name());
        }

        @Override public void actionPerformed(ActionEvent e) {
            Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(data.art.getFile().getAbsolutePath()), null);
        }

    }

    @Messages("BTN_Add_javadoc=Add local Javadoc")
    private class InstallLocalJavadocAction extends AbstractAction implements Runnable {

        private File source;

        public InstallLocalJavadocAction() {
            putValue(Action.NAME, BTN_Add_javadoc());
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            source = InstallDocSourcePanel.showInstallDialog(true);
            if (source != null) {
                RP.post(this);
            }
        }

        @Override
        public void run() {
            File target = data.getJavadocFile();
            try {
                FileUtils.copyFile(source, target);
            } catch (IOException ex) {
                ex.printStackTrace();
                target.delete();
            }
            refreshNode();
        }
    }

    @Messages("BTN_Add_sources=Add local sources")
    private class InstallLocalSourcesAction extends AbstractAction implements Runnable {

        private File source;

        public InstallLocalSourcesAction() {
            putValue(Action.NAME, BTN_Add_sources());
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            source = InstallDocSourcePanel.showInstallDialog(false);
            if (source != null) {
                RP.post(this);
            }
        }

        @Override
        public void run() {
            File target = data.getSourceFile();
            try {
                FileUtils.copyFile(source, target);
            } catch (IOException ex) {
                ex.printStackTrace();
                target.delete();
            }
            refreshNode();
        }
    }

//    private class EditAction extends AbstractAction {
//        public EditAction() {
//            putValue(Action.NAME, "Edit...");
//        }
//
//        public void actionPerformed(ActionEvent event) {
//
//            DependencyEditor ed = new DependencyEditor(project, change);
//            DialogDescriptor dd = new DialogDescriptor(ed, "Edit Dependency");
//            Object ret = DialogDisplayer.getDefault().notify(dd);
//            if (ret == NotifyDescriptor.OK_OPTION) {
//                HashMap props = ed.getProperties();
//                MavenSettings.getDefault().checkDependencyProperties(props.keySet());
//                change.setNewValues(ed.getValues(), props);
//                try {
//                    NbProjectWriter writer = new NbProjectWriter(project);
//                    List changes = (List)getLookup().lookup(List.class);
//                    writer.applyChanges(changes);
//                } catch (Exception exc) {
//                    ErrorManager.getDefault().notify(ErrorManager.USER, exc);
//                }
//            }
//        }
//    }
//
    

    private static class ArtifactSourceGroup implements SourceGroup {

        private final Artifact art;

        public ArtifactSourceGroup(Artifact art) {
            this.art = art;
        }

        //art.getFile() should be normalized
        @Override
        public FileObject getRootFolder() {
            FileObject fo = FileUtil.toFileObject(art.getFile());
            if (fo != null) {
                return FileUtil.getArchiveRoot(fo);
            }
            return null;
        }

        @Override
        public String getName() {
            return art.getId();
        }

        @Override
        public String getDisplayName() {
            return art.getId();
        }

        @Override
        public Icon getIcon(boolean opened) {
            return null;
        }

        @Override public boolean contains(FileObject file) {
            return true;
        }

        @Override
        public void addPropertyChangeListener(PropertyChangeListener listener) {
        }

        @Override
        public void removePropertyChangeListener(PropertyChangeListener listener) {
        }
    }

    private static class JarContentFilterChildren extends FilterNode.Children {

        JarContentFilterChildren(Node orig) {
            super(orig);
        }

        @Override
        protected Node copyNode(Node node) {
            return new JarFilterNode(node);
        }
    }

    private static class JarFilterNode extends FilterNode {

        JarFilterNode(Node original) {
            super(original, Children.LEAF == original.getChildren() ?
                            Children.LEAF : new JarContentFilterChildren(original));
        }

        @Override
        public Action[] getActions(boolean context) {
            List<Action> result = new ArrayList<Action>();
            result.addAll(Arrays.asList(super.getActions(false)));
            result.add(new OpenJavadocAction());

            return result.toArray(new Action[result.size()]);
        }

        @Messages("BTN_View_Javadoc=Show Javadoc")
        private class OpenJavadocAction extends AbstractAction {

            private OpenJavadocAction() {
                putValue(Action.NAME, BTN_View_Javadoc());
            }

            @Override
            @Messages({
                "# {0} - artifact path",
                "ERR_No_Javadoc_Found=Javadoc for {0} not found."})
            public void actionPerformed(ActionEvent e) {
                DataObject dobj = getOriginal().getLookup().lookup(DataObject.class);
                if (dobj == null) {
                    return;
                }
                FileObject fil = dobj.getPrimaryFile();
                FileObject jar = FileUtil.getArchiveFile(fil);
                FileObject root = FileUtil.getArchiveRoot(jar);
                String rel = FileUtil.getRelativePath(root, fil);
                rel = rel.replaceAll("[.]class$", ".html"); //NOI18N
                JavadocForBinaryQuery.Result res = JavadocForBinaryQuery.findJavadoc(root.toURL());
                if (fil.isFolder()) {
                    rel = rel + "/package-summary.html"; //NOI18N
                }
                URL javadocUrl = findJavadoc(rel, res.getRoots());
                if (javadocUrl != null) {
                    HtmlBrowser.URLDisplayer.getDefault().showURL(javadocUrl);
                } else {
                    StatusDisplayer.getDefault().setStatusText(ERR_No_Javadoc_Found(fil.getPath()));
                }
            }

            /**
             * Locates a javadoc page by a relative name and an array of javadoc roots
             * @param resource the relative name of javadoc page
             * @param urls the array of javadoc roots
             * @return the URL of found javadoc page or null if there is no such a page.
             */
            URL findJavadoc(String resource, URL[] urls) {
                for (int i = 0; i < urls.length; i++) {
                    String base = urls[i].toExternalForm();
                    if (!base.endsWith("/")) { // NOI18N
                        base = base + "/"; // NOI18N
                    }
                    try {
                        URL u = new URL(base + resource);
                        FileObject fo = URLMapper.findFileObject(u);
                        if (fo != null) {
                            return u;
                        }
                    } catch (MalformedURLException ex) {
//                            ErrorManager.getDefault().log(ErrorManager.ERROR, "Cannot create URL for "+base+resource+". "+ex.toString());   //NOI18N
                        continue;
                    }
                }
                return null;
            }

        }
    }

    private static class OpenProjectAction extends AbstractAction implements ContextAwareAction {

        static final OpenProjectAction SINGLETON = new OpenProjectAction();

        private OpenProjectAction() {}

        public @Override void actionPerformed(ActionEvent e) {
            assert false;
        }

        public @Override Action createContextAwareInstance(final Lookup context) {
            return new AbstractAction(BTN_Open_Project()) {
                public @Override void actionPerformed(ActionEvent e) {
                    Set<Project> projects = new HashSet<Project>();
                    for (Artifact art : context.lookupAll(Artifact.class)) {
                        File f = art.getFile();
                        if (f != null) {
                            Project p = FileOwnerQuery.getOwner(org.openide.util.Utilities.toURI(f));
                            if (p != null) {
                                projects.add(p);
                            }
                        }
                    }
                    OpenProjects.getDefault().open(projects.toArray(new NbMavenProjectImpl[projects.size()]), false, true);
                }
            };
        }
    }

}
