/******************************************************************************* * Copyright (c) 2015, 2016 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.views; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.core.resources.IProject; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.internal.ui.viewsupport.AppearanceAwareLabelProvider; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.IDecoration; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.StyledString.Styler; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.springframework.ide.eclipse.boot.core.BootPropertyTester; import org.springframework.ide.eclipse.boot.dash.BootDashActivator; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CloudAppDashElement; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CloudFoundryBootDashModel; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.CloudServiceInstanceDashElement; import org.springframework.ide.eclipse.boot.dash.cloudfoundry.DevtoolsUtil; import org.springframework.ide.eclipse.boot.dash.model.AbstractLaunchConfigurationsDashElement; import org.springframework.ide.eclipse.boot.dash.model.BootDashElement; import org.springframework.ide.eclipse.boot.dash.model.BootDashModel; import org.springframework.ide.eclipse.boot.dash.model.LocalCloudServiceDashElement; import org.springframework.ide.eclipse.boot.dash.model.RunState; import org.springframework.ide.eclipse.boot.dash.model.TagUtils; import org.springframework.ide.eclipse.boot.dash.ngrok.NGROKClient; import org.springframework.ide.eclipse.boot.dash.ngrok.NGROKLaunchTracker; import org.springframework.ide.eclipse.boot.dash.views.sections.BootDashColumn; import org.springframework.ide.eclipse.boot.util.Log; import org.springsource.ide.eclipse.commons.livexp.ui.Disposable; import org.springsource.ide.eclipse.commons.livexp.ui.Stylers; import com.google.common.collect.ImmutableSet; /** * Provides various methods for implementing various Label providers for the Boot Dash * and its related views, dialogs etc. * <p> * This is meant to be used as a 'delegate' object that different label provider * implementations can wrap and use rather than a direct implementation of * a particular label provider interface. * <p> * Instances of this class may allocate resources (e.g. images) * and must be disposed when they are not needed anymore. * * @author Alex Boyko * @author Kris De Volder */ @SuppressWarnings("restriction") public class BootDashLabels implements Disposable { private static final String UNKNOWN_LABEL = "???"; private static final Image[] NO_IMAGES = null; private AppearanceAwareLabelProvider javaLabels = null; private RunStateImages runStateImages = null; /** * TODO replace 'runStateImages' and this registry with a single registry * for working with both animatons & simple images. */ private ImageDecorator images = new ImageDecorator(); private Stylers stylers; /** * This constructor is deprecated. It produces something incapable of * properly styling some kinds of labels (e.g. those requiring the use * of a 'bold' font. Use the alternate constructor which * takes a {@link Stylers} argument. */ @Deprecated public BootDashLabels() { //Create slighly less-capable 'Stylers': this(new Stylers(null)); } public BootDashLabels(Stylers stylers) { this.stylers = stylers; } /////////////////////////////////////////////////////////////// //// Main apis that clients should use: @Override public void dispose() { if (javaLabels != null) { javaLabels.dispose(); } if (runStateImages!=null) { runStateImages.dispose(); runStateImages = null; } if (images!=null) { images.dispose(); images = null; } } public Image[] getImageAnimation(Object e, BootDashColumn forColum) { if (e instanceof BootDashElement) { return getImageAnimation((BootDashElement)e, forColum); } else if (e instanceof BootDashModel) { return getImageAnimation((BootDashModel)e, forColum); } return NO_IMAGES; } /** * For those who don't care about animations, fetches the first image * of the animation sequence only; or if the icon is non-animated just * the image. */ public final Image getImage(Object element, BootDashColumn column) { Image[] imgs = getImageAnimation(element, column); if (imgs!=null && imgs.length>0) { return imgs[0]; } return null; } public StyledString getStyledText(Object element, BootDashColumn column) { if (element instanceof BootDashElement) { return getStyledText((BootDashElement)element, column); } else if (element instanceof BootDashModel) { return getStyledText((BootDashModel)element, column); } return new StyledString(""+element); } /////////////////////////////////////////////////// // Type-specific apis below // // Some label providers may be only for specific types of elements and can use these // methods instead. public Image[] getImageAnimation(BootDashModel element, BootDashColumn column) { ImageDescriptor icon = getIcon(element); ImageDescriptor decoration = getDecoration(element); return toAnimation(icon, decoration); } private ImageDescriptor getIcon(BootDashModel element) { if (element instanceof CloudFoundryBootDashModel) { CloudFoundryBootDashModel cfModel = (CloudFoundryBootDashModel) element; if (cfModel.getRunTarget().isConnected()) { return BootDashActivator.getImageDescriptor("icons/cloud-ready.png"); } else { return BootDashActivator.getImageDescriptor("icons/cloud-inactive.png"); } } return element.getRunTarget().getType().getIcon(); } private ImageDescriptor getDecoration(BootDashModel element) { if (element.getRefreshState().isError()) { return BootDashActivator.getImageDescriptor("icons/error_ovr.gif"); } else if (element.getRefreshState().isWarning()) { return BootDashActivator.getImageDescriptor("icons/warning_ovr.gif"); } else if (element.getRefreshState().isLoading()) { return BootDashActivator.getImageDescriptor("icons/waiting_ovr.gif"); } return null; } private Image[] toAnimation(ImageDescriptor icon, ImageDescriptor decoration) { Image img = images.get(icon, decoration); return toAnimation(img); } private Image[] toAnimation(Image img) { if (img!=null) { return new Image[]{img}; } return NO_IMAGES; } public Image[] getImageAnimation(BootDashElement element, BootDashColumn column) { if (element instanceof CloudServiceInstanceDashElement) { ImageDescriptor img = BootDashActivator.getDefault().getImageRegistry().getDescriptor(BootDashActivator.SERVICE_ICON); return toAnimation(img, null); } else if (element instanceof LocalCloudServiceDashElement) { if (column == BootDashColumn.RUN_STATE_ICN || column == BootDashColumn.TREE_VIEWER_MAIN) { ImageDescriptor img; switch (element.getRunState()) { case RUNNING: img = BootDashActivator.getDefault().getImageRegistry().getDescriptor(BootDashActivator.SERVICE_ICON); return toAnimation(img, null); case STARTING: return getRunStateAnimation(element.getRunState()); default: img = BootDashActivator.getDefault().getImageRegistry().getDescriptor(BootDashActivator.SERVICE_INACTIVE_ICON); return toAnimation(img, null); } } else { return NO_IMAGES; } } try { if (element != null) { switch (column) { case PROJECT: IJavaProject jp = element.getJavaProject(); return jp == null ? new Image[0] : new Image[] { getJavaLabels().getImage(jp) }; case TREE_VIEWER_MAIN: case RUN_STATE_ICN: return decorateRunStateImages(element); default: return NO_IMAGES; } } } catch (Exception e) { Log.log(e); } return NO_IMAGES; } public StyledString getStyledText(BootDashModel element, BootDashColumn column) { if (element != null) { if (element.getRunTarget() != null) { if (element.getRefreshState().isLoading()) { StyledString prefix = new StyledString(); if (element.getRefreshState().getMessage() != null) { prefix = new StyledString(element.getRefreshState().getMessage() + " - ", stylers.italicColoured(SWT.COLOR_DARK_GRAY)); } return prefix.append(new StyledString(element.getRunTarget().getDisplayName(), stylers.italic())); } else { return new StyledString(element.getRunTarget().getDisplayName(), stylers.bold()); } } else { return new StyledString(UNKNOWN_LABEL); } } return stylers==null?new StyledString("null"):new StyledString("null", stylers.red()); } /** * For a given column type return the styler to use for any [...] that are * added around it. Return null */ public Styler getPrefixSuffixStyler(BootDashColumn column) { switch (column) { case TAGS: return stylers.tagBrackets(); case LIVE_PORT: return stylers.darkGreen(); case INSTANCES: return stylers.darkBlue(); case NAME: case PROJECT: // return null; case HOST: case RUN_STATE_ICN: case DEFAULT_PATH: default: return Stylers.NULL; } } public StyledString getStyledText(BootDashElement element, BootDashColumn column) { //The big case below should set either one of 'label' or'styledLabel', depending // on whether it is 'styling capable'. String label = null; StyledString styledLabel = null; if (element != null) { switch(column) { case TAGS: String text = TagUtils.toString(element.getTags()); styledLabel = stylers == null ? new StyledString(text) : TagUtils.applyTagStyles(text, stylers.tag()); break; case PROJECT: IJavaProject jp = element.getJavaProject(); if (jp == null) { // Not all projects in elements are Java projects. CF elements accept any project that contains a valid manifest.yml since the manifest.yml may // point to an executable archive for the app (.jar/.war) IProject project = element.getProject(); if (project != null) { label = project.getName(); } else { // Project and app (element) name are shown in separate columns now. If // there is no project mapping // do not show the element name anymore. That way the user knows that there is // no mapping for that element. label = ""; } } else { styledLabel = getJavaLabels().getStyledText(jp); //TODO: should use 'element.hasDevtools()' but its not implemented // yet on CF elements. boolean devtools = BootPropertyTester.hasDevtools(element.getProject()); if (devtools) { StyledString devtoolsDecoration = new StyledString(" [devtools]", stylers.darkGreen()); styledLabel.append(devtoolsDecoration); } } break; case HOST: String host = element.getLiveHost(); label = host == null ? UNKNOWN_LABEL : host; break; case TREE_VIEWER_MAIN: BootDashColumn[] cols = element.getColumns(); styledLabel = new StyledString(); for (BootDashColumn col : cols) { //Ignore RUN_STATE_ICN because its already represented in the label's icon. if (col != BootDashColumn.RUN_STATE_ICN) { StyledString append = getStyledText(element, col); if (hasText(append)) { Styler styler = getPrefixSuffixStyler(col); if (!hasText(styledLabel)) { // Nothing in the label so far, don't added brackets to first piece styledLabel = styledLabel.append(append); } else { if (col == BootDashColumn.DEFAULT_PATH) { styledLabel = styledLabel.append(" ").append(append); } else { if (styler == null) { styledLabel = styledLabel.append(" [").append(append).append("]"); } else { styledLabel = styledLabel.append(" [",styler).append(append).append("]",styler); } } } } } } break; case NAME: styledLabel = new StyledString(); if (element.getName() != null) { styledLabel.append(element.getName()); } else { styledLabel.append(UNKNOWN_LABEL); } break; case DEVTOOLS: if (element.hasDevtools()) { styledLabel = new StyledString("devtools", stylers.darkGreen()); } else { styledLabel = new StyledString(); } case RUN_STATE_ICN: label = element.getRunState().toString(); break; case LIVE_PORT: RunState runState = element.getRunState(); if (runState == RunState.RUNNING || runState == RunState.DEBUGGING) { String textLabel; ImmutableSet<Integer> ports = element.getLivePorts(); if (ports.isEmpty()) { textLabel = "unknown port"; } else { StringBuilder str = new StringBuilder(); String separator = ""; for (Integer port : ports) { str.append(separator); str.append(":"); str.append(port); separator = " "; } textLabel = str.toString(); } if (stylers == null) { label = textLabel; } else { styledLabel = new StyledString(textLabel, stylers.darkGreen()); } } break; case DEFAULT_PATH: String path = element.getDefaultRequestMappingPath(); if (stylers == null) { label = path == null ? "" : path; } else { styledLabel = new StyledString(path == null ? "" : path, stylers.darkGrey()); } break; case INSTANCES: int actual = element.getActualInstances(); int desired = element.getDesiredInstances(); if (desired!=1 || actual > 1) { //Don't show: less clutter, you can already see whether a single instance is running or not if (stylers == null) { label = actual + "/" + desired; } else { styledLabel = new StyledString(actual+"/"+desired,stylers.darkBlue()); } } break; case EXPOSED_URL: runState = element.getRunState(); if (runState == RunState.RUNNING || runState == RunState.DEBUGGING) { List<String> tunnelNames = new ArrayList<>(); if (element instanceof AbstractLaunchConfigurationsDashElement<?>) { ImmutableSet<ILaunchConfiguration> launches = ((AbstractLaunchConfigurationsDashElement<?>) element).getLaunchConfigs(); for (ILaunchConfiguration launchConfig : launches) { tunnelNames.add(launchConfig.getName()); } } for (String tunnelName : tunnelNames) { NGROKClient ngrokClient = NGROKLaunchTracker.get(tunnelName); if (ngrokClient != null) { if (styledLabel == null) { styledLabel = new StyledString("\u27A4 " + ngrokClient.getTunnel().getPublic_url(),stylers.darkBlue()); } else { styledLabel.append(new StyledString(" / \u27A4 " + ngrokClient.getTunnel().getPublic_url(),stylers.darkBlue())); } } } } break; default: label = UNKNOWN_LABEL; } } if (styledLabel!=null) { return styledLabel; } else if (label!=null) { return new StyledString(label); } return new StyledString(""); } /** * Deprecated: use getStyledText. */ @Deprecated public String getText(BootDashElement element, BootDashColumn column) { return getStyledText(element, column).getString(); } ///////////////////////////////////////////////////////////////////////////////////////////// // private / helper stuff private String commaSeparated(Collection<String> elements) { if (elements!=null) { StringBuilder buf = new StringBuilder(); boolean needComma = false; for (String string : elements) { if (needComma) { buf.append(','); } else { needComma = true; } buf.append(string); } return buf.toString(); } return ""; } private boolean hasText(StyledString stext) { return !stext.getString().isEmpty(); } private AppearanceAwareLabelProvider getJavaLabels() { if (javaLabels == null) { javaLabels = new AppearanceAwareLabelProvider(); } return javaLabels; } private Image[] getRunStateAnimation(RunState runState) { try { if (runStateImages==null) { runStateImages = new RunStateImages(); } return runStateImages.getAnimation(runState); } catch (Exception e) { Log.log(e); } return null; } private Image[] decorateRunStateImages(BootDashElement bde) throws Exception { Image[] decoratedImages = getRunStateAnimation(bde.getRunState()); if (bde.getTarget() != null && bde instanceof CloudAppDashElement && bde.getRunState() == RunState.RUNNING) { if (DevtoolsUtil.isDevClientAttached((CloudAppDashElement)bde, ILaunchManager.RUN_MODE) && decoratedImages.length > 0) { ImageDescriptor decorDesc = BootDashActivator.getDefault().getImageRegistry().getDescriptor(BootDashActivator.DT_ICON_ID); decoratedImages = runStateImages.getDecoratedImages(bde.getRunState(), decorDesc, IDecoration.BOTTOM_RIGHT); } } return decoratedImages; } }