Separate MessageManager and MessageQueue

This commit is contained in:
Peter Dell 2022-08-29 12:15:35 +02:00
parent f1a5d63644
commit 2cbf19f9ad
7 changed files with 213 additions and 113 deletions

View File

@ -0,0 +1,145 @@
/**
* Copyright (C) 2009 - 2021 <a href="https://www.wudsn.com" target="_top">Peter Dell</a>
*
* This file is part of WUDSN IDE.
*
* WUDSN IDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* WUDSN IDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with WUDSN IDE. If not, see <http://www.gnu.org/licenses/>.
*/
package com.wudsn.ide.base.common;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
/**
* Message queue
*
* @author Peter Dell
*
*/
public final class MessageQueue {
public static final class Entry {
private int messageId;
private int severity;
private String message;
private String[] parameters;
private Throwable throwable;
public Entry(int messageId, int severity, String message, String[] parameters,
Throwable throwable) {
this.messageId = messageId;
this.severity = severity;
this.message = message;
this.parameters = parameters;
this.throwable = throwable;
}
public int getMessageId() {
return messageId;
}
public int getSeverity() {
return severity;
}
public String getMessage() {
return message;
}
public String[] getParameters() {
return parameters;
}
public Throwable getThrowable() {
return throwable;
}
}
private List<Entry> entriesList;
private boolean error;
public MessageQueue() {
entriesList = new ArrayList<Entry>();
error = false;
}
public void clear() {
entriesList.clear();
error = false;
}
/**
* Sends a message to the message queue.
*
* @param messageId The message id identifying the target UI element of the
* message.
* @param severity The severity, see
* {@link IStatus#INFO},{@link IStatus#WARNING} ,
* {@link IStatus#ERROR}.
* @param message The message text, not <code>null</code>.
* @param parameters The message parameters, may be empty or null.
*/
public void sendMessage(int messageId, int severity, String message, String... parameters) {
if (message == null) {
throw new IllegalArgumentException("Parameter 'message' must not be null.");
}
Entry messageQueueEntry;
messageQueueEntry = new Entry(messageId, severity, message, parameters, null);
addMessageQueueEntry(messageQueueEntry);
}
/**
* Sends a message to the message queue based on a core exception.
*
* @param messageId The message id identifying the target UI element of the
* message.
* @param coreException The core exception with the status and text information,
* not <code>null</code>.
*/
public void sendMessage(int messageId, CoreException coreException) {
if (coreException == null) {
throw new IllegalArgumentException("Parameter 'coreException' must not be null.");
}
Entry messageQueueEntry;
messageQueueEntry = new Entry(messageId, coreException.getStatus().getSeverity(),
coreException.getStatus().getMessage(), null, coreException);
addMessageQueueEntry(messageQueueEntry);
}
private void addMessageQueueEntry(Entry messageQueueEntry) {
if (messageQueueEntry == null) {
throw new IllegalArgumentException("Parameter 'messageQueueEntry' must not be null.");
}
entriesList.add(messageQueueEntry);
if (messageQueueEntry.getSeverity() == IStatus.ERROR) {
error = true;
}
}
public boolean containsError() {
return error;
}
public List<Entry> getEntries() {
return Collections.unmodifiableList(entriesList);
}
}

View File

@ -37,6 +37,7 @@ import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchPart;
import com.wudsn.ide.base.BasePlugin;
import com.wudsn.ide.base.common.MessageQueue;
import com.wudsn.ide.base.common.TextUtility;
/**
@ -85,66 +86,28 @@ public final class MessageManager {
}
}
private static final class MessageQueueEntry {
private int messageId;
private int severity;
private String message;
private String[] parameters;
private Throwable throwable;
public MessageQueueEntry(int messageId, int severity, String message, String[] parameters,
Throwable throwable) {
this.messageId = messageId;
this.severity = severity;
this.message = message;
this.parameters = parameters;
this.throwable = throwable;
}
public int getMessageId() {
return messageId;
}
public int getSeverity() {
return severity;
}
public String getMessage() {
return message;
}
public String[] getParameters() {
return parameters;
}
public Throwable getThrowable() {
return throwable;
}
}
private IWorkbenchPart workbenchPart;
private IStatusLineManager statusLineManager;
private List<FieldRegistryEntry> fieldRegistryEntries;
private List<MessageQueueEntry> messageQueueEntries;
private boolean messageQueueError;
private MessageQueue messageQueue;
private Color yellow;
private Color red;
public MessageManager(IWorkbenchPart workbenchPart) {
public MessageManager(MessageQueue messageQueue, IWorkbenchPart workbenchPart) {
if (messageQueue == null) {
throw new IllegalArgumentException("Parameter 'messageQueue' must not be null.");
}
if (workbenchPart == null) {
throw new IllegalArgumentException("Parameter 'workbenchPart' must not be null.");
}
this.messageQueue = messageQueue;
this.workbenchPart = workbenchPart;
fieldRegistryEntries = new ArrayList<FieldRegistryEntry>();
messageQueueEntries = new ArrayList<MessageQueueEntry>();
messageQueueError = false;
yellow = new Color(Display.getDefault(), 0, 255, 255);
red = new Color(Display.getDefault(), 255, 0, 0);
@ -189,8 +152,7 @@ public final class MessageManager {
}
public void clearMessages() {
messageQueueEntries.clear();
messageQueueError = false;
messageQueue.clear();
for (FieldRegistryEntry fieldRegistryEntry : fieldRegistryEntries) {
@ -222,9 +184,7 @@ public final class MessageManager {
if (message == null) {
throw new IllegalArgumentException("Parameter 'message' must not be null.");
}
MessageQueueEntry messageQueueEntry;
messageQueueEntry = new MessageQueueEntry(messageId, severity, message, parameters, null);
addMessageQueueEntry(messageQueueEntry);
messageQueue.sendMessage(messageId, severity, message, parameters);
}
/**
@ -239,25 +199,7 @@ public final class MessageManager {
if (coreException == null) {
throw new IllegalArgumentException("Parameter 'coreException' must not be null.");
}
MessageQueueEntry messageQueueEntry;
messageQueueEntry = new MessageQueueEntry(messageId, coreException.getStatus().getSeverity(),
coreException.getStatus().getMessage(), null, coreException);
addMessageQueueEntry(messageQueueEntry);
}
private void addMessageQueueEntry(MessageQueueEntry messageQueueEntry) {
if (messageQueueEntry == null) {
throw new IllegalArgumentException("Parameter 'messageQueueEntry' must not be null.");
}
messageQueueEntries.add(messageQueueEntry);
if (messageQueueEntry.getSeverity() == IStatus.ERROR) {
messageQueueError = true;
}
}
public boolean containsError() {
return messageQueueError;
messageQueue.sendMessage(messageId, coreException);
}
public void displayMessages() {
@ -271,7 +213,7 @@ public final class MessageManager {
.getImage();
Image errorImage = fieldDecorationRegistry.getFieldDecoration(FieldDecorationRegistry.DEC_ERROR).getImage();
for (MessageQueueEntry messageQueueEntry : messageQueueEntries) {
for (MessageQueue.Entry messageQueueEntry : messageQueue.getEntries()) {
String messageText = TextUtility.format(messageQueueEntry.getMessage(), messageQueueEntry.getParameters());

View File

@ -45,7 +45,7 @@ import com.wudsn.ide.base.common.FileUtility;
import com.wudsn.ide.base.common.HexUtility;
import com.wudsn.ide.base.common.IPathUtility;
import com.wudsn.ide.base.common.StringUtility;
import com.wudsn.ide.base.gui.MessageManager;
import com.wudsn.ide.base.common.MessageQueue;
import com.wudsn.ide.gfx.GraphicsPlugin;
import com.wudsn.ide.gfx.converter.FilesConverterParameters.SourceFile;
import com.wudsn.ide.gfx.converter.ImageConverterParameters.TargetFile;
@ -54,7 +54,7 @@ import com.wudsn.ide.gfx.model.ConverterDirection;
import com.wudsn.ide.gfx.model.ConverterMode;
public final class ConverterDataLogic {
private MessageManager messageManager;
private MessageQueue messageQueue;
private FilesConverterDataLogic filesConverterDataLogic;
/**
@ -95,11 +95,11 @@ public final class ConverterDataLogic {
}
}
public ConverterDataLogic(MessageManager messageManager) {
if (messageManager == null) {
throw new IllegalArgumentException("Parameter 'messageManager' must not be null.");
public ConverterDataLogic(MessageQueue messageQueue) {
if (messageQueue == null) {
throw new IllegalArgumentException("Parameter 'messageQueue' must not be null.");
}
this.messageManager = messageManager;
this.messageQueue = messageQueue;
filesConverterDataLogic = new FilesConverterDataLogic();
}
@ -130,7 +130,7 @@ public final class ConverterDataLogic {
data.getParameters().read(file);
} catch (CoreException ex) {
messageManager.sendMessage(0, ex);
messageQueue.sendMessage(0, ex);
}
loadSources(data, true);
} else {
@ -313,7 +313,7 @@ public final class ConverterDataLogic {
data.copyParametersToBackup();
} catch (CoreException ex) {
messageManager.sendMessage(0, ex);
messageQueue.sendMessage(0, ex);
saveFile = null;
}
return saveFile;
@ -358,7 +358,7 @@ public final class ConverterDataLogic {
if (!saved) {
for (int i = 0; i < minSize; i++) {
TargetFile targetFile = imageConverterParameters.getTargetFile(i);
messageManager.sendMessage(targetFile.getPathMessageId(), IStatus.ERROR,
messageQueue.sendMessage(targetFile.getPathMessageId(), IStatus.ERROR,
"No target file with file path and content present");
}
@ -388,11 +388,11 @@ public final class ConverterDataLogic {
boolean result;
if (filePath.isEmpty()) {
messageManager.sendMessage(messageId, IStatus.WARNING, "No target file path specified");
messageQueue.sendMessage(messageId, IStatus.WARNING, "No target file path specified");
return false;
}
if (bytes == null) {
messageManager.sendMessage(messageId, IStatus.INFO, "File {0} not saved as there is no data for this file",
messageQueue.sendMessage(messageId, IStatus.INFO, "File {0} not saved as there is no data for this file",
filePath.toPortableString());
return false;
}
@ -416,12 +416,12 @@ public final class ConverterDataLogic {
}
} catch (CoreException ex) {
messageManager.sendMessage(0, ex);
messageQueue.sendMessage(0, ex);
saveFile = null;
}
if (saveFile != null) {
messageManager.sendMessage(messageId, IStatus.INFO, "File {0} saved with ${1} bytes",
messageQueue.sendMessage(messageId, IStatus.INFO, "File {0} saved with ${1} bytes",
filePath.toPortableString(), HexUtility.getLongValueHexString(bytes.length));
result = true;
}
@ -433,11 +433,11 @@ public final class ConverterDataLogic {
throw new IllegalArgumentException("Parameter 'filePath' must not be null.");
}
if (filePath.isEmpty()) {
messageManager.sendMessage(messageId, IStatus.ERROR, "No image path specified");
messageQueue.sendMessage(messageId, IStatus.ERROR, "No image path specified");
return;
}
if (imageData == null) {
messageManager.sendMessage(messageId, IStatus.ERROR,
messageQueue.sendMessage(messageId, IStatus.ERROR,
"Image {0} not saved as there is no data for this image", filePath.toPortableString());
return;
}
@ -446,7 +446,7 @@ public final class ConverterDataLogic {
imageLoader.data = new ImageData[] { imageData };
int format = ImageExtensions.getImageFormat(filePath);
if (format == ImageExtensions.UNKNOWN_FORMAT) {
messageManager.sendMessage(messageId, IStatus.ERROR,
messageQueue.sendMessage(messageId, IStatus.ERROR,
"Image {0} not saved as there is the extension cannot be mapped to a supported image format",
filePath.toPortableString());
return;
@ -467,13 +467,13 @@ public final class ConverterDataLogic {
}
success = true;
} catch (Exception ex) {
messageManager.sendMessage(messageId, IStatus.ERROR, "Image {0} not saved. {1}",
messageQueue.sendMessage(messageId, IStatus.ERROR, "Image {0} not saved. {1}",
filePath.toPortableString(), ex.getMessage());
}
// file.refreshLocal(IResource.DEPTH_ZERO, null);
if (success) {
messageManager.sendMessage(messageId, IStatus.INFO, "Image {0} saved", filePath.toPortableString());
messageQueue.sendMessage(messageId, IStatus.INFO, "Image {0} saved", filePath.toPortableString());
}
}
@ -489,7 +489,7 @@ public final class ConverterDataLogic {
try {
is = file.getContents(true);
} catch (CoreException ex) {
messageManager.sendMessage(messageId, ex);
messageQueue.sendMessage(messageId, ex);
is = null;
}
@ -499,7 +499,7 @@ public final class ConverterDataLogic {
imageData = new ImageData(is);
} catch (SWTException ex) {
messageManager.sendMessage(messageId, IStatus.ERROR,
messageQueue.sendMessage(messageId, IStatus.ERROR,
"Cannot open image file. " + ex.getMessage());
}
@ -529,10 +529,10 @@ public final class ConverterDataLogic {
result = FileUtility.readBytes(file, FileUtility.MAX_SIZE_1MB, true);
} catch (CoreException ex) {
messageManager.sendMessage(messageId, ex);
messageQueue.sendMessage(messageId, ex);
result = null;
} catch (IllegalArgumentException ex) {
messageManager.sendMessage(messageId, IStatus.ERROR, ex.getMessage());
messageQueue.sendMessage(messageId, IStatus.ERROR, ex.getMessage());
result = null;
}
@ -601,18 +601,18 @@ public final class ConverterDataLogic {
Converter converter;
if (StringUtility.isEmpty(imageConverterParameters.getConverterId())) {
messageManager.sendMessage(ConverterCommonParameters.MessageIds.CONVERTER_ID, IStatus.ERROR,
messageQueue.sendMessage(ConverterCommonParameters.MessageIds.CONVERTER_ID, IStatus.ERROR,
"No converter selected");
return false;
}
converter = imageConverterData.getConverter();
if (converter == null) {
messageManager.sendMessage(ConverterCommonParameters.MessageIds.CONVERTER_ID, IStatus.ERROR,
messageQueue.sendMessage(ConverterCommonParameters.MessageIds.CONVERTER_ID, IStatus.ERROR,
"Converter '{0}' is not registered", String.valueOf(imageConverterParameters.getConverterId()));
return false;
}
if (messageManager.containsError()) {
if (messageQueue.containsError()) {
return false;
}
@ -621,7 +621,7 @@ public final class ConverterDataLogic {
try {
imageConverterParameters.setScript(ConverterScript.getScript(converter.getClass()));
} catch (CoreException ex) {
messageManager.sendMessage(ImageConverterParameters.MessageIds.SCRIPT, ex);
messageQueue.sendMessage(ImageConverterParameters.MessageIds.SCRIPT, ex);
return false;
}
}
@ -636,14 +636,14 @@ public final class ConverterDataLogic {
ConverterScript.convertToFileData(converter, imageConverterData);
} catch (CoreException ex) {
messageManager.sendMessage(ConverterCommonParameters.MessageIds.CONVERTER_ID, ex);
messageQueue.sendMessage(ConverterCommonParameters.MessageIds.CONVERTER_ID, ex);
return false;
} catch (RuntimeException ex) {
String message = ex.getMessage();
if (message == null) {
message = ex.getClass().getName();
}
messageManager.sendMessage(ImageConverterParameters.MessageIds.SCRIPT, IStatus.ERROR, message);
messageQueue.sendMessage(ImageConverterParameters.MessageIds.SCRIPT, IStatus.ERROR, message);
return false;
}
@ -670,18 +670,18 @@ public final class ConverterDataLogic {
filesConverterParameters = filesConverterData.getParameters();
if (filesConverterParameters.getSpacingWidth() < 0) {
messageManager.sendMessage(FilesConverterParameters.MessageIds.SPACING_WIDTH, IStatus.ERROR,
messageQueue.sendMessage(FilesConverterParameters.MessageIds.SPACING_WIDTH, IStatus.ERROR,
"Spacing width must not be negative. Current value is {0}",
String.valueOf(filesConverterParameters.getSpacingWidth()));
}
if (filesConverterParameters.getColumns() <= 0) {
messageManager.sendMessage(FilesConverterParameters.MessageIds.COLUMNS, IStatus.ERROR,
messageQueue.sendMessage(FilesConverterParameters.MessageIds.COLUMNS, IStatus.ERROR,
"Columns count must be positive. Current value is {0}",
String.valueOf(filesConverterParameters.getColumns()));
}
if (filesConverterParameters.getRows() <= 0) {
messageManager.sendMessage(FilesConverterParameters.MessageIds.ROWS, IStatus.ERROR,
messageQueue.sendMessage(FilesConverterParameters.MessageIds.ROWS, IStatus.ERROR,
"Rows count must be positive. Current value is {0}",
String.valueOf(filesConverterParameters.getRows()));
}
@ -689,28 +689,28 @@ public final class ConverterDataLogic {
Converter converter;
if (StringUtility.isEmpty(filesConverterParameters.getConverterId())) {
messageManager.sendMessage(ConverterCommonParameters.MessageIds.CONVERTER_ID, IStatus.ERROR,
messageQueue.sendMessage(ConverterCommonParameters.MessageIds.CONVERTER_ID, IStatus.ERROR,
"No converter selected");
return false;
}
converter = filesConverterData.getConverter();
if (converter == null) {
messageManager.sendMessage(ConverterCommonParameters.MessageIds.CONVERTER_ID, IStatus.ERROR,
messageQueue.sendMessage(ConverterCommonParameters.MessageIds.CONVERTER_ID, IStatus.ERROR,
"Converter '{0}' is not registered", String.valueOf(filesConverterParameters.getConverterId()));
return false;
}
if (messageManager.containsError()) {
if (messageQueue.containsError()) {
return false;
}
converter.convertToImageDataSize(filesConverterData);
if (filesConverterData.getImageDataWidth() <= 0) {
messageManager.sendMessage(FilesConverterParameters.MessageIds.COLUMNS, IStatus.ERROR,
messageQueue.sendMessage(FilesConverterParameters.MessageIds.COLUMNS, IStatus.ERROR,
"Resulting image data with '{0}' is not positive",
String.valueOf(filesConverterData.getImageDataWidth()));
}
if (filesConverterData.getImageDataHeight() <= 0) {
messageManager.sendMessage(FilesConverterParameters.MessageIds.ROWS, IStatus.ERROR,
messageQueue.sendMessage(FilesConverterParameters.MessageIds.ROWS, IStatus.ERROR,
"Resulting image data height '{0}' is not positive",
String.valueOf(filesConverterData.getImageDataWidth()));
}
@ -718,12 +718,12 @@ public final class ConverterDataLogic {
int MAX_PIXELS = 1000 * 1000;
int pixels = filesConverterData.getImageDataWidth() * filesConverterData.getImageDataHeight();
if (pixels > MAX_PIXELS) {
messageManager.sendMessage(FilesConverterParameters.MessageIds.ROWS, IStatus.ERROR,
messageQueue.sendMessage(FilesConverterParameters.MessageIds.ROWS, IStatus.ERROR,
"Resulting image would have {0} pixels and exceed the memory limit of {1} pixels",
String.valueOf(pixels), String.valueOf(MAX_PIXELS));
}
if (messageManager.containsError()) {
if (messageQueue.containsError()) {
return false;
}

View File

@ -56,6 +56,7 @@ import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.EditorPart;
import com.wudsn.ide.base.BasePlugin;
import com.wudsn.ide.base.common.MessageQueue;
import com.wudsn.ide.base.common.NumberUtility;
import com.wudsn.ide.base.gui.Action;
import com.wudsn.ide.base.gui.Application;
@ -279,6 +280,7 @@ public final class GraphicsConversionEditor extends EditorPart
// ID of the editor in the plugin manifest.
public static final String ID = "com.wudsn.ide.gfx.editor.GraphicsConversionEditor";
private MessageQueue messageQueue;
private MessageManager messageManager;
private boolean processing;
private boolean closeEditor;
@ -300,9 +302,10 @@ public final class GraphicsConversionEditor extends EditorPart
private MyImageProvider imageProvider;
public GraphicsConversionEditor() {
messageManager = new MessageManager(this);
messageQueue = new MessageQueue();
messageManager = new MessageManager(messageQueue, this);
converterDataLogic = new ConverterDataLogic(messageManager);
converterDataLogic = new ConverterDataLogic(messageQueue);
converterData = converterDataLogic.createData();
selectionChangedListeners = new ArrayList<ISelectionChangedListener>();

View File

@ -34,6 +34,7 @@ import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.part.ViewPart;
import com.wudsn.ide.base.common.MessageQueue;
import com.wudsn.ide.base.gui.Action;
import com.wudsn.ide.base.gui.ActionListener;
import com.wudsn.ide.base.gui.MessageManager;
@ -105,6 +106,7 @@ public final class ImageView extends ViewPart implements ISelectionListener {
}
public static final String ID = ImageView.class.getName();
MessageQueue messageQueue;
MessageManager messageManager;
private AspectControlContribution aspectControlContribution;
@ -118,7 +120,8 @@ public final class ImageView extends ViewPart implements ISelectionListener {
* Creation is private.
*/
public ImageView() {
messageManager = new MessageManager(this);
messageQueue = new MessageQueue();
messageManager = new MessageManager(messageQueue, this);
}
@Override

View File

@ -56,6 +56,7 @@ import com.wudsn.ide.base.BasePlugin;
import com.wudsn.ide.base.common.ByteArrayUtility;
import com.wudsn.ide.base.common.FileUtility;
import com.wudsn.ide.base.common.HexUtility;
import com.wudsn.ide.base.common.MessageQueue;
import com.wudsn.ide.base.common.NumberUtility;
import com.wudsn.ide.base.common.Profiler;
import com.wudsn.ide.base.common.TextUtility;
@ -112,6 +113,7 @@ public final class HexEditor extends EditorPart implements ISelectionProvider, A
private static final String CONTEXT_MENU_ID = "#HexEditorContext";
private static final long MAX_FILE_SIZE = 8 * ByteArrayUtility.MB;
private MessageQueue messageQueue;
private MessageManager messageManager;
private HexEditorParserComponent parserComponent;
@ -144,7 +146,8 @@ public final class HexEditor extends EditorPart implements ISelectionProvider, A
// Initialize for stand alone usage.
new BasePlugin().start(null);
HexEditorParserComponent parser = new HexEditorParserComponent(new MessageManager(new HexEditor()));
HexEditorParserComponent parser = new HexEditorParserComponent(
new MessageManager(new MessageQueue(), new HexEditor()));
parser.setFileContent(new byte[100000]);
parser.determinePossibleFileContentModes();
@ -163,7 +166,8 @@ public final class HexEditor extends EditorPart implements ISelectionProvider, A
public HexEditor() {
super();
messageManager = new MessageManager(this);
messageQueue = new MessageQueue();
messageManager = new MessageManager(messageQueue, this);
parserComponent = new HexEditorParserComponent(messageManager);

View File

@ -54,6 +54,7 @@ import org.eclipse.ui.part.EditorPart;
import com.wudsn.ide.base.common.FileUtility;
import com.wudsn.ide.base.common.HexUtility;
import com.wudsn.ide.base.common.MessageQueue;
import com.wudsn.ide.base.common.NumberUtility;
import com.wudsn.ide.base.common.StringUtility;
import com.wudsn.ide.base.common.TextUtility;
@ -150,6 +151,7 @@ public final class SoundEditor extends EditorPart implements Application, SoundP
// ID of the editor in the plugin manifest.
public static final String ID = "com.wudsn.ide.snd.editor.SoundEditor"; //$NON-NLS-1$
private MessageQueue messageQueue;
private MessageManager messageManager;
private SoundPlayer player;
@ -184,7 +186,8 @@ public final class SoundEditor extends EditorPart implements Application, SoundP
public SoundEditor() {
super();
messageManager = new MessageManager(this);
messageQueue = new MessageQueue();
messageManager = new MessageManager(messageQueue, this);
}
// @Override