package ua.stu.scplib.attribute; import java.io.*; import java.text.NumberFormat; /** * <p>An abstract class specializing {@link com.pixelmed.dicom.Attribute Attribute} for * the family of string attributes.</p> * * @author dclunie */ abstract public class StringAttribute extends Attribute { /***/ protected SpecificCharacterSet specificCharacterSet; // always null except for derived classes /** * <p>Get the specific character set for this attribute.</p> * * @return the specific character set, or null if none */ public SpecificCharacterSet getSpecificCharacterSet() { return specificCharacterSet; } /***/ byte[] originalByteValues; /***/ String originalValues[]; /***/ String cachedUnpaddedStringCopy[]; /***/ int[] cachedIntegerCopy; /***/ long[] cachedLongCopy; /***/ float[] cachedFloatCopy; /***/ double[] cachedDoubleCopy; /***/ byte[] cachedPaddedByteValues; /***/ private void flushCachedCopies() { cachedUnpaddedStringCopy=null; cachedIntegerCopy=null; cachedLongCopy=null; cachedFloatCopy=null; cachedDoubleCopy=null; cachedPaddedByteValues=null; } /** * <p>Decode a byte array into a string.</p> * * @param bytes the byte buffer in which the encoded string is located * @param offset the offset into the buffer * @param length the number of bytes to be decoded * @return the string decoded according to the specified or default specific character set */ protected String translateByteArrayToString(byte[] bytes,int offset,int length) { // NOT static //System.err.println("StringAttribute.translateByteArrayToString()"); //System.err.println("StringAttribute.translateByteArrayToString() - specificCharacterSet is "+specificCharacterSet); return specificCharacterSet == null ? new String(bytes,0,length) : specificCharacterSet.translateByteArrayToString(bytes,0,length); } /** * <p>Encode a string into a byte array.</p> * * @param string the string to be encoded * @return the byte array encoded according to the specified or default specific character set * @exception UnsupportedEncodingException */ protected byte[] translateStringToByteArray(String string) throws UnsupportedEncodingException { // NOT static //System.err.println("StringAttribute.translateStringToByteArray() - string is <"+string+">"); //System.err.println("StringAttribute.translateStringToByteArray() - string is "+com.pixelmed.utils.StringUtilities.dump(string)); //System.err.println("StringAttribute.translateStringToByteArray() - specificCharacterSet is "+specificCharacterSet); byte[] b = specificCharacterSet == null ? string.getBytes() : specificCharacterSet.translateStringToByteArray(string); //System.err.println("StringAttribute.translateStringToByteArray(): return byte array is:\n"+com.pixelmed.utils.HexDump.dump(b)); return b; } /** * <p>Construct an (empty) attribute; called only by concrete sub-classes.</p> * * @param t the tag of the attribute */ protected StringAttribute(AttributeTag t) { super(t); doCommonConstructorStuff(null); } /** * <p>Construct an (empty) attribute; called only by concrete sub-classes.</p> * * @param t the tag of the attribute * @param specificCharacterSet the character set to be used for the text */ protected StringAttribute(AttributeTag t,SpecificCharacterSet specificCharacterSet) { super(t); doCommonConstructorStuff(specificCharacterSet); } /** * <p>Read an attribute from an input stream; called only by concrete sub-classes.</p> * * @param t the tag of the attribute * @param vl the value length of the attribute * @param i the input stream * @exception IOException * @exception DicomException */ protected StringAttribute(AttributeTag t,long vl,DicomInputStream i) throws IOException, DicomException { super(t); doCommonConstructorStuff(vl,i,null); } /** * <p>Read an attribute from an input stream; called only by concrete sub-classes.</p> * * @param t the tag of the attribute * @param vl the value length of the attribute * @param i the input stream * @exception IOException * @exception DicomException */ protected StringAttribute(AttributeTag t,Long vl,DicomInputStream i) throws IOException, DicomException { super(t); doCommonConstructorStuff(vl.longValue(),i,null); } /** * <p>Read an attribute from an input stream; called only by concrete sub-classes.</p> * * @param t the tag of the attribute * @param vl the value length of the attribute * @param i the input stream * @param specificCharacterSet the character set to be used for the text * @exception IOException * @exception DicomException */ protected StringAttribute(AttributeTag t,long vl,DicomInputStream i,SpecificCharacterSet specificCharacterSet) throws IOException, DicomException { super(t); doCommonConstructorStuff(vl,i,specificCharacterSet); } /** * <p>Read an attribute from an input stream; called only by concrete sub-classes.</p> * * @param t the tag of the attribute * @param vl the value length of the attribute * @param i the input stream * @param specificCharacterSet the character set to be used for the text * @exception IOException * @exception DicomException */ protected StringAttribute(AttributeTag t,Long vl,DicomInputStream i,SpecificCharacterSet specificCharacterSet) throws IOException, DicomException { super(t); doCommonConstructorStuff(vl.longValue(),i,specificCharacterSet); } /** * <p>Flesh out a constructed (empty) attribute; called only by concrete sub-classes.</p> * * @param specificCharacterSet the character set to be used for the text */ private void doCommonConstructorStuff(SpecificCharacterSet specificCharacterSet) { flushCachedCopies(); this.specificCharacterSet=specificCharacterSet; originalValues=null; originalByteValues=null; } /** * <p>Read a constructed attribute from an input stream; called only by concrete sub-classes.</p> * * @param vl the value length of the attribute * @param i the input stream * @param specificCharacterSet the character set to be used for the text * @exception IOException * @exception DicomException */ private void doCommonConstructorStuff(long vl,DicomInputStream i,SpecificCharacterSet specificCharacterSet) throws IOException, DicomException { doCommonConstructorStuff(specificCharacterSet); originalValues=null; originalByteValues=null; if (vl > 0) { originalByteValues = new byte[(int)vl]; try { i.readInsistently(originalByteValues,0,(int)vl); } catch (IOException e) { throw new DicomException("Failed to read value (length "+vl+" dec) in "+ValueRepresentation.getAsString(getVR())+" attribute "+getTag()); } String sbuf = translateByteArrayToString(originalByteValues,0,(int)vl); // may fail due to unsuported encoding and return null, though should not happen since should use default (000307) if (sbuf != null) { vl=sbuf.length(); // NB. this only makes a difference for multi-byte character sets int start=0; int delim=0; while (true) { if (delim >= vl || sbuf.charAt(delim) == '\\') { addValue(sbuf.substring(start,delim)); ++delim; start=delim; if (delim >= vl) break; } else { ++delim; } } } // else do not add values since translateByteArrayToString failed (probably unsuported encoding), but leave VL alone (>0) in case untranslated original bytes are useful (000307) :( } } /***/ public long getPaddedVL() { byte[] b = null; try { b = getPaddedByteValues(); } catch (DicomException e) { b = null; } return b == null ? 0 : b.length; } /** * <p>Get the appropriate byte for padding a string to an even length.</p> * * @return the byte pad value appropriate to the VR */ protected byte getPadByte() { return 0x20; } // space for most everything, UI will override to 0x00 /** * @exception DicomException */ private byte[] getPaddedByteValues() throws DicomException { if (cachedPaddedByteValues == null) { cachedPaddedByteValues = extractPaddedByteValues(); } return cachedPaddedByteValues; } /** * @exception DicomException */ private byte[] extractPaddedByteValues() throws DicomException { StringBuffer sb = new StringBuffer(); String[] v = getOriginalStringValues(); if (v != null) { for (int j=0; j<v.length; ++j) { if (j > 0) sb.append("\\"); sb.append(v[j]); } } //byte[] b = sb.toString().getBytes(); byte[] b = null; try { b = translateStringToByteArray(sb.toString()); } catch (UnsupportedEncodingException e) { throw new DicomException("Unsupported encoding:"+e); } // should padding take into account character set, i.e. could the pad character be different ? :( if (b != null) { int bl = b.length; if (bl%2 != 0) { byte[] b2 = new byte[bl+1]; System.arraycopy(b,0,b2,0,bl); b2[bl]=getPadByte(); b=b2; } //if (getPaddedVL() != b.length) { // throw new DicomException("Internal error - "+this+" - byte array length ("+b.length+") not equal to expected padded VL("+getPaddedVL()+")"); //} } return b; } /** * @param o * @exception IOException * @exception DicomException */ // public void write(DicomOutputStream o) throws DicomException, IOException { // writeBase(o); // byte b[] = getPaddedByteValues(); // if (b != null && b.length > 0) o.write(b); // } /***/ public String toString(DicomDictionary dictionary) { StringBuffer str = new StringBuffer(); str.append(super.toString(dictionary)); str.append(" <"); try { //String[] v = getStringValues(); String[] v = getOriginalStringValues(); if (v != null) { for (int j=0; j<v.length; ++j) { if (j > 0) str.append("\\"); str.append(v[j]); } } } catch (DicomException e) { str.append("XXXX"); } str.append(">"); return str.toString(); } /** * <p>Get the values of this attribute as a byte array.</p> * * <p>Returns the originally read byte values, if read from a stream, otherwise converts the string to bytes and pads them.</p> * * @return the values as an array of bytes * @exception DicomException thrown if values are not available */ public byte[] getByteValues() throws DicomException { return originalByteValues == null ? getPaddedByteValues() : originalByteValues; } /** * <p>Get the values of this attribute as strings.</p> * * <p>The strings are first cleaned up into a canonical form, to remove leading and trailing padding.</p> * * @param format the format to use for each numerical or decimal value * @return the values as an array of {@link java.lang.String String} * @exception DicomException not thrown */ public String[] getStringValues(NumberFormat format) throws DicomException { // ignore number format for generic string attributes if (cachedUnpaddedStringCopy == null) cachedUnpaddedStringCopy=ArrayCopyUtilities.copyStringArrayRemovingLeadingAndTrailingPadding(originalValues); return cachedUnpaddedStringCopy; } /** * <p>Get the values of this attribute as strings, the way they were originally inserted or read.</p> * * @return the values as an array of {@link java.lang.String String} * @exception DicomException not thrown */ public String[] getOriginalStringValues() throws DicomException { return originalValues; } /** * @exception DicomException */ public int[] getIntegerValues() throws DicomException { if (cachedIntegerCopy == null) cachedIntegerCopy=ArrayCopyUtilities.copyStringToIntArray(getStringValues()); // must be unpadded return cachedIntegerCopy; } /** * @exception DicomException */ public long[] getLongValues() throws DicomException { if (cachedLongCopy == null) cachedLongCopy=ArrayCopyUtilities.copyStringToLongArray(getStringValues()); // must be unpadded return cachedLongCopy; } /** * @exception DicomException */ public float[] getFloatValues() throws DicomException { if (cachedFloatCopy == null) cachedFloatCopy=ArrayCopyUtilities.copyStringToFloatArray(getStringValues()); // must be unpadded return cachedFloatCopy; } /** * @exception DicomException */ public double[] getDoubleValues() throws DicomException { if (cachedDoubleCopy == null) cachedDoubleCopy=ArrayCopyUtilities.copyStringToDoubleArray(getStringValues()); // must be unpadded return cachedDoubleCopy; } /** * @param v * @exception DicomException */ public void addValue(String v) throws DicomException { flushCachedCopies(); originalValues=ArrayCopyUtilities.expandArray(originalValues); valueLength+=v.length(); if (valueMultiplicity > 0) ++valueLength; // for the delimiter originalValues[valueMultiplicity++]=v; } /** * @param v * @exception DicomException */ public void addValue(byte v) throws DicomException { addValue(Short.toString((short)(((int)v)&0xff))); // don't ask ! } /** * @param v * @exception DicomException */ public void addValue(short v) throws DicomException { addValue(Short.toString(v)); } /** * @param v * @exception DicomException */ public void addValue(int v) throws DicomException { addValue(Integer.toString(v)); } /** * @param v * @exception DicomException */ public void addValue(long v) throws DicomException { addValue(Long.toString(v)); } /** * @param v * @exception DicomException */ public void addValue(float v) throws DicomException { addValue(Float.toString(v)); } /** * @param v * @exception DicomException */ public void addValue(double v) throws DicomException { addValue(Double.toString(v)); } /** * @exception DicomException */ public void removeValues() throws DicomException { valueLength=0; valueMultiplicity=0; originalValues=null; flushCachedCopies(); } }