001/* ***** BEGIN LICENSE BLOCK ***** 002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 003 * 004 * The contents of this file are subject to the Mozilla Public License Version 005 * 1.1 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * http://www.mozilla.org/MPL/ 008 * 009 * Software distributed under the License is distributed on an "AS IS" basis, 010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 011 * for the specific language governing rights and limitations under the 012 * License. 013 * 014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 015 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 016 * 017 * The Initial Developer of the Original Code is 018 * Agfa Healthcare. 019 * Portions created by the Initial Developer are Copyright (C) 2011 020 * the Initial Developer. All Rights Reserved. 021 * 022 * Contributor(s): 023 * See @authors listed below 024 * 025 * Alternatively, the contents of this file may be used under the terms of 026 * either the GNU General Public License Version 2 or later (the "GPL"), or 027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 028 * in which case the provisions of the GPL or the LGPL are applicable instead 029 * of those above. If you wish to allow use of your version of this file only 030 * under the terms of either the GPL or the LGPL, and not to allow others to 031 * use your version of this file under the terms of the MPL, indicate your 032 * decision by deleting the provisions above and replace them with the notice 033 * and other provisions required by the GPL or the LGPL. If you do not delete 034 * the provisions above, a recipient may use your version of this file under 035 * the terms of any one of the MPL, the GPL or the LGPL. 036 * 037 * ***** END LICENSE BLOCK ***** */ 038 039package org.dcm4che3.util; 040 041import java.io.UnsupportedEncodingException; 042import java.net.URLEncoder; 043import java.text.FieldPosition; 044import java.text.Format; 045import java.text.MessageFormat; 046import java.text.ParsePosition; 047import java.util.ArrayList; 048import java.util.Date; 049import java.util.StringTokenizer; 050 051import org.dcm4che3.data.Attributes; 052 053 054/** 055 * @author Gunter Zeilinger <gunterze@gmail.com> 056 */ 057public class AttributesFormat extends Format { 058 059 private static final long serialVersionUID = 1901510733531643054L; 060 061 private final String pattern; 062 private final int[][] tagPaths; 063 private final int[] index; 064 private final Type[] types; 065 private final MessageFormat format; 066 067 public AttributesFormat(String pattern) { 068 ArrayList<String> tokens = tokenize(pattern); 069 int n = tokens.size() / 2; 070 this.pattern = pattern; 071 this.tagPaths = new int[n][]; 072 this.index = new int[n]; 073 this.types = new Type[n]; 074 this.format = buildMessageFormat(tokens); 075 } 076 077 private ArrayList<String> tokenize(String s) { 078 ArrayList<String> result = new ArrayList<String>(); 079 StringTokenizer stk = new StringTokenizer(s, "{}", true); 080 String tk; 081 char delim; 082 char prevDelim = '}'; 083 int level = 0; 084 StringBuilder sb = new StringBuilder(); 085 while (stk.hasMoreTokens()) { 086 tk = stk.nextToken(); 087 delim = tk.charAt(0); 088 if (delim == '{') { 089 if (level++ == 0) { 090 if (prevDelim == '}') 091 result.add(""); 092 } else { 093 sb.append(delim); 094 } 095 } else if (delim == '}') { 096 if (--level == 0) { 097 result.add(sb.toString()); 098 sb.setLength(0); 099 } else if (level > 0){ 100 sb.append(delim); 101 } else 102 throw new IllegalArgumentException(s); 103 } else { 104 if (level == 0) 105 result.add(tk); 106 else 107 sb.append(tk); 108 } 109 prevDelim = delim; 110 } 111 return result; 112 } 113 114 private MessageFormat buildMessageFormat(ArrayList<String> tokens) { 115 StringBuilder formatBuilder = new StringBuilder(pattern.length()); 116 int j = 0; 117 for (int i = 0; i < tagPaths.length; i++) { 118 formatBuilder.append(tokens.get(j++)).append('{').append(i); 119 String tagStr = tokens.get(j++); 120 int typeStart = tagStr.indexOf(',') + 1; 121 if (!tagStr.startsWith("now")) { 122 int tagStrLen = typeStart != 0 123 ? typeStart - 1 124 : tagStr.length(); 125 126 int indexStart = tagStr.charAt(tagStrLen-1) == ']' 127 ? tagStr.lastIndexOf('[', tagStrLen-3) + 1 128 : 0; 129 try { 130 tagPaths[i] = TagUtils.parseTagPath(tagStr.substring(0, indexStart != 0 ? indexStart - 1 : tagStrLen)); 131 if (indexStart != 0) 132 index[i] = Integer.parseInt(tagStr.substring(indexStart, tagStrLen-1)); 133 } catch (IllegalArgumentException e) { 134 throw new IllegalArgumentException(pattern); 135 } 136 } 137 if (typeStart != 0) { 138 int typeEnd = tagStr.indexOf(',', typeStart); 139 try { 140 types[i] = Type.valueOf(tagStr.substring(typeStart, 141 typeEnd < 0 ? tagStr.length() : typeEnd)); 142 } catch (IllegalArgumentException e) { 143 throw new IllegalArgumentException(pattern); 144 } 145 if (types[i] != Type.hash && types[i] != Type.urlencoded) 146 formatBuilder.append( 147 typeStart > 0 ? tagStr.substring(typeStart-1) : tagStr); 148 } else { 149 types[i] = Type.none; 150 } 151 formatBuilder.append('}'); 152 } 153 if (j < tokens.size()) 154 formatBuilder.append(tokens.get(j)); 155 try { 156 return new MessageFormat(formatBuilder.toString()); 157 } catch (IllegalArgumentException e) { 158 throw new IllegalArgumentException(pattern); 159 } 160 } 161 162 public static AttributesFormat valueOf(String s) { 163 return s != null ? new AttributesFormat(s) : null; 164 } 165 166 @Override 167 public StringBuffer format(Object obj, StringBuffer result, FieldPosition pos) { 168 return format.format(toArgs((Attributes) obj), result, pos); 169 } 170 171 private Object[] toArgs(Attributes attrs) { 172 Object[] args = new Object[tagPaths.length]; 173 for (int i = 0; i < args.length; i++) { 174 int[] tagPath = tagPaths[i]; 175 if (tagPath == null) { // now 176 args[i] = types[i].toArg(attrs, 0, index[i]); 177 } else { 178 int last = tagPath.length - 1; 179 Attributes item = attrs; 180 for (int j = 0; j < last && item != null; j++) { 181 item = item.getNestedDataset(tagPath[j]); 182 } 183 args[i] = item != null ? types[i].toArg(item, tagPath[last], index[i]) : null; 184 } 185 } 186 return args; 187 } 188 189 @Override 190 public Object parseObject(String source, ParsePosition pos) { 191 throw new UnsupportedOperationException(); 192 } 193 194 @Override 195 public String toString() { 196 return pattern; 197 } 198 199 private static enum Type { 200 none { 201 @Override 202 Object toArg(Attributes attrs, int tag, int index) { 203 return attrs.getString(tag, index); 204 } 205 }, 206 number { 207 @Override 208 Object toArg(Attributes attrs, int tag, int index) { 209 return attrs.getDouble(tag, index, 0.); 210 } 211 }, 212 date { 213 @Override 214 Object toArg(Attributes attrs, int tag, int index) { 215 return tag != 0 ? attrs.getDate(tag, index) : new Date(); 216 } 217 }, 218 time { 219 @Override 220 Object toArg(Attributes attrs, int tag, int index) { 221 return tag != 0 ? attrs.getDate(tag, index) : new Date(); 222 } 223 }, 224 choice { 225 @Override 226 Object toArg(Attributes attrs, int tag, int index) { 227 return attrs.getDouble(tag, index, 0.); 228 } 229 }, 230 hash { 231 @Override 232 Object toArg(Attributes attrs, int tag, int index) { 233 String s = attrs.getString(tag, index); 234 return s != null ? TagUtils.toHexString(s.hashCode()) : null; 235 } 236 }, 237 urlencoded { 238 @Override 239 Object toArg(Attributes attrs, int tag, int index) { 240 String s = attrs.getString(tag, index); 241 try { 242 return s != null ? URLEncoder.encode(s, "UTF-8") : null; 243 } catch (UnsupportedEncodingException e) { 244 throw new AssertionError(e); 245 } 246 } 247 }; 248 249 abstract Object toArg(Attributes attrs, int tag, int index); 250 } 251 252}