package org.swellrt.model.generic; import org.swellrt.model.ReadableBoolean; import org.swellrt.model.ReadableFile; import org.swellrt.model.ReadableNumber; import org.swellrt.model.ReadableTypeVisitor; import org.waveprotocol.wave.media.model.AttachmentId; import org.waveprotocol.wave.model.adt.ObservableBasicValue; import org.waveprotocol.wave.model.id.InvalidIdException; import org.waveprotocol.wave.model.util.CopyOnWriteSet; import org.waveprotocol.wave.model.util.Preconditions; import org.waveprotocol.wave.model.wave.SourcesEvents; public class FileType extends Type implements ReadableFile, SourcesEvents<FileType.Listener> { public static class Value { AttachmentId attachmentId; String contentType; public String serialize() { return attachmentId.serialise() + (contentType == null || contentType.isEmpty() ? "" : "," + contentType); } protected Value(AttachmentId attachmentId, String contentType) { this.attachmentId = attachmentId; this.contentType = contentType; } public static Value deserialize(String valueString) throws InvalidModelStringValue { Preconditions.checkNotNull(valueString, "String value can't be null"); String[] parts = valueString.split(","); Value v = null; try { v = new Value(AttachmentId.deserialise(parts[0]), parts.length > 1 ? parts[1] : null); } catch (InvalidIdException e) { throw new InvalidModelStringValue(); } return v; } public AttachmentId getAttachmentId() { return attachmentId; } public String getContentType() { return contentType; } } public interface Listener { void onValueChanged(AttachmentId oldValue, AttachmentId newValue); } /** * Get an instance of FileType. This method is used for deserialization. * * @param parent the parent Type instance of this string * @param valueIndex the index of the value in the parent's value container * @return */ protected static FileType deserialize(Type parent, String valueIndex) { FileType f = new FileType(); f.attach(parent, valueIndex); return f; } public final static String TYPE_NAME = "FileType"; public final static String PREFIX = "f"; public final static String VALUE_ATTR = "v"; private ObservableBasicValue<String> observableValue; private ObservableBasicValue.Listener<String> observableValueListener = new ObservableBasicValue.Listener<String>() { @Override public void onValueChanged(String oldValue, String newValue) { for (Listener l : listeners) try { l.onValueChanged(AttachmentId.deserialise(oldValue), AttachmentId.deserialise(newValue)); } catch (InvalidIdException e) { // TODO handle exception } } }; private String path; private Type parent; private int valueRef; // the index of this value in the ValuesContainer private Value initValue; /** * The model owning this object. Despite other Types, FileType needs to know * its model before be attached in order to generate URL */ private Model model; private boolean isAttached; private final CopyOnWriteSet<Listener> listeners = CopyOnWriteSet.create(); protected FileType() { this.initValue = null; } public FileType(AttachmentId attachmentId, String contentType, Model model) { this.initValue = attachmentId != null ? new FileType.Value(attachmentId, contentType) : null; this.model = model; } @Override protected String getPrefix() { return PREFIX; } @Override protected void attach(Type parent) { Preconditions.checkArgument(parent.hasValuesContainer(), "Invalid parent type for a primitive value"); this.parent = parent; observableValue = parent.getValuesContainer().add(initValue.serialize()); observableValue.addListener(observableValueListener); valueRef = parent.getValuesContainer().indexOf(observableValue); isAttached = true; } @Override protected void attach(Type parent, int slotIndex) { this.parent = parent; valueRef = slotIndex; if (initValue != null && initValue.attachmentId != null) observableValue = parent.getValuesContainer().add(initValue.serialize(), slotIndex); else observableValue = parent.getValuesContainer().get(slotIndex); if (observableValue == null) { // return a non-attached value // this singals the actual value hasn't been received yet. return; } observableValue.addListener(observableValueListener); isAttached = true; } @Override protected void attach(Type parent, String valueIndex) { Preconditions.checkArgument(parent.hasValuesContainer(), "Invalid parent type for a primitive value"); Integer index = null; try { index = Integer.valueOf(valueIndex); } catch (NumberFormatException e) { } Preconditions.checkNotNull(index, "Value index is null"); attach(parent, index); } protected void deattach() { Preconditions.checkArgument(isAttached, "Unable to deattach an unattached FileType"); observableValue.removeListener(this.observableValueListener); observableValue = null; isAttached = false; } @Override protected boolean isAttached() { return isAttached; } @Override protected String serialize() { Preconditions.checkArgument(isAttached, "Unable to serialize an unattached FileType"); return PREFIX + "+" + Integer.toString(valueRef); } @Override protected ListElementInitializer getListElementInitializer() { return new ListElementInitializer() { @Override public String getType() { return PREFIX; } @Override public String getBackendId() { Preconditions.checkArgument(isAttached, "Unable to initialize an unattached FileType"); return serialize(); } }; } // // Listeners // @Override public void addListener(Listener listener) { listeners.add(listener); } @Override public void removeListener(Listener listener) { listeners.remove(listener); } private boolean reAttach() { if (parent != null && valueRef >= 0) { attach(parent, valueRef); return isAttached(); } return false; } // // File operations // public AttachmentId getValue() { return getFileId(); } public AttachmentId getFileId() { if (!isAttached()) if (!reAttach()) return initValue.attachmentId; String valueString = observableValue.get(); if (valueString == null || valueString.isEmpty()) return null; try { return FileType.Value.deserialize(valueString).attachmentId; } catch (InvalidModelStringValue e) { return null; } } public String getContentType() { if (!isAttached()) return initValue.contentType; else { String valueString = observableValue.get(); if (valueString == null || valueString.isEmpty()) return null; try { return FileType.Value.deserialize(valueString).contentType; } catch (InvalidModelStringValue e) { return null; } } } public void setValue(AttachmentId attachmentId, String contentType) { Preconditions.checkNotNull(attachmentId, "Attachment id can't be null"); if (isAttached()) { FileType.Value v = new FileType.Value(attachmentId, contentType); observableValue.set(v.serialize()); parent.markValueUpdate(this); } } public void setValue(FileType file) { setValue(file.getFileId(), file.getContentType()); } public void clearValue() { if (isAttached()) { observableValue.set(""); parent.markValueUpdate(this); } } @Override public String getDocumentId() { return null; } @Override public String getType() { return TYPE_NAME; } @Override protected void setPath(String path) { this.path = path; } @Override public String getPath() { return path; } @Override protected boolean hasValuesContainer() { return false; } @Override protected ValuesContainer getValuesContainer() { return null; } protected Integer getValueRefefence() { return valueRef; } @Override public Model getModel() { return model == null ? parent.getModel() : model; } @Override public void accept(ReadableTypeVisitor visitor) { visitor.visit(this); } @Override public MapType asMap() { return null; } @Override public StringType asString() { return null; } @Override public ListType asList() { return null; } @Override public TextType asText() { return null; } @Override public FileType asFile() { return this; } @Override public ReadableNumber asNumber() { return null; } @Override public ReadableBoolean asBoolean() { return null; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((parent == null) ? 0 : parent.hashCode()); result = prime * result + valueRef; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; FileType other = (FileType) obj; if (parent == null) { if (other.parent != null) return false; } else if (!parent.equals(other.parent)) return false; if (valueRef != other.valueRef) return false; return true; } }