001package jmri.jmrit.logixng.implementation;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.beans.*;
006import java.io.File;
007import java.io.IOException;
008import java.io.PrintWriter;
009import java.text.DecimalFormat;
010import java.util.Locale;
011
012import javax.annotation.*;
013
014import jmri.*;
015import jmri.jmrit.logixng.*;
016import jmri.managers.AbstractManager;
017import jmri.util.*;
018
019/**
020 * Class providing the basic logic of the NamedTable_Manager interface.
021 *
022 * @author Dave Duchamp       Copyright (C) 2007
023 * @author Daniel Bergqvist   Copyright (C) 2020
024 */
025public class DefaultNamedTableManager extends AbstractManager<NamedTable>
026        implements NamedTableManager {
027
028    DecimalFormat paddedNumber = new DecimalFormat("0000");
029
030
031    /**
032     * {@inheritDoc}
033     */
034    @Override
035    public int getXMLOrder() {
036        return LOGIXNG_TABLES;
037    }
038
039    /**
040     * {@inheritDoc}
041     */
042    @Override
043    public char typeLetter() {
044        return 'Q';
045    }
046
047    /**
048     * {@inheritDoc}
049     */
050    @Override
051    public NameValidity validSystemNameFormat(String systemName) {
052        return LogixNG_Manager.validSystemNameFormat(
053                getSubSystemNamePrefix(), systemName);
054//        if (systemName.matches(getSubSystemNamePrefix()+"(:AUTO:)?\\d+")) {
055//            return NameValidity.VALID;
056//        } else {
057//            return NameValidity.INVALID;
058//        }
059    }
060
061
062    /**
063     * {@inheritDoc}
064     */
065    @Override
066    public NamedTable newCSVTable(String systemName, String userName, String fileName)
067            throws IllegalArgumentException {
068        return newCSVTable(systemName, userName, fileName, Table.CsvType.TABBED);
069    }
070
071    /**
072    * {@inheritDoc}
073    */
074    @Override
075    public NamedTable newCSVTable(String systemName, String userName, String fileName, Table.CsvType csvType)
076            throws IllegalArgumentException {
077
078        // Check that NamedTable does not already exist
079        NamedTable x;
080        if (userName != null && !userName.equals("")) {
081            x = getByUserName(userName);
082            if (x != null) {
083                return null;
084            }
085        }
086        x = getBySystemName(systemName);
087        if (x != null) {
088            return null;
089        }
090        // Check if system name is valid
091        if (this.validSystemNameFormat(systemName) != NameValidity.VALID) {
092            throw new IllegalArgumentException("SystemName " + systemName + " is not in the correct format");
093        }
094        try {
095            log.debug("about to load file {}", fileName );
096            // NamedTable does not exist, create a new NamedTable
097            x = AbstractNamedTable.loadTableFromCSV_File(systemName, userName, fileName, true, csvType);
098        } catch (IOException ex) {
099//            Exceptions.printStackTrace(ex);
100            log.error("Cannot load table due to I/O error", ex);
101            return null;
102        }
103        // save in the maps
104        register(x);
105
106        // Keep track of the last created auto system name
107        updateAutoNumber(systemName);
108
109        return x;
110    }
111
112    /**
113     * {@inheritDoc}
114     */
115    @Override
116    public NamedTable newInternalTable(String systemName, String userName, int numRows, int numColumns)
117            throws IllegalArgumentException {
118
119        // Check that NamedTable does not already exist
120        NamedTable x;
121        if (userName != null && !userName.equals("")) {
122            x = getByUserName(userName);
123            if (x != null) {
124                return null;
125            }
126        }
127        x = getBySystemName(systemName);
128        if (x != null) {
129            return null;
130        }
131        // Check if system name is valid
132        if (this.validSystemNameFormat(systemName) != NameValidity.VALID) {
133            throw new IllegalArgumentException("SystemName " + systemName + " is not in the correct format");
134        }
135        // Table does not exist, create a new NamedTable
136        x = new DefaultInternalNamedTable(systemName, userName, numRows, numColumns);
137        // save in the maps
138        register(x);
139
140        // Keep track of the last created auto system name
141        updateAutoNumber(systemName);
142
143        return x;
144    }
145
146    /**
147     * {@inheritDoc}
148     */
149    @Override
150    public AnonymousTable newAnonymousTable(int numRows, int numColumns)
151            throws IllegalArgumentException {
152
153        // Check that NamedTable does not already exist
154        // NamedTable does not exist, create a new NamedTable
155        return new DefaultAnonymousTable(numRows, numColumns);
156    }
157
158    /**
159     * {@inheritDoc}
160     */
161    @Override
162    public NamedTable loadTableFromCSVData(
163            @Nonnull String sys, @CheckForNull String user, @Nonnull String text)
164            throws NamedBean.BadUserNameException,
165            NamedBean.BadSystemNameException,
166            IOException {
167            return AbstractNamedTable.loadTableFromCSV_Text(sys, user, text, true, Table.CsvType.TABBED);
168    }
169
170    /**
171     * {@inheritDoc}
172     */
173    @Override
174    public NamedTable loadTableFromCSV(
175            @Nonnull String sys, @CheckForNull String user,
176            @Nonnull String fileName)
177            throws NamedBean.BadUserNameException, NamedBean.BadSystemNameException, IOException {
178        return AbstractNamedTable.loadTableFromCSV_File(sys, user, fileName, true, Table.CsvType.TABBED);
179    }
180
181    /**
182     * {@inheritDoc}
183     */
184    @Override
185    public NamedTable loadTableFromCSV(
186            @Nonnull String sys, @CheckForNull String user,
187            @Nonnull File file)
188            throws NamedBean.BadUserNameException, NamedBean.BadSystemNameException, IOException {
189        return AbstractNamedTable.loadTableFromCSV_File(sys, user, file, true, Table.CsvType.TABBED);
190    }
191
192    /**
193     * {@inheritDoc}
194     */
195    @Override
196    public NamedTable getNamedTable(String name) {
197        NamedTable x = getByUserName(name);
198        if (x != null) {
199            return x;
200        }
201        return getBySystemName(name);
202    }
203
204    /**
205     * {@inheritDoc}
206     */
207    @Override
208    public NamedTable getByUserName(String name) {
209        return _tuser.get(name);
210    }
211
212    /**
213     * {@inheritDoc}
214     */
215    @Override
216    public NamedTable getBySystemName(String name) {
217        return _tsys.get(name);
218    }
219
220    /**
221     * {@inheritDoc}
222     */
223    @Override
224    public String getBeanTypeHandled(boolean plural) {
225        return Bundle.getMessage(plural ? "BeanNameNamedTables" : "BeanNameNamedTable");
226    }
227
228    /**
229     * {@inheritDoc}
230     */
231    @Override
232    public void deleteNamedTable(NamedTable x) {
233        // delete the NamedTable
234        deregister(x);
235        x.dispose();
236    }
237
238    /** {@inheritDoc} */
239    @Override
240    public void printTree(PrintWriter writer, String indent) {
241        printTree(Locale.getDefault(), writer, indent);
242    }
243
244    /** {@inheritDoc} */
245    @Override
246    public void printTree(Locale locale, PrintWriter writer, String indent) {
247        for (NamedTable namedTable : getNamedBeanSet()) {
248            if (namedTable instanceof DefaultCsvNamedTable) {
249                DefaultCsvNamedTable csvTable = (DefaultCsvNamedTable)namedTable;
250                writer.append(String.format(
251                        "Named table: System name: %s, User name: %s, File name: %s, Num rows: %d, Num columns: %d",
252                        csvTable.getSystemName(), csvTable.getUserName(),
253                        csvTable.getFileName(), csvTable.numRows(), csvTable.numColumns()));
254            } else if (namedTable != null) {
255                writer.append(String.format(
256                        "Named table: System name: %s, User name: %s, Num rows: %d, Num columns: %d",
257                        namedTable.getSystemName(), namedTable.getUserName(),
258                        namedTable.numRows(), namedTable.numColumns()));
259            } else {
260                throw new NullPointerException("namedTable is null");
261            }
262            writer.println();
263        }
264        writer.println();
265    }
266
267    static volatile DefaultNamedTableManager _instance = null;
268
269    @InvokeOnGuiThread  // this method is not thread safe
270    static public DefaultNamedTableManager instance() {
271        if (!ThreadingUtil.isGUIThread()) {
272            LoggingUtil.warnOnce(log, "instance() called on wrong thread");
273        }
274
275        if (_instance == null) {
276            _instance = new DefaultNamedTableManager();
277        }
278        return (_instance);
279    }
280
281    /**
282     * {@inheritDoc}
283     */
284    @Override
285    public Class<NamedTable> getNamedBeanClass() {
286        return NamedTable.class;
287    }
288
289    /**
290     * Inform all registered listeners of a vetoable change.If the propertyName
291     * is "CanDelete" ALL listeners with an interest in the bean will throw an
292     * exception, which is recorded returned back to the invoking method, so
293     * that it can be presented back to the user.However if a listener decides
294     * that the bean can not be deleted then it should throw an exception with
295     * a property name of "DoNotDelete", this is thrown back up to the user and
296     * the delete process should be aborted.
297     *
298     * @param p   The programmatic name of the property that is to be changed.
299     *            "CanDelete" will inquire with all listeners if the item can
300     *            be deleted. "DoDelete" tells the listener to delete the item.
301     * @param old The old value of the property.
302     * @throws java.beans.PropertyVetoException If the recipients wishes the
303     *                                          delete to be aborted (see above)
304     */
305    @OverridingMethodsMustInvokeSuper
306    public void fireVetoableChange(String p, Object old) throws PropertyVetoException {
307        PropertyChangeEvent evt = new PropertyChangeEvent(this, p, old, null);
308        for (VetoableChangeListener vc : vetoableChangeSupport.getVetoableChangeListeners()) {
309            vc.vetoableChange(evt);
310        }
311    }
312
313    /** {@inheritDoc} */
314    @Override
315    @SuppressFBWarnings(value = "OVERRIDING_METHODS_MUST_INVOKE_SUPER",
316            justification = "Further investigation is needed to handle this correctly")
317    public final void deleteBean(@Nonnull NamedTable namedTable, @Nonnull String property) throws PropertyVetoException {
318        // throws PropertyVetoException if vetoed
319        fireVetoableChange(property, namedTable);
320        if (property.equals("DoDelete")) { // NOI18N
321            deregister(namedTable);
322            namedTable.dispose();
323        }
324    }
325
326
327    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultNamedTableManager.class);
328
329}