001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.iso; 020 021import org.jpos.iso.packager.XMLPackager; 022 023import java.io.InputStream; 024import java.io.PrintStream; 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.List; 028 029/** 030 * Composite ISO field that holds one or more datasets. 031 */ 032public class ISODatasetField extends ISOComponent { 033 private int fieldNumber; 034 private final List<Dataset> datasets = new ArrayList<>(); 035 036 /** 037 * Creates an unbound dataset field. 038 */ 039 public ISODatasetField() { 040 this(-1); 041 } 042 043 /** 044 * Creates a dataset field bound to an outer field number. 045 * 046 * @param fieldNumber outer field number 047 */ 048 public ISODatasetField(int fieldNumber) { 049 this.fieldNumber = fieldNumber; 050 } 051 052 /** 053 * Appends a dataset to this field. 054 * 055 * @param dataset dataset to add 056 */ 057 public void addDataset(Dataset dataset) { 058 datasets.add(dataset); 059 } 060 061 /** 062 * Removes a dataset instance from this field. 063 * 064 * @param dataset dataset to remove 065 */ 066 public void removeDataset(Dataset dataset) { 067 datasets.remove(dataset); 068 } 069 070 /** 071 * Indicates whether this field still contains datasets. 072 * 073 * @return {@code true} when at least one dataset is present 074 */ 075 public boolean hasDatasets() { 076 return !datasets.isEmpty(); 077 } 078 079 /** 080 * Returns all datasets in insertion order. 081 * 082 * @return immutable list of datasets 083 */ 084 public List<Dataset> getDatasets() { 085 return Collections.unmodifiableList(datasets); 086 } 087 088 /** 089 * Returns all datasets that match the supplied identifier. 090 * 091 * @param identifier dataset identifier 092 * @return immutable list of matching datasets 093 */ 094 public List<Dataset> getDatasets(int identifier) { 095 List<Dataset> matches = new ArrayList<>(); 096 for (Dataset dataset : datasets) { 097 if (dataset.getIdentifier() == identifier) { 098 matches.add(dataset); 099 } 100 } 101 return Collections.unmodifiableList(matches); 102 } 103 104 /** 105 * Returns the first dataset that matches the supplied identifier. 106 * 107 * @param identifier dataset identifier 108 * @return matching dataset or {@code null} 109 */ 110 public Dataset getDataset(int identifier) { 111 for (Dataset dataset : datasets) { 112 if (dataset.getIdentifier() == identifier) { 113 return dataset; 114 } 115 } 116 return null; 117 } 118 119 /** 120 * Returns the component stored under the given dataset and element identifiers. 121 * 122 * @param datasetId dataset identifier 123 * @param elementId element identifier 124 * @return matching component or {@code null} 125 */ 126 public ISOComponent get(int datasetId, int elementId) { 127 Dataset dataset = getDataset(datasetId); 128 if (dataset instanceof ISODataset) { 129 return ((ISODataset) dataset).getComponent(elementId); 130 } 131 DatasetElement element = dataset != null ? dataset.getElement(elementId) : null; 132 return element != null ? element.getComponent() : null; 133 } 134 135 /** 136 * Returns the logical value stored under the given dataset and element identifiers. 137 * 138 * @param datasetId dataset identifier 139 * @param elementId element identifier 140 * @return element value or {@code null} 141 * @throws ISOException on component access errors 142 */ 143 public Object getValue(int datasetId, int elementId) throws ISOException { 144 ISOComponent component = get(datasetId, elementId); 145 return component != null ? component.getValue() : null; 146 } 147 148 /** 149 * Returns the bytes stored under the given dataset and element identifiers. 150 * 151 * @param datasetId dataset identifier 152 * @param elementId element identifier 153 * @return element bytes or {@code null} 154 * @throws ISOException on component access errors 155 */ 156 public byte[] getBytes(int datasetId, int elementId) throws ISOException { 157 ISOComponent component = get(datasetId, elementId); 158 return component != null ? component.getBytes() : null; 159 } 160 161 /** 162 * Returns this composite field. 163 * 164 * @return this field 165 */ 166 @Override 167 public ISOComponent getComposite() { 168 return this; 169 } 170 171 /** 172 * Returns the outer ISO field number. 173 * 174 * @return outer field number 175 */ 176 @Override 177 public Object getKey() { 178 return fieldNumber; 179 } 180 181 /** 182 * Returns the datasets carried by this field. 183 * 184 * @return dataset list 185 */ 186 @Override 187 public Object getValue() { 188 return getDatasets(); 189 } 190 191 /** 192 * Dataset fields do not expose their bytes directly and must be packed via a 193 * {@link DatasetFieldPackager}. 194 * 195 * @return never returns normally 196 * @throws ISOException always 197 */ 198 @Override 199 public byte[] getBytes() throws ISOException { 200 throw new ISOException("Dataset fields must be packed via DatasetFieldPackager"); 201 } 202 203 /** 204 * Sets the outer ISO field number. 205 * 206 * @param fieldNumber outer field number 207 */ 208 @Override 209 public void setFieldNumber(int fieldNumber) { 210 this.fieldNumber = fieldNumber; 211 } 212 213 /** 214 * Returns the outer ISO field number. 215 * 216 * @return outer field number 217 */ 218 @Override 219 public int getFieldNumber() { 220 return fieldNumber; 221 } 222 223 /** 224 * Replaces the datasets held by this field. 225 * 226 * @param obj either a {@link Dataset} or a {@link java.util.List} of datasets 227 * @throws ISOException when the supplied value type is unsupported 228 */ 229 @Override 230 public void setValue(Object obj) throws ISOException { 231 datasets.clear(); 232 if (obj instanceof Dataset) { 233 datasets.add((Dataset) obj); 234 } else if (obj instanceof List<?>) { 235 for (Object item : (List<?>) obj) { 236 if (!(item instanceof Dataset)) { 237 throw new ISOException("Invalid dataset list entry " + item); 238 } 239 datasets.add((Dataset) item); 240 } 241 } else if (obj != null) { 242 throw new ISOException("Unsupported dataset field value " + obj.getClass().getName()); 243 } 244 } 245 246 /** 247 * Dataset fields must be packed through their field packager. 248 * 249 * @return never returns normally 250 * @throws ISOException always 251 */ 252 @Override 253 public byte[] pack() throws ISOException { 254 throw new ISOException("Not available on Dataset field"); 255 } 256 257 /** 258 * Dataset fields must be unpacked through their field packager. 259 * 260 * @param b source buffer 261 * @return never returns normally 262 * @throws ISOException always 263 */ 264 @Override 265 public int unpack(byte[] b) throws ISOException { 266 throw new ISOException("Not available on Dataset field"); 267 } 268 269 /** 270 * Dataset fields must be unpacked through their field packager. 271 * 272 * @param in source stream 273 * @throws ISOException always 274 */ 275 @Override 276 public void unpack(InputStream in) throws ISOException { 277 throw new ISOException("Not available on Dataset field"); 278 } 279 280 /** 281 * Dumps the field as dataset-aware XML. 282 * 283 * @param p destination stream 284 * @param indent indentation prefix 285 */ 286 @Override 287 public void dump(PrintStream p, String indent) { 288 p.println(indent + "<" + XMLPackager.ISOFIELD_TAG + " " + XMLPackager.ID_ATTR + "=\"" + fieldNumber + "\" type=\"dataset\">"); 289 String innerIndent = indent + " "; 290 for (Dataset dataset : datasets) { 291 p.println(innerIndent + "<dataset id=\"" + String.format("%02X", dataset.getIdentifier()) + "\" format=\"" + dataset.getFormat() + "\">"); 292 String datasetIndent = innerIndent + " "; 293 for (DatasetElement element : dataset.getElements()) { 294 try { 295 String elementId = dataset.getFormat() == DatasetFormat.TLV 296 ? String.format("0x%X", element.getId()) 297 : Integer.toString(element.getId()); 298 p.println(datasetIndent 299 + "<element id=\"" 300 + elementId 301 + "\"" 302 + (element.isConstructed() ? " constructed=\"true\"" : "") 303 + " value=\"" 304 + ISOUtil.hexString(element.getBytes()) 305 + "\"/>"); 306 } catch (ISOException e) { 307 p.println(datasetIndent + "<element id=\"" + element.getId() + "\" error=\"" + ISOUtil.normalize(e.getMessage()) + "\"/>"); 308 } 309 } 310 p.println(innerIndent + "</dataset>"); 311 } 312 p.println(indent + "</" + XMLPackager.ISOFIELD_TAG + ">"); 313 } 314}