/* XLogo4Schools - A Logo Interpreter specialized for use in schools, based on XLogo by Loic Le Coq * Copyright (C) 2013 Marko Zivkovic * * Contact Information: marko88zivkovic at gmail dot com * * This program 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. This program 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 this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * * This Java source code belongs to XLogo4Schools, written by Marko Zivkovic * during his Bachelor thesis at the computer science department of ETH Zurich, * in the year 2013 and/or during future work. * * It is a reengineered version of XLogo written by Loic Le Coq, published * under the GPL License at http://xlogo.tuxfamily.org/ * * Contents of this file were entirely written by Marko Zivkovic */ package xlogo.kernel.userspace.context; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import xlogo.Logo; import xlogo.interfaces.ProcedureMapper.ProcedureMapListener; import xlogo.kernel.gui.GuiMap; import xlogo.kernel.userspace.GlobalVariableTable; import xlogo.kernel.userspace.PropertyListTable; import xlogo.kernel.userspace.files.LogoFile; import xlogo.kernel.userspace.procedures.Procedure; import xlogo.storage.WSManager; import xlogo.storage.global.GlobalConfig; /** * A LogoContext contains all the symbol tables for execution of Logo programs

* Parts of this resemble the old Workspace class, but this has a new purpose and it is used differently. * In XLogo4Schools, a LogoContext is only a container for a Logo environment - all the symbol tables. *

* Note: To be consistent, the execution stack should be included in the context too, but this would (for now) need too much work * to refactor the existing interpreter. It is for sure something that should be done in the future, when the entire interpreter is refactored. * * @author Marko Zivkovic - * @author Loic Le Coq - methods inherited from XLogo are marked with author-tag */ public class LogoContext { private LogoFile openFile; private final GlobalVariableTable globals = new GlobalVariableTable(); private final PropertyListTable propertyLists = new PropertyListTable(); private final Map files = new HashMap(); private final HashMap> procedureTable = new HashMap>(); // private Map executables = new HashMap(); private final GuiMap guiMap = new GuiMap(); public LogoContext() { } /* * Symbol table getters. */ /** * All the files in this context * @return */ public Map getFilesTable() { return files; } /** * All Executable procedures from all files. Procedures with equal names, * from different files, will be included in the same list.
* This Map is used to keep track of all defined procedures and to resolve * name conflicts. */ public HashMap> getProcedureTable() { return procedureTable; } /** * Global Logo variables * @return */ public GlobalVariableTable getGlobals() { return globals; } /** * Logo property lists * @return */ public PropertyListTable getPropertyLists() { return propertyLists; } /** * For all Gui Objects (Buttons, ComboBoxes...) */ public GuiMap getGuiMap() { return guiMap; } public boolean hasTooManyEmptyFiles(){ int max = WSManager.getWorkspaceConfig().getMaxEmptyFiles(); int count = 0; for(LogoFile file: files.values()){ if (file.isEmpty()){ count++; if(count >= max){ return true; } } } return false; } /* * Context dependent operations */ public String[] getFileOrder() { return files.keySet().toArray(new String[files.size()]); } public void openFile(String fileName) { openFile = files.get(fileName); } public void closeFile() { openFile = null; } public LogoFile getOpenFile() { return openFile; } /** * This is the preferred method to create a file within a context, because different contexts prefer different LogoFile configurations. * @throws IllegalStateException if fileName already exists in the files table * @throws IOException If the file could not be created on the file system and the text not written. */ public void createFile(String fileName, String text) throws IOException { if (files.containsKey(fileName)) throw new IllegalStateException("Attempt to create already existing file."); LogoFile file = LogoFile.createNewFile(fileName); file.setText(text); file.store(); files.put(fileName, file); installListeners(file); } public void importFile(File path, String newFileName) throws IOException { if (files.containsKey(newFileName)) throw new IllegalStateException("Attempt to create already existing file."); if (!path.isFile()) throw new IllegalArgumentException("The specified file does not exist : " + path.toString()); String extension = GlobalConfig.LOGO_FILE_EXTENSION; if(!path.getName().endsWith(extension)) throw new IllegalArgumentException("Only accept " + extension + " files, but received : " + path.toString()); String fileName = path.getName(); fileName = path.getName().substring(0, fileName.length() - extension.length()); LogoFile file = LogoFile.importFile(path, newFileName); files.put(newFileName, file); installListeners(file); } /** * Note: does not perform checks on validity of file names. * @param oldName * @param newName */ public void renameFile(String oldName, String newName) { if (oldName.equals(newName)) return; LogoFile file = files.get(oldName); // must first re-map in files table because file.setFileName fires events files.remove(oldName); files.put(newName, file); file.setFileName(newName); } private final ArrayList procedureMapListener = new ArrayList(); /** * This should be used when a file is created or added to this context. * Listeners that were previously added with {@link #addProcedureMapListener(ProcedureMapListener)} will be installed. * @param file */ public void installListeners(LogoFile file) { for (ProcedureMapListener listener : procedureMapListener) file.addProcedureMapListener(listener); } /** * This helps the Procedure Manager to register to ProcedureMap events from the logo files. * The ProcedureManager does not need to be notified explicitly when a new file is created. * @see #installListeners(LogoFile) * @param listener */ public void addProcedureMapListener(ProcedureMapListener listener) { procedureMapListener.add(listener); for (LogoFile file : files.values()) file.addProcedureMapListener(listener); } public void removeProcedureMapListener(ProcedureMapListener listener) { procedureMapListener.remove(listener); for (LogoFile file : files.values()) file.removeProcedureMapListener(listener); } /* * MISC */ /** * That's the String that is sent via TCP, and interpreted by the receiver in {@link NetworkContext}, * using {@link #setWorkspace(String)} * @author Loic Le Coq * @author Marko Zivkovic * refactored using the new data structures. */ public String toString() { StringBuffer sb = new StringBuffer(); for (String key : getGlobals().getVariables()) { sb.append("-"); sb.append(key); sb.append("\n"); sb.append(getGlobals().getValue(key)); sb.append("\n"); } for (HashMap fileToProc : procedureTable.values()) { if (fileToProc.size() != 1) continue; Procedure procedure = null; for (Procedure uniqueProc : fileToProc.values()) procedure = uniqueProc; // retrieve the only procedure in fileToProc sb.append(Logo.messages.getString("pour") + " " + procedure.name); for (int j = 0; j < procedure.nbparametre; j++) { sb.append(" :" + procedure.variable.get(j)); } sb.append("\n"); sb.append(procedure.instruction); sb.append(Logo.messages.getString("fin")); sb.append("\n\n"); } return (new String(sb)); } /** * @return true : {@link FileContainerChangeListener} should be fired. */ public boolean fireFileEvents() { return true; } /** * @return true : {@link AmbiguityListener}, * {@link ExecutablesChangedListener}, * {@link ProceduresDefinedListener} should be fired. */ public boolean fireProcedureEvents() { return true; } /** * Default is true. Other contexts might override this. * @return Whether it is allowed to create, delete, or rename files. * @note : this is only a suggestion for the files manager and the gui, modification of the table is still possible */ public boolean isFilesListEditAllowed() { return true; } }