+/*******************************************************************************\r
+ * Copyright (c) 2010- Association for Decentralized Information Management in\r
+ * Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ * \r
+ * Contributors:\r
+ * VTT Technical Research Centre of Finland - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.databoard.binding.impl;\r
+\r
+import java.io.IOException;\r
+import java.nio.ByteBuffer;\r
+import java.util.Set;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.Datatypes;\r
+import org.simantics.databoard.binding.Binding;\r
+import org.simantics.databoard.binding.IntegerBinding;\r
+import org.simantics.databoard.binding.LongBinding;\r
+import org.simantics.databoard.binding.StringBinding;\r
+import org.simantics.databoard.binding.VariantBinding;\r
+import org.simantics.databoard.binding.error.BindingException;\r
+import org.simantics.databoard.binding.mutable.MutableVariant;\r
+import org.simantics.databoard.serialization.RuntimeSerializerConstructionException;\r
+import org.simantics.databoard.serialization.Serializer;\r
+import org.simantics.databoard.serialization.SerializerConstructionException;\r
+import org.simantics.databoard.serialization.SerializerFactory;\r
+import org.simantics.databoard.type.Datatype;\r
+import org.simantics.databoard.util.Base64;\r
+import org.simantics.databoard.util.URIUtil;\r
+import org.simantics.databoard.util.binary.BinaryMemory;\r
+import org.simantics.databoard.util.binary.BinaryReadable;\r
+\r
+/**\r
+ * This class binding Variant to a filename/URL compatible String.\r
+ * The value is human-readable for strings, integers and longs. \r
+ * For datatypes the value is Base64 encoded binary.\r
+ * \r
+ * Filenames have the following encoding:\r
+ * S<string> String types, if string doesn't have the following \r
+ * control characters " : < > | ? * # \ / % [0..31] [128..] are escaped with %<hex><hex>\r
+ * I<integer> Integer types\r
+ * L<long> Long types\r
+ * B<base64> All other cases. The value is binary encoded and presented as single line Base64 string.\r
+ * Base64 encoding has url and filename safe options enabled. \r
+ *\r
+ * See StringVariantBindingExample\r
+ * @author Toni Kalajainen <toni.kalajainen@vtt.fi>\r
+ */\r
+public class StringVariantBinding extends VariantBinding {\r
+ \r
+ SerializerFactory serializationFactory;\r
+ VariantBinding variantBinding;\r
+ \r
+ public StringVariantBinding(SerializerFactory serializationFactory, VariantBinding variantBinding) {\r
+ this.serializationFactory = serializationFactory;\r
+ this.variantBinding = variantBinding;\r
+ }\r
+ \r
+ @Override\r
+ public Object create(Binding binding, Object value) throws BindingException {\r
+ if (binding instanceof StringBinding && binding.type().equals(Datatypes.STRING)) {\r
+ StringBinding sb = (StringBinding) binding;\r
+ return "S"+URIUtil.encodeURI( sb.getValue(value) );\r
+ }\r
+ \r
+ if (binding instanceof IntegerBinding && binding.type().equals(Datatypes.INTEGER)) {\r
+ IntegerBinding ib = (IntegerBinding) binding;\r
+ return "I"+ib.getValue_(value);\r
+ }\r
+ \r
+ if (binding instanceof LongBinding && binding.type().equals(Datatypes.LONG)) {\r
+ LongBinding lb = (LongBinding) binding;\r
+ return "L"+lb.getValue_(value);\r
+ }\r
+ \r
+ try {\r
+ MutableVariant v = new MutableVariant(binding, value);\r
+ byte[] data = serializationFactory.getSerializerUnchecked( variantBinding ).serialize(v);\r
+ return "B"+Base64.encodeBytes(data, Base64.URL_SAFE);\r
+ } catch (RuntimeSerializerConstructionException e) {\r
+ throw new BindingException(e);\r
+ } catch (IOException e) {\r
+ throw new BindingException(e);\r
+ }\r
+ }\r
+\r
+ @Override\r
+ public Binding getContentBinding(Object variant) throws BindingException {\r
+ String str = (String) variant;\r
+ if (str.startsWith("S")) {\r
+ return Bindings.STRING;\r
+ }\r
+ if (str.startsWith("I")) {\r
+ return Bindings.INTEGER;\r
+ }\r
+ if (str.startsWith("L")) {\r
+ return Bindings.LONG;\r
+ }\r
+ if (str.startsWith("B")) {\r
+ try {\r
+ byte[] data = Base64.decode(str.substring(1), Base64.URL_SAFE);\r
+ BinaryReadable readable = new BinaryMemory( ByteBuffer.wrap(data) ) ;\r
+ Datatype type = (Datatype) getDatatypeSerializer().deserialize(readable);\r
+ return Bindings.getMutableBinding(type);\r
+ } catch (IOException e) {\r
+ throw new BindingException(e);\r
+ } \r
+ }\r
+ \r
+ throw new BindingException("Invalid string");\r
+ }\r
+\r
+ @Override\r
+ public Datatype getContentType(Object variant) throws BindingException {\r
+ String str = (String) variant;\r
+ if (str.startsWith("S")) {\r
+ return Bindings.STRING.type();\r
+ }\r
+ if (str.startsWith("I")) {\r
+ return Bindings.INTEGER.type();\r
+ }\r
+ if (str.startsWith("L")) {\r
+ return Bindings.LONG.type();\r
+ }\r
+ if (str.startsWith("B")) {\r
+ try {\r
+ byte[] data = Base64.decode(str.substring(1), Base64.URL_SAFE);\r
+ BinaryReadable readable = new BinaryMemory( ByteBuffer.wrap(data) ) ;\r
+ return (Datatype) getDatatypeSerializer().deserialize(readable);\r
+ } catch (IOException e) {\r
+ throw new BindingException(e);\r
+ } \r
+ }\r
+ \r
+ throw new BindingException("Invalid string");\r
+ }\r
+\r
+ @Override\r
+ public Object getContent(Object obj, Binding binding)\r
+ throws BindingException {\r
+ if (obj==null) throw new BindingException("null value is not Variant");\r
+ if (obj instanceof String == false) throw new BindingException("wrong class, String expected");\r
+ \r
+ String str = (String) obj;\r
+ \r
+ if (str.startsWith("S")) {\r
+ if (binding instanceof StringBinding == false) throw new BindingException("StringBinding expected, got "+binding.getClass().getSimpleName());\r
+ String value = URIUtil.decodeURI( str.substring(1) );\r
+ StringBinding sb = (StringBinding) binding;\r
+ return sb.create(value);\r
+ }\r
+ \r
+ if (str.startsWith("I")) {\r
+ if (binding instanceof IntegerBinding == false) throw new BindingException("IntegerBinding expected, got "+binding.getClass().getSimpleName());\r
+ try {\r
+ Integer value = Integer.valueOf( str.substring(1) );\r
+ IntegerBinding ib = (IntegerBinding) binding;\r
+ return ib.create(value); \r
+ } catch (NumberFormatException nfe) {\r
+ throw new BindingException(nfe);\r
+ }\r
+ }\r
+\r
+ if (str.startsWith("L")) {\r
+ if (binding instanceof LongBinding == false) throw new BindingException("LongBinding expected, got "+binding.getClass().getSimpleName());\r
+ try {\r
+ Long value = Long.valueOf( str.substring(1) );\r
+ LongBinding lb = (LongBinding) binding;\r
+ return lb.create(value); \r
+ } catch (NumberFormatException nfe) {\r
+ throw new BindingException(nfe);\r
+ }\r
+ }\r
+ \r
+ if (str.startsWith("B")) {\r
+ try {\r
+ byte[] data = Base64.decode(str.substring(1), Base64.URL_SAFE);\r
+ BinaryReadable readable = new BinaryMemory( ByteBuffer.wrap(data) ) ;\r
+ Datatype type = (Datatype) getDatatypeSerializer().deserialize(readable);\r
+ if (!type.equals(binding.type()))\r
+ throw new BindingException("Binding for "+type.toSingleLineString()+" expected, but got "+binding.type());\r
+ return Bindings.getSerializer( binding ).deserialize(readable);\r
+ } catch (IOException ioe) {\r
+ throw new BindingException(ioe);\r
+ } catch (RuntimeSerializerConstructionException e) {\r
+ throw new BindingException(e);\r
+ } catch (SerializerConstructionException e) {\r
+ throw new BindingException(e);\r
+ }\r
+ }\r
+ \r
+ throw new BindingException("Invalid value"); \r
+ }\r
+\r
+ @Override\r
+ public Object getContent(Object obj) throws BindingException {\r
+ if (obj==null) throw new BindingException("null value is not Variant");\r
+ if (obj instanceof String == false) throw new BindingException("wrong class, String expected");\r
+ \r
+ String str = (String) obj;\r
+ \r
+ if (str.startsWith("S")) {\r
+ /*String value =*/ URIUtil.decodeURI( str.substring(1) ); \r
+ return Bindings.STRING;\r
+ }\r
+ \r
+ if (str.startsWith("I")) {\r
+ try {\r
+ /*Integer value =*/ Integer.valueOf( str.substring(1) );\r
+ return Bindings.INTEGER; \r
+ } catch (NumberFormatException nfe) {\r
+ throw new BindingException(nfe);\r
+ }\r
+ }\r
+\r
+ if (str.startsWith("L")) {\r
+ try {\r
+ /*Long value =*/ Long.valueOf( str.substring(1) );\r
+ return Bindings.LONG; \r
+ } catch (NumberFormatException nfe) {\r
+ throw new BindingException(nfe);\r
+ }\r
+ }\r
+ \r
+ if (str.startsWith("B")) {\r
+ try {\r
+ byte[] data = Base64.decode(str.substring(1), Base64.URL_SAFE);\r
+ BinaryReadable readable = new BinaryMemory( ByteBuffer.wrap(data) );\r
+ MutableVariant variant = (MutableVariant) Bindings.getSerializerUnchecked( Bindings.MUTABLE_VARIANT).deserialize(readable);\r
+ return variant.getValue();\r
+ } catch (IOException ioe) {\r
+ throw new BindingException(ioe);\r
+ } catch (RuntimeSerializerConstructionException e) {\r
+ throw new BindingException(e);\r
+ }\r
+ }\r
+ \r
+ throw new BindingException("Invalid value"); \r
+ }\r
+\r
+ @Override\r
+ public void setContent(Object variant, Binding binding, Object value)\r
+ throws BindingException {\r
+ throw new BindingException("Cannot set value to an immutable String");\r
+ }\r
+\r
+ @Override\r
+ public void assertInstaceIsValid(Object obj, Set<Object> validInstances)\r
+ throws BindingException {\r
+ if (obj==null) throw new BindingException("null value is not Variant");\r
+ if (obj instanceof String == false) throw new BindingException("wrong class, String expected");\r
+ Object value = getContent(obj);\r
+ Binding binding = getContentBinding(obj);\r
+ binding.assertInstaceIsValid(value);\r
+ }\r
+\r
+ @Override\r
+ public boolean isInstance(Object obj) { \r
+ return obj instanceof String;\r
+ }\r
+\r
+ static Serializer DATATYPE_SERIALIZER; \r
+ static Serializer getDatatypeSerializer()\r
+ {\r
+ if (DATATYPE_SERIALIZER == null) DATATYPE_SERIALIZER = Bindings.getSerializerUnchecked( Bindings.getBindingUnchecked(Datatype.class) );\r
+ return DATATYPE_SERIALIZER;\r
+ }\r
+\r
+ @Override\r
+ protected boolean baseEquals(Object obj) {\r
+ StringVariantBinding o = (StringVariantBinding)obj;\r
+ return super.baseEquals(obj) && o.serializationFactory == serializationFactory && o.variantBinding == variantBinding;\r
+ }\r
+}\r