/******************************************************************************* * Copyright (c) 2015 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.dash.model; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.Platform; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.jdt.core.IJavaProject; import org.springframework.ide.eclipse.boot.core.BootPropertyTester; import org.springframework.ide.eclipse.boot.dash.livexp.LiveSets; import org.springframework.ide.eclipse.boot.dash.metadata.IPropertyStore; import org.springframework.ide.eclipse.boot.dash.metadata.IScopedPropertyStore; import org.springframework.ide.eclipse.boot.dash.metadata.PropertyStoreFactory; import org.springframework.ide.eclipse.boot.dash.model.BootDashModel.ElementStateListener; import org.springframework.ide.eclipse.boot.dash.util.CollectionUtils; import org.springframework.ide.eclipse.boot.dash.util.DebugUtil; import org.springframework.ide.eclipse.boot.launch.util.BootLaunchUtils; import org.springframework.ide.eclipse.boot.util.Log; import org.springsource.ide.eclipse.commons.frameworks.core.workspace.ClasspathListenerManager; import org.springsource.ide.eclipse.commons.frameworks.core.workspace.ClasspathListenerManager.ClasspathListener; import org.springsource.ide.eclipse.commons.livexp.core.DisposeListener; import org.springsource.ide.eclipse.commons.livexp.core.LiveExpression; import org.springsource.ide.eclipse.commons.livexp.core.ObservableSet; import org.springsource.ide.eclipse.commons.livexp.core.ValueListener; import org.springsource.ide.eclipse.commons.livexp.ui.Disposable; import com.google.common.base.Function; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; /** * Concrete BootDashElement that wraps an IProject * * @author Kris De Volder */ public class BootProjectDashElement extends AbstractLaunchConfigurationsDashElement<IProject> { private static final boolean DEBUG = DebugUtil.isDevelopment(); private static void debug(String string) { if (DEBUG) { System.err.println(string); } } private IScopedPropertyStore<IProject> projectProperties; private LaunchConfDashElementFactory childFactory; private ObservableSet<BootDashElement> children; private ObservableSet<Integer> ports; private LiveExpression<Boolean> hasDevtools = null; public BootProjectDashElement(IProject project, LocalBootDashModel context, IScopedPropertyStore<IProject> projectProperties, BootProjectDashElementFactory factory, LaunchConfDashElementFactory childFactory) { super(context, project); this.projectProperties = projectProperties; this.childFactory = childFactory; if (DEBUG) { onDispose((e) -> { debug("disposed: "+this); }); } } @Override public boolean hasDevtools() { if (hasDevtools==null) { hasDevtools = new LiveExpression<Boolean>(false) { @Override protected Boolean compute() { boolean val = BootPropertyTester.hasDevtools(getProject()); return val; } }; hasDevtools.refresh(); ClasspathListenerManager classpathListener = new ClasspathListenerManager(new ClasspathListener() { public void classpathChanged(IJavaProject jp) { if (jp.getProject().equals(getProject())) { hasDevtools.refresh(); } } }); this.dependsOn(hasDevtools); this.addDisposableChild(classpathListener); } return hasDevtools.getValue(); } @Override public IProject getProject() { return delegate; } @Override public String getName() { return delegate.getName(); } @Override protected IPropertyStore createPropertyStore() { return PropertyStoreFactory.createForScope(delegate, projectProperties); } @Override public ImmutableSet<ILaunchConfiguration> getLaunchConfigs() { return getLaunchConfigsExp().getValues(); } @Override public int getLivePort() { return CollectionUtils.getAnyOr(getLivePorts(), -1); } @Override public ImmutableSet<Integer> getLivePorts() { return getLivePortsExp().getValues(); } private ObservableSet<Integer> getLivePortsExp() { if (ports==null) { ports = createSortedLiveSummary((BootDashElement element) -> { int port = element.getLivePort(); if (port>0) { return port; } return null; }); this.dependsOn(ports); } return ports; } @SuppressWarnings({ "rawtypes", "unchecked" }) private void dependsOn(LiveExpression<?> liveProperty) { liveProperty.addListener(new ValueListener() { public void gotValue(LiveExpression exp, Object value) { getBootDashModel().notifyElementChanged(BootProjectDashElement.this); } }); } /** * Creates a ObservableSet that is a 'summary' of some property of the children of this node. The summary * is a set of all the values of the given property on all the children. The set is sorted using the element's * natural ordering. */ private <T extends Comparable<T>> ObservableSet<T> createSortedLiveSummary(final Function<BootDashElement, T> getter) { final ObservableSet<T> summary = new ObservableSet<T>() { @Override protected ImmutableSet<T> compute() { debug("port-summary["+getName()+"]: compute()..."); ImmutableSet.Builder<T> builder = ImmutableSortedSet.naturalOrder(); for (BootDashElement child : getCurrentChildren()) { add(builder, child); } ImmutableSet<T> result = builder.build(); debug("port-summary["+getName()+"]: compute() => "+result); return result; } protected void add(ImmutableSet.Builder<T> builder, BootDashElement child) { debug("port-summary["+getName()+"]: add port for "+child); T v = getter.apply(child); debug("port-summary["+getName()+"]: add port for "+child+" = "+v); if (v!=null) { builder.add(v); } } }; final ElementStateListener elementListener = new ElementStateListener() { public void stateChanged(BootDashElement e) { summary.refresh(); } }; getBootDashModel().addElementStateListener(elementListener); summary.onDispose(new DisposeListener() { public void disposed(Disposable disposed) { getBootDashModel().removeElementStateListener(elementListener); } }); addDisposableChild(summary); return summary; } private ObservableSet<ILaunchConfiguration> getLaunchConfigsExp() { return getBootDashModel().launchConfTracker.getConfigs(delegate); } /** * All children including 'invisible ones' that may be hidden from the children returned * by getChildren. */ @Override public ObservableSet<BootDashElement> getChildren() { if (children==null) { children = LiveSets.mapSync(getBootDashModel().launchConfTracker.getConfigs(delegate), new Function<ILaunchConfiguration, BootDashElement>() { public BootDashElement apply(ILaunchConfiguration input) { return childFactory.createOrGet(input); } } ); children.addListener(new ValueListener<ImmutableSet<BootDashElement>>() { public void gotValue(LiveExpression<ImmutableSet<BootDashElement>> exp, ImmutableSet<BootDashElement> value) { getBootDashModel().notifyElementChanged(BootProjectDashElement.this); refreshRunState(); } }); addDisposableChild(children); } return this.children; } @Override public void refreshLivePorts() { for (BootDashElement child : getChildren().getValues()) { try { ((AbstractLaunchConfigurationsDashElement<?>)child).refreshLivePorts(); } catch (ClassCastException e) { //Should be impossible (unless something changes in how elements in dash are nested) Log.log(e); } } } @Override public ImmutableSet<ILaunch> getLaunches() { return ImmutableSet.copyOf(BootLaunchUtils.getLaunches(getLaunchConfigs())); } @Override public Object getParent() { return getBootDashModel(); } }