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 java.util.ArrayList;
022import java.util.Collections;
023import java.util.List;
024
025/**
026 * Mutable dataset implementation used by {@link ISODatasetField}.
027 */
028public class ISODataset implements Dataset {
029    private final int identifier;
030    private final DatasetFormat format;
031    private final List<DatasetElement> elements = new ArrayList<>();
032
033    /**
034     * Creates an empty dataset.
035     *
036     * @param identifier dataset identifier
037     * @param format dataset format
038     */
039    public ISODataset(int identifier, DatasetFormat format) {
040        if (identifier < 0x01 || identifier > 0xFE) {
041            throw new IllegalArgumentException("dataset identifier out of range");
042        }
043        this.identifier = identifier;
044        this.format = format;
045    }
046
047    @Override
048    public int getIdentifier() {
049        return identifier;
050    }
051
052    @Override
053    public DatasetFormat getFormat() {
054        return format;
055    }
056
057    /**
058     * Appends an element without replacing existing entries with the same id.
059     *
060     * @param element element to append
061     */
062    public void addElement(DatasetElement element) {
063        elements.add(element);
064    }
065
066    /**
067     * Replaces any existing elements with the same id and then appends the supplied element.
068     *
069     * @param element element to store
070     */
071    public void putElement(DatasetElement element) {
072        elements.removeIf(existing -> existing.getId() == element.getId());
073        addElement(element);
074    }
075
076    /**
077     * Appends a primitive element.
078     *
079     * @param id element identifier
080     * @param component backing component
081     */
082    public void addElement(int id, ISOComponent component) {
083        addElement(new DatasetElement(id, component));
084    }
085
086    /**
087     * Replaces any existing element with the same id.
088     *
089     * @param id element identifier
090     * @param component backing component
091     */
092    public void putElement(int id, ISOComponent component) {
093        putElement(new DatasetElement(id, component));
094    }
095
096    /**
097     * Appends an element and records its constructed TLV flag.
098     *
099     * @param id element identifier
100     * @param component backing component
101     * @param constructed whether the tag is constructed
102     */
103    public void addElement(int id, ISOComponent component, boolean constructed) {
104        addElement(new DatasetElement(id, component, constructed));
105    }
106
107    /**
108     * Replaces any existing element with the same id and records its constructed TLV flag.
109     *
110     * @param id element identifier
111     * @param component backing component
112     * @param constructed whether the tag is constructed
113     */
114    public void putElement(int id, ISOComponent component, boolean constructed) {
115        putElement(new DatasetElement(id, component, constructed));
116    }
117
118    /**
119     * Removes all elements that match the supplied identifier.
120     *
121     * @param id element identifier to remove
122     */
123    public void removeElement(int id) {
124        elements.removeIf(existing -> existing.getId() == id);
125    }
126
127    /**
128     * Indicates whether the dataset contains any elements.
129     *
130     * @return {@code true} when empty
131     */
132    public boolean isEmpty() {
133        return elements.isEmpty();
134    }
135
136    /**
137     * Stores a character element and returns this dataset for fluent chaining.
138     *
139     * @param id element identifier
140     * @param value element value
141     * @return this dataset
142     */
143    public ISODataset with(int id, String value) {
144        ISOField field = new ISOField(id, value);
145        putElement(id, field);
146        return this;
147    }
148
149    /**
150     * Stores a binary element and returns this dataset for fluent chaining.
151     *
152     * @param id element identifier
153     * @param value element value
154     * @return this dataset
155     */
156    public ISODataset with(int id, byte[] value) {
157        ISOBinaryField field = new ISOBinaryField(id, value);
158        putElement(id, field);
159        return this;
160    }
161
162    /**
163     * Stores an ISO component and returns this dataset for fluent chaining.
164     *
165     * @param id element identifier
166     * @param component backing component
167     * @return this dataset
168     */
169    public ISODataset with(int id, ISOComponent component) {
170        component.setFieldNumber(id);
171        putElement(id, component);
172        return this;
173    }
174
175    /**
176     * Stores an ISO component and its constructed TLV flag, returning this dataset for fluent chaining.
177     *
178     * @param id element identifier
179     * @param component backing component
180     * @param constructed whether the tag is constructed
181     * @return this dataset
182     */
183    public ISODataset with(int id, ISOComponent component, boolean constructed) {
184        component.setFieldNumber(id);
185        putElement(id, component, constructed);
186        return this;
187    }
188
189    @Override
190    public List<DatasetElement> getElements() {
191        return Collections.unmodifiableList(elements);
192    }
193
194    @Override
195    public List<DatasetElement> getElements(int id) {
196        List<DatasetElement> matches = new ArrayList<>();
197        for (DatasetElement element : elements) {
198            if (element.getId() == id) {
199                matches.add(element);
200            }
201        }
202        return Collections.unmodifiableList(matches);
203    }
204
205    @Override
206    public DatasetElement getElement(int id) {
207        for (DatasetElement element : elements) {
208            if (element.getId() == id) {
209                return element;
210            }
211        }
212        return null;
213    }
214
215    /**
216     * Returns the {@link ISOComponent} of the first matching element.
217     *
218     * @param id element identifier
219     * @return matching component, or {@code null} if no element has the given id
220     */
221    public ISOComponent getComponent(int id) {
222        DatasetElement element = getElement(id);
223        return element != null ? element.getComponent() : null;
224    }
225
226    /**
227     * Returns the logical value of the first matching element.
228     *
229     * @param id element identifier
230     * @return element value or {@code null}
231     * @throws ISOException on component access errors
232     */
233    public Object getValue(int id) throws ISOException {
234        DatasetElement element = getElement(id);
235        return element != null ? element.getValue() : null;
236    }
237
238    /**
239     * Returns the bytes of the first matching element.
240     *
241     * @param id element identifier
242     * @return element bytes or {@code null}
243     * @throws ISOException on component access errors
244     */
245    public byte[] getBytes(int id) throws ISOException {
246        DatasetElement element = getElement(id);
247        return element != null ? element.getBytes() : null;
248    }
249}