package io.sloeber.core.api;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import javax.swing.event.ChangeListener;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.envvar.IContributedEnvironment;
import org.eclipse.cdt.core.envvar.IEnvironmentVariable;
import org.eclipse.cdt.core.envvar.IEnvironmentVariableManager;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.CSourceEntry;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICExclusionPatternPathEntry;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.core.settings.model.ICResourceDescription;
import org.eclipse.cdt.core.settings.model.ICSettingEntry;
import org.eclipse.cdt.core.settings.model.ICSourceEntry;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import io.sloeber.core.Activator;
import io.sloeber.core.InternalBoardDescriptor;
import io.sloeber.core.common.Common;
import io.sloeber.core.common.ConfigurationPreferences;
import io.sloeber.core.common.Const;
import io.sloeber.core.tools.Helpers;
import io.sloeber.core.tools.Programmers;
import io.sloeber.core.tools.ShouldHaveBeenInCDT;
import io.sloeber.core.tools.TxtFile;
@SuppressWarnings("nls")
public class BoardDescriptor {
@Override
public String toString() {
return getBoardsFile() + " \"" + getBoardName() + "\" " + getUploadPort(); //$NON-NLS-2$
}
// preference nodes
public static final String NODE_ARDUINO = Activator.NODE_ARDUINO;
/*
* This is the basic info contained in the descriptor
*/
private String myUploadPort;
private String myUploadProtocol;
private String myBoardID;
private Map<String, String> myOptions;
/*
* the following data is stored to detect changes that will make the equal
* fail so os changes, workspace changes, eclipse install changes will force
* a update on the stored data
*/
private String myProjectName = new String();
private String myOSName = Platform.getOS();
private String myWorkSpaceLocation = Common.getWorkspaceRoot().toString();
private String myWorkEclipseLocation = ConfigurationPreferences.getEclipseHome().toString();
/*
* Stuff to make things work
*/
private File myBoardsFile;
protected TxtFile myTxtFile;
private ChangeListener myChangeListeners = null;
private static final IEclipsePreferences myStorageNode = InstanceScope.INSTANCE.getNode(NODE_ARDUINO);
/*
* Some constants
*/
private static final String KEY_LAST_USED_BOARD = "Last used Board";
private static final String KEY_LAST_USED_UPLOAD_PORT = "Last Used Upload port";
private static final String KEY_LAST_USED_UPLOAD_PROTOCOL = "last Used upload Protocol";
private static final String KEY_LAST_USED_BOARDS_FILE = "Last used Boards file";
private static final String KEY_LAST_USED_BOARD_MENU_OPTIONS = "last used Board custom option selections";
private static final String MENUSELECTION = Const.ENV_KEY_JANTJE_START + "MENU.";
private static final String ENV_KEY_JANTJE_UPLOAD_PORT = Const.ENV_KEY_JANTJE_START + "COM_PORT";
private static final String ENV_KEY_JANTJE_BOARD_NAME = Const.ENV_KEY_JANTJE_START + "BOARD_NAME";
private static final String ENV_KEY_JANTJE_PROJECT_NAME = Const.ENV_KEY_JANTJE_START + "PROJECT_NAME";
private static final String ENV_KEY_JANTJE_OS = Const.ENV_KEY_JANTJE_START + "OS_NAME";
private static final String ENV_KEY_JANTJE_WORKSPACE_LOCATION = Const.ENV_KEY_JANTJE_START + "WORKSPACE_LOCATION";
private static final String ENV_KEY_JANTJE_ECLIPSE_LOCATION = Const.ENV_KEY_JANTJE_START + "ECLIPSE_LOCATION";
/**
* Compare 2 descriptors and return true is they are equal. This method
* detects - OS changes - project name changes - moves of workspace -
* changed runtine eclipse install
*
* @param obj
* @return true if equal otherwise false
*/
public boolean equals(BoardDescriptor obj) {
if (!this.getUploadPort().equals(obj.getUploadPort())) {
return false;
}
if (!this.getUploadProtocol().equals(obj.getUploadProtocol())) {
return false;
}
if (!this.getBoardID().equals(obj.getBoardID())) {
return false;
}
if (!this.getBoardsFile().equals(obj.getBoardsFile())) {
return false;
}
if (!this.getOptions().equals(obj.getOptions())) {
return false;
}
if (!this.getProjectName().equals(obj.getProjectName())) {
return false;
}
if (!this.getMyOSName().equals(obj.getMyOSName())) {
return false;
}
if (!this.getMyWorkEclipseLocation().equals(obj.getMyWorkEclipseLocation())) {
return false;
}
if (!this.getMyWorkSpaceLocation().equals(obj.getMyWorkSpaceLocation())) {
return false;
}
return true;
}
/*
* Create a sketchProject. This class does not really create a sketch
* object. Nor does it look for existing (mapping) sketch projects This
* class represents the data passed between the UI and the core This class
* does contain a create to create the project When confdesc is null the
* data will be taken from the "last used " otherwise the data is taken from
* the project the confdesc belongs to
*
*/
public static BoardDescriptor makeBoardDescriptor(ICConfigurationDescription confdesc) {
return new InternalBoardDescriptor(confdesc);
}
protected BoardDescriptor() {
}
protected BoardDescriptor(ICConfigurationDescription confdesc) {
if (confdesc == null) {
this.myBoardsFile = new File(myStorageNode.get(KEY_LAST_USED_BOARDS_FILE, ""));
this.myTxtFile = new TxtFile(this.myBoardsFile);
this.myBoardID = myStorageNode.get(KEY_LAST_USED_BOARD, "");
this.myUploadPort = myStorageNode.get(KEY_LAST_USED_UPLOAD_PORT, "");
this.myUploadProtocol = myStorageNode.get(KEY_LAST_USED_UPLOAD_PROTOCOL,
Defaults.getDefaultUploadProtocol());
menuOptionsFromString(myStorageNode.get(KEY_LAST_USED_BOARD_MENU_OPTIONS, new String()));
} else {
this.myUploadPort = Common.getBuildEnvironmentVariable(confdesc, ENV_KEY_JANTJE_UPLOAD_PORT, "");
this.myUploadProtocol = Common.getBuildEnvironmentVariable(confdesc,
Common.get_Jantje_KEY_PROTOCOL(Const.ACTION_UPLOAD), "");
this.myBoardsFile = new File(
Common.getBuildEnvironmentVariable(confdesc, Const.ENV_KEY_JANTJE_BOARDS_FILE, ""));
this.myBoardID = Common.getBuildEnvironmentVariable(confdesc, Const.ENV_KEY_JANTJE_BOARD_ID, "");
this.myProjectName = Common.getBuildEnvironmentVariable(confdesc, ENV_KEY_JANTJE_PROJECT_NAME, "");
this.myTxtFile = new TxtFile(this.myBoardsFile);
this.myOSName = Common.getBuildEnvironmentVariable(confdesc, ENV_KEY_JANTJE_OS, "");
this.myWorkSpaceLocation = Common.getBuildEnvironmentVariable(confdesc, ENV_KEY_JANTJE_WORKSPACE_LOCATION,
"");
this.myWorkEclipseLocation = Common.getBuildEnvironmentVariable(confdesc, ENV_KEY_JANTJE_ECLIPSE_LOCATION,
"");
this.myOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
IEnvironmentVariableManager envManager = CCorePlugin.getDefault().getBuildEnvironmentManager();
IContributedEnvironment contribEnv = envManager.getContributedEnvironment();
IEnvironmentVariable[] curVariables = contribEnv.getVariables(confdesc);
for (IEnvironmentVariable curVariable : curVariables) {
if (curVariable.getName().startsWith(MENUSELECTION)) {
this.myOptions.put(curVariable.getName().substring(MENUSELECTION.length()), curVariable.getValue());
}
}
}
}
public static BoardDescriptor makeBoardDescriptor(File boardsFile, String boardID, Map<String, String> options) {
return new InternalBoardDescriptor(boardsFile, boardID, options);
}
/**
* make a board descriptor for each board in the board.txt file with the
* default options
*
* @param boardFile
* @return a list of board descriptors
*/
public static List<BoardDescriptor> makeBoardDescriptors(File boardFile) {
TxtFile txtFile = new TxtFile(boardFile);
List<BoardDescriptor> boards = new ArrayList<>();
for (String curboardName : txtFile.getAllNames()) {
Map<String, String> boardSection = txtFile.getSection(txtFile.getBoardIDFromBoardName(curboardName));
if (boardSection != null) {
if (!"true".equalsIgnoreCase(boardSection.get("hide"))) {
boards.add(makeBoardDescriptor(boardFile, txtFile.getBoardIDFromBoardName(curboardName), null));
}
}
}
return boards;
}
/**
* create a board descriptor
*
* @param boardsFile
* @param boardID
* @param options
* if null default options are taken
*/
protected BoardDescriptor(File boardsFile, String boardID, Map<String, String> options) {
this.myUploadPort = new String();
this.myUploadProtocol = Defaults.getDefaultUploadProtocol();
this.myBoardID = boardID;
this.myOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
this.myBoardsFile = boardsFile;
this.myTxtFile = new TxtFile(this.myBoardsFile);
if (options != null) {
this.myOptions.putAll(options);
} else {
TreeMap<String, String> allOptions = this.myTxtFile.getMenus();
for (Map.Entry<String, String> curoption : allOptions.entrySet()) {
if (!this.myOptions.containsKey(curoption.getKey())) {
String[] menuOptions = this.myTxtFile.getMenuItemIDsFromMenuID(curoption.getKey(), boardID);
if (menuOptions.length > 0) {
this.myOptions.put(curoption.getKey(), menuOptions[0]);
}
}
}
}
}
/**
* tries to set the project to the boarddescriptor
*
* @param project
* @param monitor
* @return true if success false if failed
*/
public boolean configureProject(IProject project, IProgressMonitor monitor) {
ICProjectDescription prjCDesc = CoreModel.getDefault().getProjectDescription(project);
ICConfigurationDescription configurationDescription = prjCDesc.getActiveConfiguration();
try {
save(configurationDescription);
// prjCDesc.setActiveConfiguration(configurationDescription);
CoreModel.getDefault().getProjectDescriptionManager().setProjectDescription(project, prjCDesc, true, null);
} catch (Exception e) {
e.printStackTrace();
Common.log(new Status(IStatus.ERROR, io.sloeber.core.Activator.getId(), "failed to save the board settings",
e));
return false;
}
return true;
}
/*
* Method to create a project based on the board
*/
public IProject createProject(String projectName, URI projectURI,
ArrayList<ConfigurationDescriptor> cfgNamesAndTCIds, CodeDescriptor codeDescription,
CompileOptions compileOptions, IProgressMonitor monitor) throws Exception {
IProject projectHandle;
projectHandle = ResourcesPlugin.getWorkspace().getRoot().getProject(Common.MakeNameCompileSafe(projectName));
// try {
IWorkspace workspace = ResourcesPlugin.getWorkspace();
final IProjectDescription desc = workspace.newProjectDescription(projectHandle.getName());
desc.setLocationURI(projectURI);
projectHandle.create(desc, monitor);
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
projectHandle.open(IResource.BACKGROUND_REFRESH, monitor);
// Creates the .cproject file with the configurations
ICProjectDescription prjCDesc = ShouldHaveBeenInCDT.setCProjectDescription(projectHandle, cfgNamesAndTCIds,
true, monitor);
// Add the C C++ AVR and other needed Natures to the project
Helpers.addTheNatures(desc);
// Add the Arduino folder
Helpers.createNewFolder(projectHandle, Const.ARDUINO_CODE_FOLDER_NAME, null);
for (ConfigurationDescriptor curConfig : cfgNamesAndTCIds) {
ICConfigurationDescription configurationDescription = prjCDesc.getConfigurationByName(curConfig.configName);
compileOptions.save(configurationDescription);
save(configurationDescription);
}
// Set the path variables
// ArduinoHelpers.setProjectPathVariables(prjCDesc.getActiveConfiguration());
// Intermediately save or the adding code will fail
// Release is the active config (as that is the "IDE" Arduino
// type....)
ICConfigurationDescription defaultConfigDescription = prjCDesc
.getConfigurationByName(cfgNamesAndTCIds.get(0).configName);
ICResourceDescription cfgd = defaultConfigDescription.getResourceDescription(new Path(new String()), true);
ICExclusionPatternPathEntry[] entries = cfgd.getConfiguration().getSourceEntries();
if (entries.length == 1) {
Path exclusionPath[] = new Path[8];
exclusionPath[0] = new Path(Const.LIBRARY_PATH_SUFFIX + "/?*/**/?xamples/**");
exclusionPath[1] = new Path(Const.LIBRARY_PATH_SUFFIX + "/?*/**/?xtras/**");
exclusionPath[2] = new Path(Const.LIBRARY_PATH_SUFFIX + "/?*/**/test*/**");
exclusionPath[3] = new Path(Const.LIBRARY_PATH_SUFFIX + "/?*/**/third-party/**");
exclusionPath[4] = new Path(Const.LIBRARY_PATH_SUFFIX + "**/._*");
exclusionPath[5] = new Path(Const.LIBRARY_PATH_SUFFIX + "/?*/c*/?*");
exclusionPath[6] = new Path(Const.LIBRARY_PATH_SUFFIX + "/?*/d*/?*");
exclusionPath[7] = new Path(Const.LIBRARY_PATH_SUFFIX + "/?*/D*/?*");
ICExclusionPatternPathEntry newSourceEntry = new CSourceEntry(entries[0].getFullPath(), exclusionPath,
ICSettingEntry.VALUE_WORKSPACE_PATH);
ICSourceEntry[] out = null;
out = new ICSourceEntry[1];
out[0] = (ICSourceEntry) newSourceEntry;
try {
cfgd.getConfiguration().setSourceEntries(out);
} catch (CoreException e) {
// ignore
}
} else {
// this should not happen
}
codeDescription.createFiles(projectHandle, monitor);
prjCDesc.setActiveConfiguration(defaultConfigDescription);
prjCDesc.setCdtProjectCreated();
CoreModel.getDefault().getProjectDescriptionManager().setProjectDescription(projectHandle, prjCDesc, true,
null);
projectHandle.setDescription(desc, new NullProgressMonitor());
projectHandle.refreshLocal(IResource.DEPTH_INFINITE, null);
monitor.done();
return projectHandle;
}
public void save(ICConfigurationDescription confdesc) throws Exception {
saveConfiguration(confdesc, null);
if (confdesc != null) {
IProject project = confdesc.getProjectDescription().getProject();
Helpers.setTheEnvironmentVariables(project, confdesc, (InternalBoardDescriptor) this);
Helpers.addArduinoCodeToProject(project, confdesc);
Helpers.removeInvalidIncludeFolders(confdesc);
Helpers.setDirtyFlag(project, confdesc);
}
}
public void saveConfiguration() {
saveConfiguration(null, null);
}
public void saveConfiguration(ICConfigurationDescription confDesc, IContributedEnvironment contribEnvIn) {
if (confDesc != null) {
IContributedEnvironment contribEnv = contribEnvIn;
if (contribEnv == null) {
IEnvironmentVariableManager envManager = CCorePlugin.getDefault().getBuildEnvironmentManager();
contribEnv = envManager.getContributedEnvironment();
}
Common.setBuildEnvironmentVariable(contribEnv, confDesc, Const.ENV_KEY_JANTJE_PLATFORM_FILE,
getPlatformFile());
Common.setBuildEnvironmentVariable(contribEnv, confDesc, "JANTJE.SELECTED.PLATFORM",
getPlatformPath().toString());
Common.setBuildEnvironmentVariable(contribEnv, confDesc, ENV_KEY_JANTJE_BOARD_NAME, getBoardName());
Common.setBuildEnvironmentVariable(contribEnv, confDesc, Const.ENV_KEY_JANTJE_BOARDS_FILE, getBoardsFile());
Common.setBuildEnvironmentVariable(contribEnv, confDesc, Const.ENV_KEY_JANTJE_BOARD_ID, this.myBoardID);
Common.setBuildEnvironmentVariable(contribEnv, confDesc, Const.ENV_KEY_JANTJE_ARCITECTURE_ID,
getArchitecture());
Common.setBuildEnvironmentVariable(contribEnv, confDesc, Const.ENV_KEY_JANTJE_PACKAGE_ID, getPackage());
Common.setBuildEnvironmentVariable(contribEnv, confDesc, ENV_KEY_JANTJE_UPLOAD_PORT, this.myUploadPort);
Common.setBuildEnvironmentVariable(contribEnv, confDesc, ENV_KEY_JANTJE_PROJECT_NAME, getProjectName());
Common.setBuildEnvironmentVariable(contribEnv, confDesc, ENV_KEY_JANTJE_OS, this.myOSName);
Common.setBuildEnvironmentVariable(contribEnv, confDesc, ENV_KEY_JANTJE_WORKSPACE_LOCATION,
this.myWorkSpaceLocation);
Common.setBuildEnvironmentVariable(contribEnv, confDesc, ENV_KEY_JANTJE_ECLIPSE_LOCATION,
this.myWorkEclipseLocation);
Common.setBuildEnvironmentVariable(confDesc, Common.get_Jantje_KEY_PROTOCOL(Const.ACTION_UPLOAD),
this.myUploadProtocol);
if (this.myOptions != null) {
for (Map.Entry<String, String> curoption : this.myOptions.entrySet()) {
Common.setBuildEnvironmentVariable(contribEnv, confDesc, MENUSELECTION + curoption.getKey(),
curoption.getValue());
}
}
}
// Also save last used values
myStorageNode.put(KEY_LAST_USED_BOARDS_FILE, getBoardsFile());
myStorageNode.put(KEY_LAST_USED_BOARD, this.myBoardID);
myStorageNode.put(KEY_LAST_USED_UPLOAD_PORT, this.myUploadPort);
myStorageNode.put(KEY_LAST_USED_UPLOAD_PROTOCOL, this.myUploadProtocol);
myStorageNode.put(KEY_LAST_USED_BOARD_MENU_OPTIONS, menuOptionsToString());
}
public String getPackage() {
return this.myTxtFile.getPackage();
}
public String getArchitecture() {
return this.myTxtFile.getArchitecture();
}
public String getBoardsFile() {
return new Path(this.myBoardsFile.toString()).toString();
}
public String getBoardName() {
return this.myTxtFile.getNameFromID(this.myBoardID);
}
public String getUploadPort() {
return this.myUploadPort;
}
public String getUploadProtocol() {
return this.myUploadProtocol;
}
public IPath getPlatformPath() {
try {
return new Path(this.myBoardsFile.getParent());
} catch (Exception e) {
return new Path(new String());
}
}
public String getPlatformFile() {
return getPlatformPath().append(Const.PLATFORM_FILE_NAME).toString();
}
public void setUploadPort(String newUploadPort) {
this.myUploadPort = newUploadPort;
}
public void setUploadProtocol(String newUploadProtocol) {
this.myUploadProtocol = newUploadProtocol;
}
public void setBoardID(String boardID) {
if (!boardID.equals(this.myBoardID)) {
this.myBoardID = boardID;
informChangeListeners();
}
}
public void setBoardName(String boardName) {
String newBoardID = this.myTxtFile.getBoardIDFromBoardName(boardName);
if ((newBoardID == null || this.myBoardID.equals(newBoardID))) {
return;
}
this.myBoardID = newBoardID;
informChangeListeners();
}
public void setBoardsFile(File boardsFile) {
if (boardsFile == null) {
return;// ignore
}
if (this.myBoardsFile.equals(boardsFile)) {
return;
}
this.myBoardsFile = boardsFile;
this.myTxtFile = new TxtFile(this.myBoardsFile);
informChangeListeners();
}
public void setOptions(Map<String, String> options) {
this.myOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
this.myOptions.putAll(options);
}
/**
* Returns the options for this board This reflects the options selected
* through the menu functionality in the boards.txt
*
* @return a map of case insensitive ordered key value pairs
*/
public Map<String, String> getOptions() {
return this.myOptions;
}
// private void setMenuOptions(ICConfigurationDescription confdesc) {
// String store =menuOptionsToString();
// try {
// confdesc.getProjectDescription().getProject().setPersistentProperty(this.optionsStorageQualifiedName,
// store);
// } catch (CoreException e) {
// e.printStackTrace();
// }
//
// }
public String getBoardID() {
return this.myBoardID;
}
public String[] getCompatibleBoards() {
return this.myTxtFile.getAllNames();
}
public String[] getUploadProtocols() {
if (this.myBoardsFile.exists()) {
return Programmers.getUploadProtocols(this.myBoardsFile.toString());
}
return new String[0];
}
public String[] getMenuItemNamesFromMenuID(String menuID) {
return this.myTxtFile.getMenuItemNamesFromMenuID(menuID, this.myBoardID);
}
public Set<String> getAllMenuNames() {
return this.myTxtFile.getMenuNames();
}
public TreeMap<String, IPath> getAllExamples() {
return BoardsManager.getAllExamples(this);
}
public void addChangeListener(ChangeListener l) {
this.myChangeListeners = l;
}
public void removeChangeListener() {
this.myChangeListeners = null;
}
private void informChangeListeners() {
if (this.myChangeListeners != null) {
this.myChangeListeners.stateChanged(null);
}
}
public String getMenuIdFromMenuName(String menuName) {
return this.myTxtFile.getMenuIDFromMenuName(menuName);
}
public String getMenuNameFromMenuID(String id) {
return this.myTxtFile.getMenuNameFromID(id);
}
public String getMenuItemNamedFromMenuItemID(String menuItemID, String menuID) {
return this.myTxtFile.getMenuItemNameFromMenuItemID(this.myBoardID, menuID, menuItemID);
}
/**
* convert the options to a string so it can be stored
*
* @return a string representation of the options
*/
private String menuOptionsToString() {
String ret = new String();
String concat = new String();
if (this.myOptions != null) {
for (Entry<String, String> curOption : this.myOptions.entrySet()) {
ret += concat + curOption.getKey() + '=' + curOption.getValue();
concat = "\n";
}
}
return ret;
}
/**
* convert a string to a options so it can be read from a string based
* storage
*
* @param options
*/
private void menuOptionsFromString(String options) {
this.myOptions = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
if (options != null) {
String[] lines = options.split("\n");
for (String curLine : lines) {
String[] values = curLine.split("=", 2);
if (values.length == 2) {
this.myOptions.put(values[0], values[1]);
}
}
}
}
public String getMenuItemIDFromMenuItemName(String menuItemName, String menuID) {
return this.myTxtFile.getMenuItemIDFromMenuItemName(this.myBoardID, menuID, menuItemName);
}
public static String getUploadPort(IProject project) {
return Common.getBuildEnvironmentVariable(project, ENV_KEY_JANTJE_UPLOAD_PORT, new String());
}
public static void storeUploadPort(IProject project, String uploadPort) {
Common.setBuildEnvironmentVariable(project, ENV_KEY_JANTJE_UPLOAD_PORT, uploadPort);
}
public String getMyOSName() {
return this.myOSName;
}
public String getMyWorkSpaceLocation() {
return this.myWorkSpaceLocation;
}
public String getMyWorkEclipseLocation() {
return this.myWorkEclipseLocation;
}
public String getProjectName() {
return this.myProjectName;
}
}