/* Copyright (c) 2010, Carl Burch. License information is located in the
* com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
package com.cburch.logisim.file;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
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.SubcircuitFactory;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.comp.ComponentFactory;
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 {
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()));
}
}
}
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;
name = Strings.get("defaultProjectName");
if (Projects.windowNamed(name)) {
for (int i = 2; true; i++) {
if (!Projects.windowNamed(name + " " + i)) {
name += " " + i;
break;
}
}
}
}
//
// access methods
//
@Override
public String getName() { return name; }
@Override
public boolean isDirty() { return dirty; }
public String getMessage() {
if (messages.size() == 0) return null;
return messages.removeFirst();
}
public Loader getLoader() {
return loader;
}
public Options getOptions() {
return options;
}
@Override
public List<AddTool> getTools() {
return tools;
}
@Override
public List<Library> getLibraries() {
return libraries;
}
@Override
public List<?> getElements() {
return ListUtil.joinImmutableLists(tools, libraries);
}
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 boolean contains(Circuit circ) {
for (AddTool tool : tools) {
SubcircuitFactory factory = (SubcircuitFactory) tool.getFactory();
if (factory.getSubcircuit() == circ) return true;
}
return false;
}
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;
}
public AddTool getAddTool(Circuit circ) {
for (AddTool tool : tools) {
SubcircuitFactory factory = (SubcircuitFactory) tool.getFactory();
if (factory.getSubcircuit() == circ) {
return tool;
}
}
return null;
}
public Circuit getMainCircuit() {
return main;
}
public int getCircuitCount() {
return tools.size();
}
//
// listener methods
//
public void addLibraryListener(LibraryListener what) {
listeners.add(what);
}
public void removeLibraryListener(LibraryListener what) {
listeners.remove(what);
}
private void fireEvent(int action, Object data) {
LibraryEvent e = new LibraryEvent(this, action, data);
for (LibraryListener l : listeners) {
l.libraryChanged(e);
}
}
//
// modification actions
//
public void addMessage(String msg) {
messages.addLast(msg);
}
public void setDirty(boolean value) {
if (dirty != value) {
dirty = value;
fireEvent(LibraryEvent.DIRTY_STATE, value ? Boolean.TRUE : Boolean.FALSE);
}
}
public void setName(String name) {
this.name = name;
fireEvent(LibraryEvent.SET_NAME, name);
}
public void addCircuit(Circuit circuit) {
addCircuit(circuit, tools.size());
}
public void addCircuit(Circuit circuit, int index) {
AddTool tool = new AddTool(circuit.getSubcircuitFactory());
tools.add(index, tool);
if (tools.size() == 1) setMainCircuit(circuit);
fireEvent(LibraryEvent.ADD_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 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 addLibrary(Library lib) {
libraries.add(lib);
fireEvent(LibraryEvent.ADD_LIBRARY, lib);
}
public void removeLibrary(Library lib) {
libraries.remove(lib);
fireEvent(LibraryEvent.REMOVE_LIBRARY, lib);
}
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;
}
public void setMainCircuit(Circuit circuit) {
if (circuit == null) return;
this.main = circuit;
fireEvent(LibraryEvent.SET_MAIN, circuit);
}
//
// other methods
//
void write(OutputStream out, LibraryLoader loader) throws IOException {
try {
XmlWriter.write(this, out, loader);
} 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);
}
}
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()));
return null;
}
}
Tool findTool(Tool query) {
for (Library lib : getLibraries()) {
Tool ret = findTool(lib, query);
if (ret != null) return ret;
}
return null;
}
private Tool findTool(Library lib, Tool query) {
for (Tool tool : lib.getTools()) {
if (tool.equals(query)) return tool;
}
return null;
}
//
// creation methods
//
public static LogisimFile createNew(Loader loader) {
LogisimFile ret = new LogisimFile(loader);
ret.main = new Circuit("main");
// The name will be changed in LogisimPreferences
ret.tools.add(new AddTool(ret.main.getSubcircuitFactory()));
return ret;
}
public static LogisimFile load(File file, Loader loader)
throws IOException {
InputStream in = new FileInputStream(file);
SAXException firstExcept = null;
try {
return loadSub(in, loader);
} catch (SAXException e) {
firstExcept = e;
} 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);
} catch (Throwable t) {
loader.showError(StringUtil.format(
Strings.get("xmlFormatError"), firstExcept.toString()));
} finally {
try {
in.close();
} catch (Throwable 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 {
// 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);
LogisimFile ret = xmlReader.readLibrary(inBuffered);
ret.loader = loader;
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");
}
}