package net.sf.openrocket.document;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.MessageFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sf.openrocket.appearance.DecalImage;
import net.sf.openrocket.document.attachments.FileSystemAttachment;
import net.sf.openrocket.util.BugException;
import net.sf.openrocket.util.ChangeSource;
import net.sf.openrocket.util.FileUtils;
import net.sf.openrocket.util.StateChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DecalRegistry {
private static Logger log = LoggerFactory.getLogger(DecalRegistry.class);
DecalRegistry() {
}
private Map<String, DecalImageImpl> registeredDecals = new HashMap<String, DecalImageImpl>();
public DecalImage makeUniqueImage(DecalImage original) {
if (!(original instanceof DecalImageImpl)) {
return original;
}
DecalImageImpl o = (DecalImageImpl) original;
DecalImageImpl newDecal = o.clone();
String newName = makeUniqueName(o.getName());
// Return the old decal if a new one isn't required.
if (newName.equals(o.getName())) {
return original;
}
newDecal.name = newName;
registeredDecals.put(newName, newDecal);
return newDecal;
}
public DecalImage getDecalImage(Attachment attachment) {
String decalName = attachment.getName();
DecalImageImpl d;
if (attachment instanceof FileSystemAttachment) {
File location = ((FileSystemAttachment) attachment).getLocation();
d = findDecalForFile(location);
if (d != null) {
return d;
}
// It's a new file, generate a name for it.
decalName = makeUniqueName(location.getName());
d = new DecalImageImpl(decalName, attachment);
d.setFileSystemLocation(location);
registeredDecals.put(decalName, d);
return d;
} else {
d = registeredDecals.get(decalName);
if (d != null) {
return d;
}
}
d = new DecalImageImpl(attachment);
registeredDecals.put(decalName, d);
return d;
}
public Collection<DecalImage> getDecalList() {
Set<DecalImage> decals = new TreeSet<DecalImage>();
decals.addAll(registeredDecals.values());
return decals;
}
public class DecalImageImpl implements DecalImage, Cloneable, Comparable<DecalImage>, ChangeSource {
private final Attachment delegate;
private String name;
private File fileSystemLocation;
private DecalImageImpl(String name, Attachment delegate) {
this.name = name;
this.delegate = delegate;
}
private DecalImageImpl(Attachment delegate) {
this.delegate = delegate;
}
public String getName() {
return name != null ? name : delegate.getName();
}
@Override
public void fireChangeEvent(Object source) {
delegate.fireChangeEvent(source);
}
/**
* This function returns an InputStream backed by a byte[] containing the decal pixels.
* If it reads in the bytes from an actual file, the underlying file is closed.
*
* @return InputStream containing byte[] of the image
* @throws FileNotFoundException
* @throws IOException
*/
public InputStream getBytes() throws FileNotFoundException, IOException {
// First check if the decal is located on the file system
File exportedFile = getFileSystemLocation();
if (exportedFile != null) {
InputStream rawIs = new FileInputStream(exportedFile);
try {
byte[] bytes = FileUtils.readBytes(rawIs);
return new ByteArrayInputStream(bytes);
} finally {
rawIs.close();
}
}
return delegate.getBytes();
}
@Override
public void exportImage(File file) throws IOException {
try {
InputStream is = getBytes();
OutputStream os = new BufferedOutputStream(new FileOutputStream(file));
FileUtils.copy(is, os);
is.close();
os.close();
this.fileSystemLocation = file;
} catch (IOException iex) {
throw new BugException(iex);
}
}
File getFileSystemLocation() {
return fileSystemLocation;
}
void setFileSystemLocation(File fileSystemLocation) {
this.fileSystemLocation = fileSystemLocation;
}
@Override
public String toString() {
return getName();
}
@Override
public int compareTo(DecalImage o) {
return getName().compareTo(o.getName());
}
@Override
protected DecalImageImpl clone() {
DecalImageImpl clone = new DecalImageImpl(this.delegate);
clone.fileSystemLocation = this.fileSystemLocation;
return clone;
}
@Override
public void addChangeListener(StateChangeListener listener) {
delegate.addChangeListener(listener);
}
@Override
public void removeChangeListener(StateChangeListener listener) {
delegate.removeChangeListener(listener);
}
}
private DecalImageImpl findDecalForFile(File file) {
for (DecalImageImpl d : registeredDecals.values()) {
if (file.equals(d.getFileSystemLocation())) {
return d;
}
}
return null;
}
/**
* Regular expression for parsing file names with numerical identifiers.
* For examples:
*
* decals/an image (3).png
*
* group(0) = "decals/an image (3).png"
* group(1) = "decals/an image"
* group(2) = " (3)"
* group(3) = "3"
* group(4) = "png"
*
* decals/an image.png
*
* group(0) = "decals/an image.png"
* group(1) = "decals/an image"
* group(2) = "null"
* group(3) = "null"
* group(4) = "png"
*/
private static final Pattern fileNamePattern = Pattern.compile("(.*?)( \\((\\d+)\\)+)?\\.(\\w*)");
private static final int BASE_NAME_INDEX = 1;
private static final int NUMBER_INDEX = 3;
private static final int EXTENSION_INDEX = 4;
private String makeUniqueName(String name) {
String newName = name;
if (!newName.startsWith("decals/")) {
newName = "decals/" + name;
}
String basename = "";
String extension = "";
Matcher nameMatcher = fileNamePattern.matcher(newName);
if (nameMatcher.matches()) {
basename = nameMatcher.group(BASE_NAME_INDEX);
extension = nameMatcher.group(EXTENSION_INDEX);
}
Set<Integer> counts = new TreeSet<Integer>();
boolean needsRewrite = false;
for (DecalImageImpl d : registeredDecals.values()) {
Matcher m = fileNamePattern.matcher(d.getName());
if (m.matches()) {
if (basename.equals(m.group(BASE_NAME_INDEX)) && extension.equals(m.group(EXTENSION_INDEX))) {
String intString = m.group(NUMBER_INDEX);
if (intString != null) {
Integer i = Integer.parseInt(intString);
counts.add(i);
}
needsRewrite = true;
}
} else if (newName.equals(d.getName())) {
needsRewrite = true;
}
}
if (!needsRewrite) {
return newName;
}
// find a missing integer;
Integer newIndex = 1;
while (counts.contains(newIndex)) {
newIndex++;
}
return MessageFormat.format("{0} ({1}).{2}", basename, newIndex, extension);
}
}