/*******************************************************************************
* This file is part of logisim-evolution.
*
* logisim-evolution 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 3 of the License, or
* (at your option) any later version.
*
* logisim-evolution 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 logisim-evolution. If not, see <http://www.gnu.org/licenses/>.
*
* Original code by Carl Burch (http://www.cburch.com), 2011.
* Subsequent modifications by :
* + Haute École Spécialisée Bernoise
* http://www.bfh.ch
* + Haute École du paysage, d'ingénierie et d'architecture de Genève
* http://hepia.hesge.ch/
* + Haute École d'Ingénierie et de Gestion du Canton de Vaud
* http://www.heig-vd.ch/
* The project is currently maintained by :
* + REDS Institute - HEIG-VD
* Yverdon-les-Bains, Switzerland
* http://reds.heig-vd.ch
*******************************************************************************/
package com.cburch.logisim.file;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JOptionPane;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import org.xml.sax.SAXException;
import com.cburch.logisim.circuit.Circuit;
import com.cburch.logisim.circuit.CircuitAttributes;
import com.cburch.logisim.circuit.CircuitEvent;
import com.cburch.logisim.circuit.CircuitListener;
import com.cburch.logisim.circuit.SubcircuitFactory;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.comp.ComponentFactory;
import com.cburch.logisim.proj.Project;
import com.cburch.logisim.proj.Projects;
import com.cburch.logisim.tools.AddTool;
import com.cburch.logisim.tools.Library;
import com.cburch.logisim.tools.Tool;
import com.cburch.logisim.util.EventSourceWeakSupport;
import com.cburch.logisim.util.ListUtil;
import com.cburch.logisim.util.StringUtil;
public class LogisimFile extends Library implements LibraryEventSource,CircuitListener {
public void circuitChanged(CircuitEvent event) {
int act = event.getAction();
if (act == CircuitEvent.ACTION_CHECK_NAME) {
String oldname = (String) event.getData();
String newname = event.getCircuit().getName();
if (NameIsInUse(newname,event.getCircuit())) {
JOptionPane.showMessageDialog(null, "\""+newname+"\": "+Strings.get("circuitNameExists"));
event.getCircuit().getStaticAttributes().setValue(CircuitAttributes.NAME_ATTR, (String) oldname);;
}
}
}
// Name check Methods
private boolean NameIsInUse(String Name,Circuit changed) {
if (Name.isEmpty())
return false;
for (Library mylib : getLibraries()) {
if (NameIsInLibraries(mylib,Name))
return true;
}
for (Circuit mytool : this.getCircuits()) {
if (Name.toUpperCase().equals(mytool.getName().toUpperCase())&&
!mytool.equals(changed))
return true;
}
return false;
}
private boolean NameIsInLibraries(Library lib, String Name) {
if (Name.isEmpty())
return false;
for (Library mylib : lib.getLibraries()) {
if (NameIsInLibraries(mylib,Name))
return true;
}
for (Tool mytool : lib.getTools()) {
if (Name.toUpperCase().equals(mytool.getName().toUpperCase()))
return true;
}
return false;
}
private static class WritingThread extends Thread {
OutputStream out;
LogisimFile file;
WritingThread(OutputStream out, LogisimFile file) {
this.out = out;
this.file = file;
}
@Override
public void run() {
try {
file.write(out, file.loader);
} catch (IOException e) {
file.loader.showError(StringUtil.format(
Strings.get("fileDuplicateError"), e.toString()));
}
try {
out.close();
} catch (IOException e) {
file.loader.showError(StringUtil.format(
Strings.get("fileDuplicateError"), e.toString()));
}
}
}
//
// creation methods
//
public static LogisimFile createNew(Loader loader, Project proj) {
LogisimFile ret = new LogisimFile(loader);
ret.main = new Circuit("main", ret,proj);
// The name will be changed in LogisimPreferences
ret.tools.add(new AddTool(ret.main.getSubcircuitFactory()));
return ret;
}
private static String getFirstLine(BufferedInputStream in)
throws IOException {
byte[] first = new byte[512];
in.mark(first.length - 1);
in.read(first);
in.reset();
int lineBreak = first.length;
for (int i = 0; i < lineBreak; i++) {
if (first[i] == '\n') {
lineBreak = i;
}
}
return new String(first, 0, lineBreak, "UTF-8");
}
public static LogisimFile load(File file, Loader loader) throws IOException {
InputStream in = new FileInputStream(file);
Throwable firstExcept = null;
try {
return loadSub(in, loader, file);
} catch (Throwable t) {
firstExcept = t;
} finally {
in.close();
}
if (firstExcept != null) {
// We'll now try to do it using a reader. This is to work around
// Logisim versions prior to 2.5.1, when files were not saved using
// UTF-8 as the encoding (though the XML file reported otherwise).
try {
in = new ReaderInputStream(new FileReader(file), "UTF8");
return loadSub(in, loader, file);
} catch (Exception t) {
loader.showError(StringUtil.format(
Strings.get("xmlFormatError"), firstExcept.toString()));
} finally {
try {
in.close();
} catch (Exception t) {
}
}
}
return null;
}
public static LogisimFile load(InputStream in, Loader loader)
throws IOException {
try {
return loadSub(in, loader);
} catch (SAXException e) {
loader.showError(StringUtil.format(Strings.get("xmlFormatError"),
e.toString()));
return null;
}
}
public static LogisimFile loadSub(InputStream in, Loader loader)
throws IOException, SAXException {
return (loadSub(in, loader, null));
}
public static LogisimFile loadSub(InputStream in, Loader loader, File file)
throws IOException, SAXException {
// fetch first line and then reset
BufferedInputStream inBuffered = new BufferedInputStream(in);
String firstLine = getFirstLine(inBuffered);
if (firstLine == null) {
throw new IOException("File is empty");
} else if (firstLine.equals("Logisim v1.0")) {
// if this is a 1.0 file, then set up a pipe to translate to
// 2.0 and then interpret as a 2.0 file
throw new IOException("Version 1.0 files no longer supported");
}
XmlReader xmlReader = new XmlReader(loader, file);
/* Can set the project pointer to zero as it is fixed later */
LogisimFile ret = xmlReader.readLibrary(inBuffered,null);
ret.loader = loader;
return ret;
}
private EventSourceWeakSupport<LibraryListener> listeners = new EventSourceWeakSupport<LibraryListener>();
private Loader loader;
private LinkedList<String> messages = new LinkedList<String>();
private Options options = new Options();
private LinkedList<AddTool> tools = new LinkedList<AddTool>();
private LinkedList<Library> libraries = new LinkedList<Library>();
private Circuit main = null;
private String name;
private boolean dirty = false;
LogisimFile(Loader loader) {
this.loader = loader;
// Creates the default project name, adding an underscore if needed
name = Strings.get("defaultProjectName");
if (Projects.windowNamed(name)) {
for (int i = 2; true; i++) {
if (!Projects.windowNamed(name + "_" + i)) {
name += "_" + i;
break;
}
}
}
}
public void addCircuit(Circuit circuit) {
addCircuit(circuit, tools.size());
}
public void addCircuit(Circuit circuit, int index) {
circuit.addCircuitListener(this);
AddTool tool = new AddTool(circuit.getSubcircuitFactory());
tools.add(index, tool);
if (tools.size() == 1)
setMainCircuit(circuit);
fireEvent(LibraryEvent.ADD_TOOL, tool);
}
public void addLibrary(Library lib) {
libraries.add(lib);
fireEvent(LibraryEvent.ADD_LIBRARY, lib);
}
//
// listener methods
//
public void addLibraryListener(LibraryListener what) {
listeners.add(what);
}
//
// modification actions
//
public void addMessage(String msg) {
messages.addLast(msg);
}
@SuppressWarnings("resource")
public LogisimFile cloneLogisimFile(Loader newloader) {
PipedInputStream reader = new PipedInputStream();
PipedOutputStream writer = new PipedOutputStream();
try {
reader.connect(writer);
} catch (IOException e) {
newloader.showError(StringUtil.format(
Strings.get("fileDuplicateError"), e.toString()));
return null;
}
new WritingThread(writer, this).start();
try {
return LogisimFile.load(reader, newloader);
} catch (IOException e) {
newloader.showError(StringUtil.format(
Strings.get("fileDuplicateError"), e.toString()));
try {
reader.close();
} catch (IOException e1) {
}
return null;
}
}
public boolean contains(Circuit circ) {
for (AddTool tool : tools) {
SubcircuitFactory factory = (SubcircuitFactory) tool.getFactory();
if (factory.getSubcircuit() == circ)
return true;
}
return false;
}
private Tool findTool(Library lib, Tool query) {
for (Tool tool : lib.getTools()) {
if (tool.equals(query))
return tool;
}
return null;
}
Tool findTool(Tool query) {
for (Library lib : getLibraries()) {
Tool ret = findTool(lib, query);
if (ret != null)
return ret;
}
return null;
}
private void fireEvent(int action, Object data) {
LibraryEvent e = new LibraryEvent(this, action, data);
for (LibraryListener l : listeners) {
l.libraryChanged(e);
}
}
public AddTool getAddTool(Circuit circ) {
for (AddTool tool : tools) {
SubcircuitFactory factory = (SubcircuitFactory) tool.getFactory();
if (factory.getSubcircuit() == circ) {
return tool;
}
}
return null;
}
public Circuit getCircuit(String name) {
if (name == null)
return null;
for (AddTool tool : tools) {
SubcircuitFactory factory = (SubcircuitFactory) tool.getFactory();
if (name.equals(factory.getName()))
return factory.getSubcircuit();
}
return null;
}
public int getCircuitCount() {
return tools.size();
}
public List<Circuit> getCircuits() {
List<Circuit> ret = new ArrayList<Circuit>(tools.size());
for (AddTool tool : tools) {
SubcircuitFactory factory = (SubcircuitFactory) tool.getFactory();
ret.add(factory.getSubcircuit());
}
return ret;
}
@Override
public List<?> getElements() {
return ListUtil.joinImmutableLists(tools, libraries);
}
@Override
public List<Library> getLibraries() {
return libraries;
}
public boolean removeLibrary(String Name) {
int index = -1;
for (Library lib : libraries)
if (lib.getName().equals(Name))
index = libraries.indexOf(lib);
if (index < 0)
return false;
libraries.remove(index);
return true;
}
public Loader getLoader() {
return loader;
}
public Circuit getMainCircuit() {
return main;
}
public String getMessage() {
if (messages.size() == 0)
return null;
return messages.removeFirst();
}
//
// access methods
//
@Override
public String getName() {
return name;
}
public Options getOptions() {
return options;
}
@Override
public List<AddTool> getTools() {
return tools;
}
public String getUnloadLibraryMessage(Library lib) {
HashSet<ComponentFactory> factories = new HashSet<ComponentFactory>();
for (Tool tool : lib.getTools()) {
if (tool instanceof AddTool) {
factories.add(((AddTool) tool).getFactory());
}
}
for (Circuit circuit : getCircuits()) {
for (Component comp : circuit.getNonWires()) {
if (factories.contains(comp.getFactory())) {
return StringUtil.format(Strings.get("unloadUsedError"),
circuit.getName());
}
}
}
ToolbarData tb = options.getToolbarData();
MouseMappings mm = options.getMouseMappings();
for (Tool t : lib.getTools()) {
if (tb.usesToolFromSource(t)) {
return Strings.get("unloadToolbarError");
}
if (mm.usesToolFromSource(t)) {
return Strings.get("unloadMappingError");
}
}
return null;
}
@Override
public boolean isDirty() {
return dirty;
}
public void moveCircuit(AddTool tool, int index) {
int oldIndex = tools.indexOf(tool);
if (oldIndex < 0) {
tools.add(index, tool);
fireEvent(LibraryEvent.ADD_TOOL, tool);
} else {
AddTool value = tools.remove(oldIndex);
tools.add(index, value);
fireEvent(LibraryEvent.MOVE_TOOL, tool);
}
}
public void removeCircuit(Circuit circuit) {
if (tools.size() <= 1) {
throw new RuntimeException("Cannot remove last circuit");
}
int index = getCircuits().indexOf(circuit);
if (index >= 0) {
Tool circuitTool = tools.remove(index);
if (main == circuit) {
AddTool dflt_tool = tools.get(0);
SubcircuitFactory factory = (SubcircuitFactory) dflt_tool
.getFactory();
setMainCircuit(factory.getSubcircuit());
}
fireEvent(LibraryEvent.REMOVE_TOOL, circuitTool);
}
}
public void removeLibrary(Library lib) {
libraries.remove(lib);
fireEvent(LibraryEvent.REMOVE_LIBRARY, lib);
}
public void removeLibraryListener(LibraryListener what) {
listeners.remove(what);
}
public void setDirty(boolean value) {
if (dirty != value) {
dirty = value;
fireEvent(LibraryEvent.DIRTY_STATE, value ? Boolean.TRUE
: Boolean.FALSE);
}
}
public void setMainCircuit(Circuit circuit) {
if (circuit == null)
return;
this.main = circuit;
fireEvent(LibraryEvent.SET_MAIN, circuit);
}
public void setName(String name) {
this.name = name;
fireEvent(LibraryEvent.SET_NAME, name);
}
//
// other methods
//
void write(OutputStream out, LibraryLoader loader) throws IOException {
write(out, loader, null);
}
void write(OutputStream out, LibraryLoader loader, File dest)
throws IOException {
try {
XmlWriter.write(this, out, loader, dest);
} catch (TransformerConfigurationException e) {
loader.showError("internal error configuring transformer");
} catch (ParserConfigurationException e) {
loader.showError("internal error configuring parser");
} catch (TransformerException e) {
String msg = e.getMessage();
String err = Strings.get("xmlConversionError");
if (msg == null)
err += ": " + msg;
loader.showError(err);
}
}
}