/******************************************************************************* * Copyright (C) 2007, 2013 IBM Corporation and others. * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com> * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> * Copyright (C) 2008, Google Inc. * Copyright (C) 2008, Tor Arne Vestbø <torarnv@gmail.com> * Copyright (C) 2011, Dariusz Luksza <dariusz@luksza.org> * Copyright (C) 2011, Christian Halstrick <christian.halstrick@sap.com> * Copyright (C) 2015, IBM Corporation (Dani Megert <daniel_megert@ch.ibm.com>) * Copyright (C) 2016, Thomas Wolf <thomas.wolf@paranor.ch> * * 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: * Andre Bossert <anb0s@anbos.de> - Cleaning up the DecoratableResourceAdapter *******************************************************************************/ package org.eclipse.egit.ui.internal.decorators; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.resources.mapping.ResourceMapping; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.egit.core.AdapterUtils; import org.eclipse.egit.core.internal.indexdiff.IndexDiffChangedListener; import org.eclipse.egit.core.internal.indexdiff.IndexDiffData; import org.eclipse.egit.core.internal.util.ExceptionCollector; import org.eclipse.egit.core.project.GitProjectData; import org.eclipse.egit.core.project.RepositoryMappingChangeListener; import org.eclipse.egit.core.project.RepositoryMapping; import org.eclipse.egit.ui.Activator; import org.eclipse.egit.ui.UIPreferences; import org.eclipse.egit.ui.internal.UIIcons; import org.eclipse.egit.ui.internal.UIText; import org.eclipse.egit.ui.internal.resources.ResourceStateFactory; import org.eclipse.egit.ui.internal.resources.IResourceState.StagingState; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.IDecoration; import org.eclipse.jface.viewers.ILightweightLabelDecorator; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.LabelProviderChangedEvent; import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.lib.Repository; import org.eclipse.osgi.util.NLS; import org.eclipse.osgi.util.TextProcessor; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Display; import org.eclipse.team.internal.ui.Utils; import org.eclipse.team.ui.ISharedImages; import org.eclipse.team.ui.TeamImages; import org.eclipse.team.ui.TeamUI; import org.eclipse.ui.IContributorResourceAdapter; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.themes.ITheme; /** * Supplies annotations for displayed resources * * This decorator provides annotations to indicate the status of each resource * when compared to <code>HEAD</code>, as well as the index in the relevant * repository. */ public class GitLightweightDecorator extends LabelProvider implements ILightweightLabelDecorator, IPropertyChangeListener, IndexDiffChangedListener { /** * Property constant pointing back to the extension point id of the * decorator */ public static final String DECORATOR_ID = "org.eclipse.egit.ui.internal.decorators.GitLightweightDecorator"; //$NON-NLS-1$ /** * Collector for keeping the error view from filling up with exceptions */ private static final ExceptionCollector EXCEPTION_COLLECTOR = new ExceptionCollector( UIText.Decorator_exceptionMessageCommon, Activator.getPluginId(), IStatus.ERROR, Activator.getDefault().getLog()); private static final List<String> FONT_IDS = Arrays.asList( UIPreferences.THEME_UncommittedChangeFont, UIPreferences.THEME_IgnoredResourceFont); private static final List<String> COLOR_IDS = Arrays.asList( UIPreferences.THEME_UncommittedChangeBackgroundColor, UIPreferences.THEME_UncommittedChangeForegroundColor, UIPreferences.THEME_IgnoredResourceBackgroundColor, UIPreferences.THEME_IgnoredResourceForegroundColor); private static RGB defaultBackgroundRgb; private RepositoryMappingChangeListener mappingChangeListener = new RepositoryMappingChangeListener() { @Override public void repositoryChanged(RepositoryMapping which) { fireLabelEvent(); } }; /** * Constructs a new Git resource decorator */ public GitLightweightDecorator() { // This is an optimization to ensure that while decorating our fonts and // colors are pre-created and decoration can occur without having to syncExec. ensureFontAndColorsCreated(FONT_IDS, COLOR_IDS); TeamUI.addPropertyChangeListener(this); Activator.addPropertyChangeListener(this); PlatformUI.getWorkbench().getThemeManager().getCurrentTheme() .addPropertyChangeListener(this); org.eclipse.egit.core.Activator.getDefault().getIndexDiffCache().addIndexDiffChangedListener(this); GitProjectData.addRepositoryChangeListener(mappingChangeListener); } /** * This method will ensure that the fonts and colors used by the decorator * are cached in the registries. This avoids having to syncExec when * decorating since we ensure that the fonts and colors are pre-created. * * @param actFonts fonts ids to cache * @param actColors color ids to cache */ private void ensureFontAndColorsCreated(final List<String> actFonts, final List<String> actColors) { final Display display = PlatformUI.getWorkbench().getDisplay(); display.syncExec(new Runnable() { @Override public void run() { ITheme theme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme(); for (int i = 0; i < actColors.size(); i++) { theme.getColorRegistry().get(actColors.get(i)); } for (int i = 0; i < actFonts.size(); i++) { theme.getFontRegistry().get(actFonts.get(i)); } defaultBackgroundRgb = display.getSystemColor( SWT.COLOR_LIST_BACKGROUND).getRGB(); } }); } @Override public void dispose() { super.dispose(); PlatformUI.getWorkbench().getThemeManager().getCurrentTheme() .removePropertyChangeListener(this); TeamUI.removePropertyChangeListener(this); Activator.removePropertyChangeListener(this); org.eclipse.egit.core.Activator.getDefault().getIndexDiffCache().removeIndexDiffChangedListener(this); GitProjectData.removeRepositoryChangeListener(mappingChangeListener); mappingChangeListener = null; } /** * This method should only be called by the decorator thread. * * @see org.eclipse.jface.viewers.ILightweightLabelDecorator#decorate(java.lang.Object, * org.eclipse.jface.viewers.IDecoration) */ @Override public void decorate(Object element, IDecoration decoration) { // Don't decorate if UI plugin is not running if (Activator.getDefault() == null) { return; } // Don't decorate if the workbench is not running or the workspace is // not yet ready or already shut down if (!PlatformUI.isWorkbenchRunning() || ResourcesPlugin.getWorkspace() == null) { return; } final IResource resource = getResource(element); try { if (resource == null) { decorateResourceMapping(element, decoration); } else { decorateResource(resource, decoration); } } catch (CoreException e) { handleException(resource, e); } catch (Exception e) { handleException(resource, new CoreException(Activator .createErrorStatus(NLS.bind(UIText.Decorator_exceptionMessage, resource), e))); } } /** * Decorates a single resource (i.e. a project). * * @param resource the resource to decorate * @param decoration the decoration * @throws CoreException */ private void decorateResource(@NonNull IResource resource, IDecoration decoration) throws CoreException { if (resource.getType() == IResource.ROOT || !resource.isAccessible()) { return; } IndexDiffData indexDiffData = ResourceStateFactory.getInstance() .getIndexDiffDataOrNull(resource); if (indexDiffData == null) { return; } IDecoratableResource decoratableResource = null; final DecorationHelper helper = new DecorationHelper( Activator.getDefault().getPreferenceStore()); try { decoratableResource = new DecoratableResourceAdapter(indexDiffData, resource); } catch (IOException e) { throw new CoreException(Activator.createErrorStatus( NLS.bind(UIText.Decorator_exceptionMessage, resource), e)); } helper.decorate(decoration, decoratableResource); } /** * Decorates a resource mapping (i.e. a Working Set). * * @param element the element for which the decoration was initially called * @param decoration the decoration * @throws CoreException */ private void decorateResourceMapping(Object element, IDecoration decoration) throws CoreException { @SuppressWarnings("restriction") ResourceMapping mapping = Utils.getResourceMapping(element); IDecoratableResource decoRes; try { decoRes = new DecoratableResourceMapping(mapping); } catch (IOException e) { throw new CoreException(Activator.createErrorStatus( NLS.bind(UIText.Decorator_exceptionMessage, element), e)); } /* * don't render question marks on working sets. !isTracked() can have two reasons: * 1) nothing is tracked. * 2) no indexDiff for the contained projects ready yet. * in both cases, don't do anything to not pollute the display of the sets. */ if(!decoRes.isTracked()) return; final DecorationHelper helper = new DecorationHelper( Activator.getDefault().getPreferenceStore()); helper.decorate(decoration, decoRes); } /** * Helper class for doing resource decoration, based on the given * preferences * * Used for real-time decoration, as well as in the decorator preview * preferences page */ public static class DecorationHelper { /** */ public static final String BINDING_RESOURCE_NAME = "name"; //$NON-NLS-1$ /** */ public static final String BINDING_BRANCH_NAME = "branch"; //$NON-NLS-1$ /** */ public static final String BINDING_BRANCH_STATUS = "branch_status"; //$NON-NLS-1$ /** */ public static final String BINDING_REPOSITORY_NAME = "repository"; //$NON-NLS-1$ /** */ public static final String BINDING_SHORT_MESSAGE = "short_message"; //$NON-NLS-1$ /** */ public static final String BINDING_DIRTY_FLAG = "dirty"; //$NON-NLS-1$ /** */ public static final String BINDING_STAGED_FLAG = "staged"; //$NON-NLS-1$ /** */ public static final String FILE_FORMAT_DEFAULT="{dirty:>} {name}"; //$NON-NLS-1$ /** */ public static final String FOLDER_FORMAT_DEFAULT = "{dirty:>} {name}"; //$NON-NLS-1$ /** */ public static final String PROJECT_FORMAT_DEFAULT = "{dirty:>} {name} [{repository }{branch}{ branch_status}]"; //$NON-NLS-1$ /** */ public static final String SUBMODULE_FORMAT_DEFAULT = "{dirty:>} {name} [{branch}{ branch_status}]{ short_message}"; //$NON-NLS-1$ private IPreferenceStore store; /** * Define a cached image descriptor which only creates the image data * once */ private static class CachedImageDescriptor extends ImageDescriptor { ImageDescriptor descriptor; ImageData data; public CachedImageDescriptor(ImageDescriptor descriptor) { this.descriptor = descriptor; } @Override public ImageData getImageData() { if (data == null) { data = descriptor.getImageData(); } return data; } } private static ImageDescriptor trackedImage; private static ImageDescriptor untrackedImage; private static ImageDescriptor stagedImage; private static ImageDescriptor stagedAddedImage; private static ImageDescriptor stagedRemovedImage; private static ImageDescriptor conflictImage; private static ImageDescriptor assumeUnchangedImage; private static ImageDescriptor dirtyImage; static { trackedImage = new CachedImageDescriptor(TeamImages .getImageDescriptor(ISharedImages.IMG_CHECKEDIN_OVR)); untrackedImage = new CachedImageDescriptor(UIIcons.OVR_UNTRACKED); stagedImage = new CachedImageDescriptor(UIIcons.OVR_STAGED); stagedAddedImage = new CachedImageDescriptor(UIIcons.OVR_STAGED_ADD); stagedRemovedImage = new CachedImageDescriptor( UIIcons.OVR_STAGED_REMOVE); conflictImage = new CachedImageDescriptor(UIIcons.OVR_CONFLICT); assumeUnchangedImage = new CachedImageDescriptor(UIIcons.OVR_ASSUMEUNCHANGED); dirtyImage = new CachedImageDescriptor(UIIcons.OVR_DIRTY); } /** * Constructs a decorator using the rules from the given * <code>preferencesStore</code> * * @param preferencesStore * the preferences store with the preferred decorator rules */ public DecorationHelper(IPreferenceStore preferencesStore) { store = preferencesStore; } /** * Decorates the given <code>decoration</code> based on the state of the * given <code>resource</code>, using the preferences passed when * constructing this decoration helper. * * @param decoration * the decoration to decorate * @param resource * the resource to retrieve state from */ public void decorate(IDecoration decoration, IDecoratableResource resource) { decorateFontAndColour(decoration, resource); if (resource.isIgnored()) return; decorateText(decoration, resource); decorateIcons(decoration, resource); } private void decorateFontAndColour(IDecoration decoration, IDecoratableResource resource) { ITheme current = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme(); Color bc = null; Color fc = null; Font f = null; if (resource.isIgnored()) { bc = current.getColorRegistry().get( UIPreferences.THEME_IgnoredResourceBackgroundColor); fc = current.getColorRegistry().get( UIPreferences.THEME_IgnoredResourceForegroundColor); f = current.getFontRegistry().get( UIPreferences.THEME_IgnoredResourceFont); } else if (!resource.isTracked() || resource.isDirty() || resource.isStaged()) { bc = current.getColorRegistry().get( UIPreferences.THEME_UncommittedChangeBackgroundColor); fc = current.getColorRegistry().get( UIPreferences.THEME_UncommittedChangeForegroundColor); f = current.getFontRegistry().get( UIPreferences.THEME_UncommittedChangeFont); } if (bc != null) { setBackgroundColor(decoration, bc); } if (fc != null) { decoration.setForegroundColor(fc); } if (f != null) { decoration.setFont(f); } } private void setBackgroundColor(IDecoration decoration, Color color) { // In case the color is not changed from the default, do not set the // background because it paints over things from the theme such as // alternating line colors (see bug 412183). if (!color.getRGB().equals(defaultBackgroundRgb)) decoration.setBackgroundColor(color); } private void decorateText(IDecoration decoration, IDecoratableResource resource) { String format = ""; //$NON-NLS-1$ switch (resource.getType()) { default: case IResource.FILE: format = store .getString(UIPreferences.DECORATOR_FILETEXT_DECORATION); break; case IResource.FOLDER: case DecoratableResourceMapping.RESOURCE_MAPPING: if (resource.isRepositoryContainer()) { // Use the submodule formatting if it's a submodule or // nested repository root format = store.getString( UIPreferences.DECORATOR_SUBMODULETEXT_DECORATION); } else { format = store.getString( UIPreferences.DECORATOR_FOLDERTEXT_DECORATION); } break; case DecoratableResourceMapping.WORKING_SET: // working sets will use the project formatting but only if the // repo and branch is available if (resource.getRepositoryName() != null && resource.getBranch() != null) format = store .getString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION); else format = store .getString(UIPreferences.DECORATOR_FOLDERTEXT_DECORATION); break; case IResource.PROJECT: format = store .getString(UIPreferences.DECORATOR_PROJECTTEXT_DECORATION); break; } Map<String, String> bindings = new HashMap<>(); bindings.put(BINDING_RESOURCE_NAME, resource.getName()); bindings.put(BINDING_REPOSITORY_NAME, resource.getRepositoryName()); bindings.put(BINDING_BRANCH_NAME, resource.getBranch()); bindings.put(BINDING_BRANCH_STATUS, resource.getBranchStatus()); bindings.put(BINDING_DIRTY_FLAG, resource.isDirty() ? ">" : null); //$NON-NLS-1$ bindings.put(BINDING_STAGED_FLAG, resource.isStaged() ? "*" : null); //$NON-NLS-1$ bindings.put(BINDING_SHORT_MESSAGE, resource.getCommitMessage()); decorate(decoration, format, bindings); } private void decorateIcons(IDecoration decoration, IDecoratableResource resource) { ImageDescriptor overlay = null; if (resource.isTracked()) { if (store.getBoolean(UIPreferences.DECORATOR_SHOW_TRACKED_ICON)) overlay = trackedImage; if (store .getBoolean(UIPreferences.DECORATOR_SHOW_ASSUME_UNCHANGED_ICON) && resource.isAssumeUnchanged()) overlay = assumeUnchangedImage; // Staged overrides tracked StagingState staged = resource.getStagingState(); if (store.getBoolean(UIPreferences.DECORATOR_SHOW_STAGED_ICON) && staged != StagingState.NOT_STAGED) { if (staged == StagingState.ADDED) overlay = stagedAddedImage; else if (staged == StagingState.REMOVED) overlay = stagedRemovedImage; else overlay = stagedImage; } // Dirty overrides staged if(store .getBoolean(UIPreferences.DECORATOR_SHOW_DIRTY_ICON) && resource.isDirty()) { overlay = dirtyImage; } // Conflicts override everything if (store .getBoolean(UIPreferences.DECORATOR_SHOW_CONFLICTS_ICON) && resource.hasConflicts()) overlay = conflictImage; } else if (store .getBoolean(UIPreferences.DECORATOR_SHOW_UNTRACKED_ICON)) { overlay = untrackedImage; } // Overlays can only be added once, so do it at the end decoration.addOverlay(overlay); } /** * Decorates the given <code>decoration</code>, using the specified text * <code>format</code>, and mapped using the variable bindings from * <code>bindings</code> * * @param decoration * the decoration to decorate * @param format * the format to base the decoration on * @param bindings * the bindings between variables in the format and actual * values */ public static void decorate(IDecoration decoration, String format, Map<String, String> bindings) { StringBuilder prefix = new StringBuilder(); StringBuilder suffix = new StringBuilder(); StringBuilder output = prefix; int length = format.length(); int start = -1; int end = length; while (true) { if ((end = format.indexOf('{', start)) > -1) { output.append(format.substring(start + 1, end)); if ((start = format.indexOf('}', end)) > -1) { String key = format.substring(end + 1, start); String s; boolean spaceBefore = false; boolean spaceAfter = false; // Allow users to override the binding if (key.indexOf(':') > -1) { String[] keyAndBinding = key.split(":", 2); //$NON-NLS-1$ key = keyAndBinding[0]; if (keyAndBinding.length > 1 && bindings.get(key) != null) bindings.put(key, keyAndBinding[1]); } else { if (key.charAt(0) == ' ') { spaceBefore = true; key = key.substring(1); } if (key.charAt(key.length() - 1) == ' ') { spaceAfter = true; key = key.substring(0, key.length() - 1); } } // We use the BINDING_RESOURCE_NAME key to determine if // we are doing the prefix or suffix. The name isn't // actually part of either. if (key.equals(BINDING_RESOURCE_NAME)) { output = suffix; s = null; } else { s = bindings.get(key); } if (s != null) { if (spaceBefore) output.append(' '); output.append(s); if (spaceAfter) output.append(' '); } else { // Support removing prefix character if binding is // null int curLength = output.length(); if (curLength > 0) { char c = output.charAt(curLength - 1); if (c == ':' || c == '@') { output.deleteCharAt(curLength - 1); } } } } else { output.append(format.substring(end, length)); break; } } else { output.append(format.substring(start + 1, length)); break; } } String prefixString = prefix.toString().replaceAll("^\\s+", ""); //$NON-NLS-1$ //$NON-NLS-2$ if (prefixString.length() > 0) decoration.addPrefix(TextProcessor.process(prefixString, "()[].")); //$NON-NLS-1$ String suffixString = suffix.toString().replaceAll("\\s+$", ""); //$NON-NLS-1$ //$NON-NLS-2$ if (suffixString.length() > 0) decoration.addSuffix(TextProcessor.process(suffixString, "()[].")); //$NON-NLS-1$ } } // -------- Refresh handling -------- /** * Perform a blanket refresh of all decorations */ public static void refresh() { PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { @Override public void run() { Activator.getDefault().getWorkbench().getDecoratorManager() .update(DECORATOR_ID); } }); } /** * Callback for IPropertyChangeListener events * * If any of the relevant preferences has been changed we refresh all * decorations (all projects and their resources). * * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent) */ @Override public void propertyChange(PropertyChangeEvent event) { final String prop = event.getProperty(); // If the property is of any interest to us if (prop.equals(TeamUI.GLOBAL_IGNORES_CHANGED) || prop.equals(TeamUI.GLOBAL_FILE_TYPES_CHANGED) || prop.equals(Activator.DECORATORS_CHANGED)) { postLabelEvent(); } else if (prop.equals(UIPreferences.THEME_UncommittedChangeBackgroundColor) || prop.equals(UIPreferences.THEME_UncommittedChangeFont) || prop.equals(UIPreferences.THEME_UncommittedChangeForegroundColor) || prop.equals(UIPreferences.THEME_IgnoredResourceFont) || prop.equals(UIPreferences.THEME_IgnoredResourceBackgroundColor) || prop.equals(UIPreferences.THEME_IgnoredResourceForegroundColor)) { ensureFontAndColorsCreated(FONT_IDS, COLOR_IDS); postLabelEvent(); // TODO do I really need this? } } @Override public void indexDiffChanged(Repository repository, IndexDiffData indexDiffData) { // clear calculated repo data DecoratableResourceHelper.clearState(repository); postLabelEvent(); } // -------- Helper methods -------- private static IResource getResource(Object actElement) { Object element = actElement; if (element instanceof ResourceMapping) { element = ((ResourceMapping) element).getModelObject(); } IResource resource = null; if (element instanceof IResource) { resource = (IResource) element; } else if (element instanceof IAdaptable) { final IAdaptable adaptable = (IAdaptable) element; resource = AdapterUtils.adapt(adaptable, IResource.class); if (resource == null) { final IContributorResourceAdapter adapter = AdapterUtils.adapt(adaptable, IContributorResourceAdapter.class); if (adapter != null) resource = adapter.getAdaptedResource(adaptable); } } return resource; } /** * Post a label event to the LabelEventJob * * Posts a generic label event. No specific elements are provided; all * decorations shall be invalidated. Same as * <code>postLabelEvent(null, true)</code>. */ private void postLabelEvent() { // Post label event to LabelEventJob LabelEventJob.getInstance().postLabelEvent(this); } void fireLabelEvent() { final LabelProviderChangedEvent event = new LabelProviderChangedEvent( this); // Re-trigger decoration process (in UI thread) PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() { @Override public void run() { fireLabelProviderChanged(event); } }); } /** * Handle exceptions that occur in the decorator. Exceptions are only logged * for resources that are accessible (i.e. exist in an open project). * * @param resource * The resource that triggered the exception * @param e * The exception that occurred */ private static void handleException(IResource resource, CoreException e) { if (resource == null || resource.isAccessible()) EXCEPTION_COLLECTOR.handleException(e); } } /** * Job reducing label events to prevent unnecessary (i.e. redundant) event * processing */ class LabelEventJob extends Job { /** * Constant defining the waiting time (in milliseconds) until an event is * fired */ private static final long DELAY = 100L; private static LabelEventJob instance = new LabelEventJob("LabelEventJob"); //$NON-NLS-1$ /** * Get the LabelEventJob singleton * * @return the LabelEventJob singleton */ static LabelEventJob getInstance() { return instance; } private LabelEventJob(final String name) { super(name); } private GitLightweightDecorator glwDecorator; /** * Post a label event * * @param decorator * The GitLightweightDecorator that is used to fire a * LabelProviderChangedEvent */ void postLabelEvent(final GitLightweightDecorator decorator) { if (glwDecorator == null) glwDecorator = decorator; if (getState() == SLEEPING || getState() == WAITING) cancel(); schedule(DELAY); } @Override protected IStatus run(IProgressMonitor monitor) { if (glwDecorator != null) glwDecorator.fireLabelEvent(); return Status.OK_STATUS; } }