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