/* * Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC * All rights reserved. * * The source code of this document is proprietary work, and is not licensed for * distribution. For information about licensing, contact Sam Harwell at: * sam@tunnelvisionlabs.com */ package org.tvl.goworks.project; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import javax.swing.Icon; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectManager; import org.netbeans.api.project.SourceGroup; import org.netbeans.api.project.Sources; import org.netbeans.api.queries.SharabilityQuery; import org.netbeans.spi.project.support.GenericSources; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.util.ChangeSupport; import org.openide.util.Mutex; /** * * @author Sam Harwell */ final class GoSourcesImpl implements Sources, ChangeListener { /** * Go package root sources type. * @see org.netbeans.api.project.Sources */ public static final String SOURCES_TYPE_GO = "go"; // NOI18N private final GoProject _project; private final ChangeSupport _changeSupport = new ChangeSupport(this); private boolean _dirty; private final Map<String,SourceGroup[]> _cachedGroups = new ConcurrentHashMap<>(); private long _eventId; private final FireAction _fireTask = new FireAction(); public GoSourcesImpl(GoProject project) { this._project = project; } @Override public SourceGroup[] getSourceGroups(final String type) { final SourceGroup[] cachedGroups = _cachedGroups.get(type); if (cachedGroups != null) { return cachedGroups; } return ProjectManager.mutex().readAccess(new Mutex.Action<SourceGroup[]>() { @Override public SourceGroup[] run() { SourceGroup[] groups; switch (type) { case GoSourcesImpl.SOURCES_TYPE_GO: final FileObject sourcesDirectory = _project.getSourceRoot(); if (sourcesDirectory != null) { SourceGroup group = new SourceGroup() { final PropertyChangeSupport pcs = new PropertyChangeSupport(this); @Override public FileObject getRootFolder() { return sourcesDirectory; } @Override public String getName() { return sourcesDirectory.getName(); } @Override public String getDisplayName() { return sourcesDirectory.getName(); } @Override public Icon getIcon(boolean opened) { return null; // return opened ? icon : openedIcon; } @Override public boolean contains(FileObject file) { if (file == sourcesDirectory) { return true; } String path = FileUtil.getRelativePath(sourcesDirectory, file); if (path == null) { return false; } if (file.isFolder()) { path += "/"; // NOI18N } Project p = _project; if (file.isFolder() && file != p.getProjectDirectory() && ProjectManager.getDefault().isProject(file)) { // #67450: avoid actually loading the nested project. return false; } // XXX disabled for typed source roots; difficult to make fast (#97215) Project owner = FileOwnerQuery.getOwner(file); if (owner != null && owner != p) { return false; } if (SharabilityQuery.getSharability(file) == SharabilityQuery.Sharability.NOT_SHARABLE) { return false; } // else MIXED, UNKNOWN, or SHARABLE return true; } @Override public void addPropertyChangeListener(PropertyChangeListener listener) { pcs.addPropertyChangeListener(listener); } @Override public void removePropertyChangeListener(PropertyChangeListener listener) { pcs.removePropertyChangeListener(listener); } }; groups = new SourceGroup[] { group }; } else { groups = new SourceGroup[0]; } break; case Sources.TYPE_GENERIC: groups = new SourceGroup[] { GenericSources.group(_project, _project.getProjectDirectory(), "go-generic", "Go", null, null) }; break; default: groups = new SourceGroup[0]; break; } long myEventId; synchronized (GoSourcesImpl.this) { if (_dirty) { _dirty = false; } myEventId = ++_eventId; } synchronized (GoSourcesImpl.this) { if (myEventId == _eventId) { GoSourcesImpl.this._cachedGroups.put(type, groups); } } return groups; } }); } @Override public void addChangeListener(ChangeListener listener) { _changeSupport.addChangeListener(listener); } @Override public void removeChangeListener(ChangeListener listener) { _changeSupport.removeChangeListener(listener); } private void fireChange() { synchronized (this) { _cachedGroups.clear(); //threading: CHM.clear is not atomic, the getSourceGroup may return staled data which is not a problem in this case. _dirty = true; } ProjectManager.mutex().postReadRequest(_fireTask.activate()); } @Override public void stateChanged(ChangeEvent e) { this.fireChange(); } private class FireAction implements Runnable { private AtomicBoolean fire = new AtomicBoolean(); @Override public void run() { if (fire.getAndSet(false)) { _changeSupport.fireChange(); } } FireAction activate() { this.fire.set(true); return this; } } }