001/*****************************************************************************
002 * Copyright by The HDF Group.                                               *
003 * Copyright by the Board of Trustees of the University of Illinois.         *
004 * All rights reserved.                                                      *
005 *                                                                           *
006 * This file is part of the HDF Java Products distribution.                  *
007 * The full copyright notice, including terms governing use, modification,   *
008 * and redistribution, is contained in the files COPYING and Copyright.html. *
009 * COPYING can be found at the root of the source code distribution tree.    *
010 * Or, see https://support.hdfgroup.org/products/licenses.html               *
011 * If you do not have access to either file, you may request a copy from     *
012 * help@hdfgroup.org.                                                        *
013 ****************************************************************************/
014
015package hdf.view.MetaDataView;
016
017import java.io.File;
018import java.lang.reflect.Array;
019import java.math.BigInteger;
020import java.util.Iterator;
021import java.util.List;
022import java.util.StringTokenizer;
023
024import org.eclipse.jface.dialogs.MessageDialog;
025import org.eclipse.swt.SWT;
026import org.eclipse.swt.custom.ScrolledComposite;
027import org.eclipse.swt.events.MenuAdapter;
028import org.eclipse.swt.events.MenuEvent;
029import org.eclipse.swt.events.SelectionAdapter;
030import org.eclipse.swt.events.SelectionEvent;
031import org.eclipse.swt.graphics.Font;
032import org.eclipse.swt.graphics.Point;
033import org.eclipse.swt.graphics.Rectangle;
034import org.eclipse.swt.layout.GridData;
035import org.eclipse.swt.layout.GridLayout;
036import org.eclipse.swt.widgets.Button;
037import org.eclipse.swt.widgets.Combo;
038import org.eclipse.swt.widgets.Composite;
039import org.eclipse.swt.widgets.Dialog;
040import org.eclipse.swt.widgets.Display;
041import org.eclipse.swt.widgets.Event;
042import org.eclipse.swt.widgets.FileDialog;
043import org.eclipse.swt.widgets.Label;
044import org.eclipse.swt.widgets.Listener;
045import org.eclipse.swt.widgets.Menu;
046import org.eclipse.swt.widgets.MenuItem;
047import org.eclipse.swt.widgets.Shell;
048import org.eclipse.swt.widgets.TabFolder;
049import org.eclipse.swt.widgets.TabItem;
050import org.eclipse.swt.widgets.Table;
051import org.eclipse.swt.widgets.TableColumn;
052import org.eclipse.swt.widgets.TableItem;
053import org.eclipse.swt.widgets.Text;
054
055import hdf.hdf5lib.H5;
056import hdf.hdf5lib.HDF5Constants;
057import hdf.object.Attribute;
058import hdf.object.CompoundDS;
059import hdf.object.Dataset;
060import hdf.object.Datatype;
061import hdf.object.FileFormat;
062import hdf.object.Group;
063import hdf.object.HObject;
064import hdf.object.MetaDataContainer;
065import hdf.object.ScalarDS;
066import hdf.view.DefaultFileFilter;
067import hdf.view.Tools;
068import hdf.view.ViewProperties;
069import hdf.view.DataView.DataViewManager;
070import hdf.view.TreeView.DefaultTreeView;
071import hdf.view.TreeView.TreeView;
072import hdf.view.dialog.InputDialog;
073import hdf.view.dialog.NewStringAttributeDialog;
074import hdf.view.dialog.NewDataObjectDialog;
075import hdf.view.dialog.NewScalarAttributeDialog;
076//import hdf.view.dialog.NewCompoundAttributeDialog;
077
078/**
079 * DefaultBaseMetaDataView is a default implementation of the MetaDataView which
080 * is used to show data properties of an object. Data properties include
081 * attributes and general object information such as the object type, data type
082 * and data space.
083 *
084 * This base class is responsible for displaying an object's general information
085 * and attributes, since these are not object-specific. Subclasses of this class
086 * are responsible for displaying any extra object-specific content by
087 * overriding the addObjectSpecificContent() method.
088 *
089 * @author Jordan T. Henderson
090 * @version 1.0 4/20/2018
091 */
092public abstract class DefaultBaseMetaDataView implements MetaDataView {
093
094    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DefaultBaseMetaDataView.class);
095
096    /** The default display */
097    protected final Display               display = Display.getDefault();
098
099    /** The view manger reference */
100    protected final DataViewManager       viewManager;
101
102    private final Composite               parent;
103
104    /** The metadata container */
105    protected final TabFolder             contentTabFolder;
106
107    /** The attribute metadata pane */
108    protected final Composite             attributeInfoPane;
109
110    /** The general metadata pane */
111    protected final Composite             generalObjectInfoPane;
112
113    /** The current font */
114    protected Font                        curFont;
115
116    /** The HDF data object */
117    protected HObject                     dataObject;
118
119    /* The table to hold the list of attributes attached to the HDF object */
120    private Table                         attrTable;
121
122    private Label                         attrNumberLabel;
123
124    private List<?>                       attrList;
125
126    private int                           numAttributes;
127
128    /** The HDF data object is hdf5 type */
129    protected boolean                     isH5;
130    /** The HDF data object is hdf4 type */
131    protected boolean                     isH4;
132    /** The HDF data object is netcdf type */
133    protected boolean                     isN3;
134
135    private static final String[]         attrTableColNames = { "Name", "Type", "Array Size", "Value[50](...)" };
136
137    private static final int              ATTR_TAB_INDEX = 0;
138    private static final int              GENERAL_TAB_INDEX = 1;
139
140    /**
141     *The metadata view interface for displaying metadata information
142     *
143     * @param parentComposite
144     *        the parent visual object
145     * @param viewer
146     *        the viewer to use
147     * @param theObj
148     *        the object to display the metadata info
149     */
150    public DefaultBaseMetaDataView(Composite parentComposite, DataViewManager viewer, HObject theObj) {
151        this.parent = parentComposite;
152        this.viewManager = viewer;
153        this.dataObject = theObj;
154
155        numAttributes = 0;
156
157        isH5 = dataObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5));
158        isH4 = dataObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF4));
159        isN3 = dataObject.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_NC3));
160
161        try {
162            curFont = new Font(display, ViewProperties.getFontType(), ViewProperties.getFontSize(), SWT.NORMAL);
163        }
164        catch (Exception ex) {
165            curFont = null;
166        }
167
168        /* Get the metadata information before adding GUI components */
169        try {
170            attrList = ((MetaDataContainer) dataObject).getMetadata();
171            if (attrList != null)
172                numAttributes = attrList.size();
173        }
174        catch (Exception ex) {
175            attrList = null;
176            log.debug("Error retrieving metadata of object '" + dataObject.getName() + "':", ex);
177        }
178
179        log.trace("dataObject={} isN3={} isH4={} isH5={} numAttributes={}", dataObject, isN3, isH4, isH5, numAttributes);
180
181        contentTabFolder = new TabFolder(parent, SWT.NONE);
182        contentTabFolder.addSelectionListener(new SelectionAdapter() {
183            @Override
184            public void widgetSelected(SelectionEvent e) {
185                switch (contentTabFolder.getSelectionIndex()) {
186                    case ATTR_TAB_INDEX:
187                        parent.setData("MetaDataView.LastTabIndex", ATTR_TAB_INDEX);
188                        break;
189                    case GENERAL_TAB_INDEX:
190                    default:
191                        parent.setData("MetaDataView.LastTabIndex", GENERAL_TAB_INDEX);
192                        break;
193                }
194            }
195        });
196
197        attributeInfoPane = createAttributeInfoPane(contentTabFolder, dataObject);
198        if (attributeInfoPane != null) {
199            TabItem attributeInfoItem = new TabItem(contentTabFolder, SWT.None, ATTR_TAB_INDEX);
200            attributeInfoItem.setText("Object Attribute Info");
201            attributeInfoItem.setControl(attributeInfoPane);
202        }
203
204        generalObjectInfoPane = createGeneralObjectInfoPane(contentTabFolder, dataObject);
205        if (generalObjectInfoPane != null) {
206            TabItem generalInfoItem = new TabItem(contentTabFolder, SWT.None, GENERAL_TAB_INDEX);
207            generalInfoItem.setText("General Object Info");
208            generalInfoItem.setControl(generalObjectInfoPane);
209        }
210
211        /* Add any extra information depending on the object type */
212        try {
213            addObjectSpecificContent();
214        }
215        catch (UnsupportedOperationException ex) {
216        }
217
218        if (parent instanceof ScrolledComposite)
219            ((ScrolledComposite) parent).setContent(contentTabFolder);
220
221        /*
222         * If the MetaDataView.LastTabIndex key data exists in the parent
223         * composite, retrieve its value to determine which remembered
224         * tab to select.
225         */
226        Object lastTabObject = parent.getData("MetaDataView.LastTabIndex");
227        if (lastTabObject != null) {
228            contentTabFolder.setSelection((int) lastTabObject);
229        }
230    }
231
232    /**
233     * Additional metadata to display
234     */
235    protected abstract void addObjectSpecificContent();
236
237    private Composite createAttributeInfoPane(Composite parent, final HObject dataObject) {
238        if (parent == null || dataObject == null) return null;
239
240        org.eclipse.swt.widgets.Group attributeInfoGroup = null;
241
242        attributeInfoGroup = new org.eclipse.swt.widgets.Group(parent, SWT.NONE);
243        attributeInfoGroup.setFont(curFont);
244        attributeInfoGroup.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW));
245        attributeInfoGroup.setLayout(new GridLayout(3, false));
246        attributeInfoGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
247
248        if (isH5) {
249            StringBuilder objCreationStr = new StringBuilder("Creation Order NOT Tracked");
250            long ocplID = -1;
251            long objid = -1;
252            int creationOrder = 0;
253            try {
254                objid = dataObject.open();
255                if (objid >= 0) {
256                    if (dataObject instanceof Group) {
257                        ocplID = H5.H5Gget_create_plist(objid);
258                    }
259                    else if (dataObject instanceof Dataset) {
260                        ocplID = H5.H5Dget_create_plist(objid);
261                    }
262                    if (ocplID >= 0) {
263                        creationOrder = H5.H5Pget_attr_creation_order(ocplID);
264                        log.trace("createAttributeInfoPane(): creationOrder={}", creationOrder);
265                        if ((creationOrder & HDF5Constants.H5P_CRT_ORDER_TRACKED) > 0) {
266                            objCreationStr.setLength(0);
267                            objCreationStr.append("Creation Order Tracked");
268                            if ((creationOrder & HDF5Constants.H5P_CRT_ORDER_INDEXED) > 0)
269                                objCreationStr.append(" and Indexed");
270                        }
271                    }
272                }
273            }
274            finally {
275                H5.H5Pclose(ocplID);
276                dataObject.close(objid);
277            }
278
279            /* Creation order section */
280            Label label;
281            label = new Label(attributeInfoGroup, SWT.LEFT);
282            label.setFont(curFont);
283            label.setText("Attribute Creation Order: ");
284            label.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, false, false));
285
286            Text text;
287            text = new Text(attributeInfoGroup, SWT.SINGLE | SWT.BORDER);
288            text.setEditable(false);
289            text.setFont(curFont);
290            text.setText(objCreationStr.toString());
291            text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
292        }
293
294        log.trace("createAttributeInfoPane(): numAttributes={}", numAttributes);
295
296        attrNumberLabel = new Label(attributeInfoGroup, SWT.RIGHT);
297        attrNumberLabel.setFont(curFont);
298        attrNumberLabel.setText("Number of attributes = 0");
299        attrNumberLabel.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, false, false));
300
301        Button addButton = new Button(attributeInfoGroup, SWT.PUSH);
302        addButton.setFont(curFont);
303        addButton.setText("Add Attribute");
304        addButton.setEnabled(!(dataObject.getFileFormat().isReadOnly()));
305        addButton.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
306        addButton.addSelectionListener(new SelectionAdapter() {
307            @Override
308            public void widgetSelected(SelectionEvent e) {
309                addAttribute(dataObject);
310            }
311        });
312
313        /* Deleting attributes is not supported by HDF4 */
314        Button delButton = new Button(attributeInfoGroup, SWT.PUSH);
315        delButton.setFont(curFont);
316        delButton.setText("Delete Attribute");
317        delButton.setEnabled(isH5 && !(dataObject.getFileFormat().isReadOnly()));
318        delButton.setLayoutData(new GridData(SWT.END, SWT.FILL, false, false));
319        delButton.addSelectionListener(new SelectionAdapter() {
320            @Override
321            public void widgetSelected(SelectionEvent e) {
322                deleteAttribute(dataObject);
323            }
324        });
325
326        attrTable = new Table(attributeInfoGroup, SWT.FULL_SELECTION | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
327        attrTable.setLinesVisible(true);
328        attrTable.setHeaderVisible(true);
329        attrTable.setFont(curFont);
330        attrTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1));
331
332        Menu attrPopupMenu = createAttributePopupMenu(attrTable);
333        attrTable.setMenu(attrPopupMenu);
334
335        /*
336         * Add a double-click listener for editing attribute values in a separate
337         * TableView
338         */
339        attrTable.addListener(SWT.MouseDoubleClick, new Listener() {
340            @Override
341            public void handleEvent(Event arg0) {
342                int selectionIndex = attrTable.getSelectionIndex();
343                if (selectionIndex < 0) {
344                    Tools.showError(Display.getDefault().getShells()[0], "Select", "No Attribute selected");
345                    return;
346                }
347
348                final TableItem item = attrTable.getItem(selectionIndex);
349
350                viewManager.getTreeView().setDefaultDisplayMode(true);
351
352                try {
353                    Display.getDefault().syncExec(new Runnable() {
354                        @Override
355                        public void run() {
356                            try {
357                                viewManager.getTreeView().showDataContent((HObject) item.getData());
358                            }
359                            catch (Exception ex) {
360                                log.debug("Attribute showDataContent failure: ", ex);
361                            }
362                        }
363                    });
364                }
365                catch (Exception e) {
366                    log.debug("Attribute showDataContent loading manually interrupted");
367                }
368            }
369        });
370
371        /*
372         * Add a right-click listener for showing a menu that has options for renaming
373         * an attribute, editing an attribute, or deleting an attribute
374         */
375        attrTable.addListener(SWT.MenuDetect, new Listener() {
376            @Override
377            public void handleEvent(Event arg0) {
378                int index = attrTable.getSelectionIndex();
379                if (index < 0) return;
380
381                attrTable.getMenu().setVisible(true);
382            }
383        });
384
385        for (int i = 0; i < attrTableColNames.length; i++) {
386            TableColumn column = new TableColumn(attrTable, SWT.NONE);
387            column.setText(attrTableColNames[i]);
388            column.setMoveable(false);
389
390            /*
391             * Make sure all columns show even when the object in question has no attributes
392             */
393            if (i == attrTableColNames.length - 1)
394                column.setWidth(200);
395            else
396                column.setWidth(50);
397        }
398
399        if (attrList != null) {
400            attrNumberLabel.setText("Number of attributes = " + numAttributes);
401
402            Attribute attr = null;
403            for (int i = 0; i < numAttributes; i++) {
404                attr = (Attribute) attrList.get(i);
405
406                log.trace("createAttributeInfoPane(): attr[{}] is {} of type {}", i, attr.getAttributeName(),
407                        attr.getAttributeDatatype().getDescription());
408
409                addAttributeTableItem(attrTable, attr);
410            }
411        }
412
413        for (int i = 0; i < attrTableColNames.length; i++) {
414            attrTable.getColumn(i).pack();
415        }
416
417        // Prevent attributes with many values, such as array types, from making
418        // the window too wide
419        attrTable.getColumn(3).setWidth(200);
420
421        return attributeInfoGroup;
422    }
423
424    private Composite createGeneralObjectInfoPane(Composite parent, final HObject dataObject) {
425        if (parent == null || dataObject == null) return null;
426
427        FileFormat theFile = dataObject.getFileFormat();
428        boolean isRoot = ((dataObject instanceof Group) && ((Group) dataObject).isRoot());
429        String objTypeStr = "Unknown";
430        Label label;
431        Text text;
432
433        /* Add an SWT Group to encompass all of the GUI components */
434        org.eclipse.swt.widgets.Group generalInfoGroup = new org.eclipse.swt.widgets.Group(parent, SWT.NONE);
435        generalInfoGroup.setFont(curFont);
436        generalInfoGroup.setLayout(new GridLayout(2, false));
437        generalInfoGroup.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_LIGHT_SHADOW));
438
439        /* Object name section */
440        label = new Label(generalInfoGroup, SWT.LEFT);
441        label.setFont(curFont);
442        label.setText("Name: ");
443
444        text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
445        text.setEditable(false);
446        text.setFont(curFont);
447        text.setText(dataObject.getName());
448        text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
449
450        /* Object Path section */
451        label = new Label(generalInfoGroup, SWT.LEFT);
452        label.setFont(curFont);
453        label.setText("Path: ");
454
455        text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
456        text.setEditable(false);
457        text.setFont(curFont);
458        text.setText(dataObject.getPath() == null ? "/"
459                : dataObject.getPath()); /* TODO: temporary workaround until Object Library is fixed */
460        text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
461
462        /* Object Type section */
463        label = new Label(generalInfoGroup, SWT.LEFT);
464        label.setFont(curFont);
465        label.setText("Type: ");
466
467        if (isH5) {
468            if (dataObject instanceof Group) {
469                objTypeStr = "HDF5 Group";
470            }
471            else if (dataObject instanceof ScalarDS) {
472                objTypeStr = "HDF5 Dataset";
473            }
474            else if (dataObject instanceof CompoundDS) {
475                objTypeStr = "HDF5 Dataset";
476            }
477            else if (dataObject instanceof Datatype) {
478                objTypeStr = "HDF5 Named Datatype";
479            }
480            else {
481                log.debug("createGeneralObjectInfoPane(): unknown HDF5 dataObject");
482            }
483        }
484        else if (isH4) {
485            if (dataObject instanceof Group) {
486                objTypeStr = "HDF4 Group";
487            }
488            else if (dataObject instanceof ScalarDS) {
489                ScalarDS ds = (ScalarDS) dataObject;
490                if (ds.isImage()) {
491                    objTypeStr = "HDF4 Raster Image";
492                }
493                else {
494                    objTypeStr = "HDF4 SDS";
495                }
496            }
497            else if (dataObject instanceof CompoundDS) {
498                objTypeStr = "HDF4 Vdata";
499            }
500            else {
501                log.debug("createGeneralObjectInfoPane(): unknown HDF4 dataObject");
502            }
503        }
504        else if (isN3) {
505            if (dataObject instanceof Group) {
506                objTypeStr = "netCDF3 Group";
507            }
508            else if (dataObject instanceof ScalarDS) {
509                objTypeStr = "netCDF3 Dataset";
510            }
511            else {
512                log.debug("createGeneralObjectInfoPane(): unknown netCDF3 dataObject");
513            }
514        }
515        else {
516            if (dataObject instanceof Group) {
517                objTypeStr = "Group";
518            }
519            else if (dataObject instanceof ScalarDS) {
520                objTypeStr = "Dataset";
521            }
522            else if (dataObject instanceof CompoundDS) {
523                objTypeStr = "Dataset";
524            }
525            else {
526                log.debug("createGeneralObjectInfoPane(): unknown dataObject");
527            }
528        }
529
530        text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
531        text.setEditable(false);
532        text.setFont(curFont);
533        text.setText(objTypeStr);
534        text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
535
536        /* Object ID section */
537
538        // bug #926 to remove the OID, put it back on Nov. 20, 2008, --PC
539        String oidStr = null;
540        long[] oID = dataObject.getOID();
541        if (oID != null) {
542            oidStr = String.valueOf(oID[0]);
543            if (isH4)
544                oidStr += ", " + oID[1];
545
546            if (isH5) {
547                label = new Label(generalInfoGroup, SWT.LEFT);
548                label.setFont(curFont);
549                label.setText("Object Ref:       ");
550            }
551            else {
552                label = new Label(generalInfoGroup, SWT.LEFT);
553                label.setFont(curFont);
554                label.setText("Tag, Ref:        ");
555            }
556
557            text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
558            text.setEditable(false);
559            text.setFont(curFont);
560            text.setText(oidStr);
561            text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
562        }
563
564        /*
565         * If this is the root group, add some special extra info, such as the Library
566         * Version bounds set for the file.
567         */
568        if (isRoot) {
569            /* Get the file's size */
570            long fileSize = 0;
571            try {
572                fileSize = (new File(dataObject.getFile())).length();
573            }
574            catch (Exception ex) {
575                fileSize = -1;
576            }
577            fileSize /= 1024;
578
579            /* Retrieve the number of subgroups and datasets in the root group */
580            HObject root = theFile.getRootObject();
581            HObject theObj = null;
582            Iterator<HObject> it = ((Group) root).depthFirstMemberList().iterator();
583            int groupCount = 0;
584            int datasetCount = 0;
585
586            while (it.hasNext()) {
587                theObj = it.next();
588
589                if (theObj instanceof Group)
590                    groupCount++;
591                else
592                    datasetCount++;
593            }
594
595            /* Append all of the file's information to the general object info pane */
596            String fileInfo = "";
597
598            fileInfo = "size=" + fileSize + "K,  groups=" + groupCount + ",  datasets=" + datasetCount;
599
600            /* File name section */
601            label = new Label(generalInfoGroup, SWT.LEFT);
602            label.setFont(curFont);
603            label.setText("File Name: ");
604
605            text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
606            text.setEditable(false);
607            text.setFont(curFont);
608            text.setText(dataObject.getFileFormat().getName());
609            text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
610
611            /* File Path section */
612            label = new Label(generalInfoGroup, SWT.LEFT);
613            label.setFont(curFont);
614            label.setText("File Path: ");
615
616            text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
617            text.setEditable(false);
618            text.setFont(curFont);
619            text.setText((new File(dataObject.getFile())).getParent());
620            text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
621
622            label = new Label(generalInfoGroup, SWT.LEFT);
623            label.setFont(curFont);
624            label.setText("File Type: ");
625
626            if (isH5)
627                objTypeStr = "HDF5,  " + fileInfo;
628            else if (isH4)
629                objTypeStr = "HDF4,  " + fileInfo;
630            else if (isN3)
631                objTypeStr = "netCDF3,  " + fileInfo;
632            else
633                objTypeStr = fileInfo;
634
635            text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
636            text.setEditable(false);
637            text.setFont(curFont);
638            text.setText(objTypeStr);
639            text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
640
641            if (isH5) {
642                log.trace("createGeneralObjectInfoPane(): get Library Version bounds info");
643                String libversion = "";
644                try {
645                    libversion = dataObject.getFileFormat().getLibBoundsDescription();
646                }
647                catch (Exception ex) {
648                    log.debug("Get Library Bounds Description failure: ", ex);
649                }
650
651                if (libversion.length() > 0) {
652                    label = new Label(generalInfoGroup, SWT.LEFT);
653                    label.setFont(curFont);
654                    label.setText("Library version bounds: ");
655
656                    text = new Text(generalInfoGroup, SWT.SINGLE | SWT.BORDER);
657                    text.setEditable(false);
658                    text.setFont(curFont);
659                    text.setText(libversion);
660                    text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
661                }
662
663                Button userBlockButton = new Button(generalInfoGroup, SWT.PUSH);
664                userBlockButton.setText("Show User Block");
665                userBlockButton.addSelectionListener(new SelectionAdapter() {
666                    @Override
667                    public void widgetSelected(SelectionEvent e) {
668                        new UserBlockDialog(display.getShells()[0], SWT.NONE, dataObject).open();
669                    }
670                });
671            }
672        }
673
674        /* Add a dummy label to take up some vertical space between sections */
675        label = new Label(generalInfoGroup, SWT.LEFT);
676        label.setText("");
677        label.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
678
679        return generalInfoGroup;
680    }
681
682    @Override
683    public HObject getDataObject() {
684        return dataObject;
685    }
686
687    private Menu createAttributePopupMenu(final Table table) {
688        final Menu menu = new Menu(table);
689        MenuItem item;
690
691        item = new MenuItem(menu, SWT.PUSH);
692        item.setText("Rename Attribute");
693        item.addSelectionListener(new SelectionAdapter() {
694            @Override
695            public void widgetSelected(SelectionEvent e) {
696                int selectionIndex = table.getSelectionIndex();
697                if (selectionIndex < 0) {
698                    Tools.showError(Display.getDefault().getShells()[0], "Select", "No Attribute selected");
699                    return;
700                }
701
702                HObject itemObj = (HObject) table.getItem(selectionIndex).getData();
703                String result = new InputDialog(Display.getDefault().getShells()[0],
704                        Display.getDefault().getShells()[0].getText() + " - Rename Attribute", "New Attribute Name",
705                        itemObj.getName()).open();
706
707                if ((result == null) || ((result = result.trim()) == null) || (result.length() < 1)) {
708                    return;
709                }
710
711                Attribute attr = (Attribute) attrTable.getItem(selectionIndex).getData();
712                renameAttribute(attr, result);
713            }
714        });
715
716        item = new MenuItem(menu, SWT.PUSH);
717        item.setText("View/Edit Attribute Value");
718        item.addSelectionListener(new SelectionAdapter() {
719            @Override
720            public void widgetSelected(SelectionEvent e) {
721                int selectionIndex = attrTable.getSelectionIndex();
722                if (selectionIndex < 0) {
723                    Tools.showError(Display.getDefault().getShells()[0], "Select", "No Attribute selected");
724                    return;
725                }
726
727                final TableItem item = attrTable.getItem(selectionIndex);
728
729                viewManager.getTreeView().setDefaultDisplayMode(true);
730
731                try {
732                    Display.getDefault().syncExec(new Runnable() {
733                        @Override
734                        public void run() {
735                            try {
736                                viewManager.getTreeView().showDataContent((HObject) item.getData());
737                            }
738                            catch (Exception ex) {
739                                log.debug("Attribute showDataContent failure: ", ex);
740                            }
741                        }
742                    });
743                }
744                catch (Exception ex) {
745                    log.debug("Attribute showDataContent loading manually interrupted");
746                }
747            }
748        });
749
750        item = new MenuItem(menu, SWT.PUSH);
751        item.setText("Delete Attribute");
752        item.addSelectionListener(new SelectionAdapter() {
753            @Override
754            public void widgetSelected(SelectionEvent e) {
755                int selectionIndex = attrTable.getSelectionIndex();
756                if (selectionIndex < 0) {
757                    Tools.showError(Display.getDefault().getShells()[0], "Select", "No Attribute selected");
758                    return;
759                }
760
761                deleteAttribute(dataObject);
762            }
763        });
764
765        menu.addMenuListener(new MenuAdapter() {
766            @Override
767            public void menuShown(MenuEvent e) {
768                /* 'Rename Attribute' MenuItem */
769                menu.getItem(0).setEnabled(!dataObject.getFileFormat().isReadOnly() && !isH4);
770
771                /* 'Delete Attribute' MenuItem */
772                menu.getItem(2).setEnabled(!dataObject.getFileFormat().isReadOnly() && !isH4);
773            }
774        });
775
776        return menu;
777    }
778
779    @Override
780    public Attribute addAttribute(HObject obj) {
781        if (obj == null) return null;
782
783        HObject root = obj.getFileFormat().getRootObject();
784        Attribute attr = null;
785        if (isH5) {
786            NewScalarAttributeDialog dialog = new NewScalarAttributeDialog(display.getShells()[0], obj,
787                    ((Group) root).breadthFirstMemberList());
788            dialog.open();
789            attr = dialog.getAttribute();
790        }
791        else {
792            NewStringAttributeDialog dialog = new NewStringAttributeDialog(display.getShells()[0], obj,
793                    ((Group) root).breadthFirstMemberList());
794            dialog.open();
795            attr = dialog.getAttribute();
796        }
797
798        if (attr == null) {
799            log.debug("addAttribute(): attr is null");
800            return null;
801        }
802
803        addAttributeTableItem(attrTable, attr);
804
805        numAttributes++;
806        attrNumberLabel.setText("Number of attributes = " + numAttributes);
807
808        if (viewManager.getTreeView() instanceof DefaultTreeView)
809            ((DefaultTreeView) viewManager.getTreeView()).updateItemIcon(obj);
810
811        return attr;
812    }
813
814    @Override
815    public Attribute deleteAttribute(HObject obj) {
816        if (obj == null) return null;
817
818        int idx = attrTable.getSelectionIndex();
819        if (idx < 0) {
820            log.debug("deleteAttribute(): no attribute is selected");
821            Tools.showError(display.getShells()[0], "Delete", "No attribute is selected.");
822            return null;
823        }
824
825        int answer = SWT.NO;
826        if (Tools.showConfirm(display.getShells()[0], "Delete",
827                "Do you want to delete the selected attribute?"))
828            answer = SWT.YES;
829        if (answer == SWT.NO) {
830            log.trace("deleteAttribute(): attribute deletion cancelled");
831            return null;
832        }
833
834        if (attrList == null) {
835            log.debug("deleteAttribute(): Attribute list was null; can't delete an attribute from it");
836            return null;
837        }
838
839        Attribute attr = (Attribute) attrList.get(idx);
840
841        log.trace("deleteAttribute(): Attribute selected for deletion: {}", attr.getAttributeName());
842
843        try {
844            ((MetaDataContainer) obj).removeMetadata(attr);
845        }
846        catch (Exception ex) {
847            log.debug("deleteAttribute(): attribute deletion failed for object '{}': ", obj.getName(), ex);
848        }
849
850        attrTable.remove(idx);
851        numAttributes--;
852
853        attrNumberLabel.setText("Number of attributes = " + numAttributes);
854
855        if (viewManager.getTreeView() instanceof DefaultTreeView)
856            ((DefaultTreeView) viewManager.getTreeView()).updateItemIcon(obj);
857
858        return attr;
859    }
860
861    private void renameAttribute(Attribute attr, String newName) {
862        if ((attr == null) || (newName == null) || (newName = newName.trim()) == null || (newName.length() < 1)) {
863            log.debug("renameAttribute(): Attribute is null or Attribute's new name is null");
864            return;
865        }
866
867        String attrName = attr.getAttributeName();
868
869        log.trace("renameAttribute(): oldName={} newName={}", attrName, newName);
870
871        if (isH5) {
872            try {
873                dataObject.getFileFormat().renameAttribute(dataObject, attrName, newName);
874            }
875            catch (Exception ex) {
876                log.debug("renameAttribute(): renaming failure:", ex);
877                Tools.showError(display.getShells()[0], "Delete", ex.getMessage());
878            }
879
880            /* Update the attribute table */
881            int selectionIndex = attrTable.getSelectionIndex();
882            if (selectionIndex < 0) {
883                Tools.showError(Display.getDefault().getShells()[0], "Delete", "No Attribute selected");
884                return;
885            }
886
887            attrTable.getItem(selectionIndex).setText(0, newName);
888        }
889        else {
890            log.debug("renameAttribute(): renaming attributes is only allowed for HDF5 files");
891        }
892
893        if (dataObject instanceof MetaDataContainer) {
894            try {
895                ((MetaDataContainer) dataObject).updateMetadata(attr);
896            }
897            catch (Exception ex) {
898                log.debug("renameAttribute(): updateMetadata() failure:", ex);
899                Tools.showError(display.getShells()[0], "Delete", ex.getMessage());
900            }
901        }
902    }
903
904    /**
905     * Update an attribute's value. Currently can only update a single data point.
906     *
907     * @param attr
908     *            the selected attribute.
909     * @param newValue
910     *            the string of the new value.
911     */
912    private void updateAttributeValue(Attribute attr, String newValue) {
913        if ((attr == null) || (newValue == null) || (newValue = newValue.trim()) == null || (newValue.length() < 1)) {
914            log.debug("updateAttributeValue(): Attribute is null or Attribute's new value is null");
915            return;
916        }
917
918        String attrName = attr.getAttributeName();
919        Object data;
920
921        log.trace("updateAttributeValue(): changing value of attribute '{}'", attrName);
922
923        try {
924            data = attr.getAttributeData();
925        }
926        catch (Exception ex) {
927            log.debug("updateAttributeValue(): getData() failure:", ex);
928            return;
929        }
930
931        if (data == null) {
932            log.debug("updateAttributeValue(): attribute's data was null");
933            return;
934        }
935
936        int arrayLength = Array.getLength(data);
937        StringTokenizer st = new StringTokenizer(newValue, ",");
938        if (st.countTokens() < arrayLength) {
939            log.debug("updateAttributeValue(): More data values needed: {}", newValue);
940            Tools.showError(display.getShells()[0], "Update", "More data values needed: " + newValue);
941            return;
942        }
943
944        char cNT = ' ';
945        String cName = data.getClass().getName();
946        int cIndex = cName.lastIndexOf('[');
947        if (cIndex >= 0) {
948            cNT = cName.charAt(cIndex + 1);
949        }
950        boolean isUnsigned = attr.getAttributeDatatype().isUnsigned();
951
952        log.trace("updateAttributeValue(): array_length={} cName={} NT={} isUnsigned={}", arrayLength, cName,
953                cNT, isUnsigned);
954
955        double d = 0;
956        String theToken = null;
957        long max = 0;
958        long min = 0;
959        for (int i = 0; i < arrayLength; i++) {
960            max = min = 0;
961            theToken = st.nextToken().trim();
962            try {
963                if (!(Array.get(data, i) instanceof String)) {
964                    d = Double.parseDouble(theToken);
965                }
966            }
967            catch (NumberFormatException ex) {
968                log.debug("updateAttributeValue(): NumberFormatException: ", ex);
969                Tools.showError(display.getShells()[0], "Update", ex.getMessage());
970                return;
971            }
972
973            if (isUnsigned && (d < 0)) {
974                log.debug("updateAttributeValue(): Negative value for unsigned integer: {}", theToken);
975                Tools.showError(display.getShells()[0], "Update", "Negative value for unsigned integer: " + theToken);
976                return;
977            }
978
979            switch (cNT) {
980                case 'B': {
981                    if (isUnsigned) {
982                        min = 0;
983                        max = 255;
984                    }
985                    else {
986                        min = Byte.MIN_VALUE;
987                        max = Byte.MAX_VALUE;
988                    }
989
990                    if ((d > max) || (d < min)) {
991                        Tools.showError(display.getShells()[0], "Update",
992                                "Data is out of range[" + min + ", " + max + "]: " + theToken);
993                    }
994                    else {
995                        Array.setByte(data, i, (byte) d);
996                    }
997                    break;
998                }
999                case 'S': {
1000                    if (isUnsigned) {
1001                        min = 0;
1002                        max = 65535;
1003                    }
1004                    else {
1005                        min = Short.MIN_VALUE;
1006                        max = Short.MAX_VALUE;
1007                    }
1008
1009                    if ((d > max) || (d < min)) {
1010                        Tools.showError(display.getShells()[0], "Update",
1011                                "Data is out of range[" + min + ", " + max + "]: " + theToken);
1012                    }
1013                    else {
1014                        Array.setShort(data, i, (short) d);
1015                    }
1016                    break;
1017                }
1018                case 'I': {
1019                    if (isUnsigned) {
1020                        min = 0;
1021                        max = 4294967295L;
1022                    }
1023                    else {
1024                        min = Integer.MIN_VALUE;
1025                        max = Integer.MAX_VALUE;
1026                    }
1027
1028                    if ((d > max) || (d < min)) {
1029                        Tools.showError(display.getShells()[0], "Update",
1030                                "Data is out of range[" + min + ", " + max + "]: " + theToken);
1031                    }
1032                    else {
1033                        Array.setInt(data, i, (int) d);
1034                    }
1035                    break;
1036                }
1037                case 'J':
1038                    long lvalue = 0;
1039                    if (isUnsigned) {
1040                        if (theToken != null) {
1041                            String theValue = theToken;
1042                            BigInteger maxJ = new BigInteger("18446744073709551615");
1043                            BigInteger big = new BigInteger(theValue);
1044                            if ((big.compareTo(maxJ) > 0) || (big.compareTo(BigInteger.ZERO) < 0)) {
1045                                Tools.showError(display.getShells()[0], "Update",
1046                                        "Data is out of range[" + min + ", " + max + "]: " + theToken);
1047                            }
1048                            lvalue = big.longValue();
1049                            log.trace("updateAttributeValue(): big.longValue={}", lvalue);
1050                            Array.setLong(data, i, lvalue);
1051                        }
1052                        else
1053                            Array.set(data, i, theToken);
1054                    }
1055                    else {
1056                        min = Long.MIN_VALUE;
1057                        max = Long.MAX_VALUE;
1058                        if ((d > max) || (d < min)) {
1059                            Tools.showError(display.getShells()[0], "Update",
1060                                    "Data is out of range[" + min + ", " + max + "]: " + theToken);
1061                        }
1062                        lvalue = (long) d;
1063                        log.trace("updateAttributeValue(): longValue={}", lvalue);
1064                        Array.setLong(data, i, lvalue);
1065                    }
1066                    break;
1067                case 'F':
1068                    Array.setFloat(data, i, (float) d);
1069                    break;
1070                case 'D':
1071                    Array.setDouble(data, i, d);
1072                    break;
1073                default:
1074                    Array.set(data, i, theToken);
1075                    break;
1076            }
1077        }
1078
1079        try {
1080            dataObject.getFileFormat().writeAttribute(dataObject, attr, true);
1081        }
1082        catch (Exception ex) {
1083            log.debug("updateAttributeValue(): writeAttribute failure: ", ex);
1084            Tools.showError(display.getShells()[0], "Update", ex.getMessage());
1085            return;
1086        }
1087
1088        /* Update the attribute table */
1089        int selectionIndex = attrTable.getSelectionIndex();
1090        if (selectionIndex < 0) {
1091            Tools.showError(Display.getDefault().getShells()[0], "Update", "No Attribute selected");
1092            return;
1093        }
1094
1095        attrTable.getItem(selectionIndex).setText(3, attr.toAttributeString(", "));
1096
1097        if (dataObject instanceof MetaDataContainer) {
1098            try {
1099                ((MetaDataContainer) dataObject).updateMetadata(attr);
1100            }
1101            catch (Exception ex) {
1102                log.debug("updateAttributeValue(): updateMetadata() failure:", ex);
1103                Tools.showError(display.getShells()[0], "Update", ex.getMessage());
1104            }
1105        }
1106    }
1107
1108    private void addAttributeTableItem(Table table, Attribute attr) {
1109        if (table == null || attr == null) {
1110            log.debug("addAttributeTableItem(): table or attribute is null");
1111            return;
1112        }
1113
1114        String attrName = attr.getAttributeName();
1115        String attrType = attr.getAttributeDatatype().getDescription();
1116        StringBuilder attrSize = new StringBuilder();
1117        String attrValue = attr.toAttributeString(", ", 50);
1118        String[] rowData = new String[attrTableColNames.length];
1119
1120        if (attrName == null) attrName = "null";
1121        if (attrType == null) attrType = "null";
1122        if (attrValue == null) attrValue = "null";
1123
1124        TableItem item = new TableItem(attrTable, SWT.NONE);
1125        item.setFont(curFont);
1126        item.setData(attr);
1127
1128        if (attr.getProperty("field") != null) {
1129            rowData[0] = attrName + " {Field: " + attr.getProperty("field") + "}";
1130        }
1131        else {
1132            rowData[0] = attrName;
1133        }
1134
1135        if (attr.isAttributeScalar()) {
1136            attrSize.append("Scalar");
1137        }
1138        else {
1139            long[] dims = attr.getAttributeDims();
1140            attrSize.append(String.valueOf(dims[0]));
1141            for (int j = 1; j < dims.length; j++) {
1142                attrSize.append(" x ").append(dims[j]);
1143            }
1144        }
1145
1146        rowData[1] = attrType;
1147        rowData[2] = attrSize.toString();
1148        rowData[3] = attrValue;
1149
1150        item.setText(rowData);
1151    }
1152
1153    private class UserBlockDialog extends Dialog {
1154        private Shell          shell;
1155
1156        private final HObject  obj;
1157
1158        private byte[]         userBlock;
1159
1160        private final String[] displayChoices = { "Text", "Binary", "Octal", "Hexadecimal", "Decimal" };
1161
1162        private Button         jamButton;
1163        private Text           userBlockArea;
1164
1165        public UserBlockDialog(Shell parent, int style, HObject obj) {
1166            super(parent, style);
1167
1168            this.obj = obj;
1169
1170            userBlock = Tools.getHDF5UserBlock(obj.getFile());
1171        }
1172
1173        public void open() {
1174            Shell openParent = getParent();
1175            shell = new Shell(openParent, SWT.DIALOG_TRIM | SWT.RESIZE);
1176            shell.setFont(curFont);
1177            shell.setText("User Block - " + obj);
1178            shell.setLayout(new GridLayout(5, false));
1179
1180            Label label = new Label(shell, SWT.RIGHT);
1181            label.setFont(curFont);
1182            label.setText("Display As: ");
1183
1184            Combo userBlockDisplayChoice = new Combo(shell, SWT.SINGLE | SWT.READ_ONLY);
1185            userBlockDisplayChoice.setFont(curFont);
1186            userBlockDisplayChoice.setItems(displayChoices);
1187            userBlockDisplayChoice.select(0);
1188            userBlockDisplayChoice.addSelectionListener(new SelectionAdapter() {
1189                @Override
1190                public void widgetSelected(SelectionEvent e) {
1191                    Combo source = (Combo) e.widget;
1192                    int type = 0;
1193
1194                    String typeName = source.getItem(source.getSelectionIndex());
1195
1196                    jamButton.setEnabled(false);
1197                    userBlockArea.setEditable(false);
1198
1199                    if (typeName.equalsIgnoreCase("Text")) {
1200                        type = 0;
1201                        jamButton.setEnabled(true);
1202                        userBlockArea.setEditable(true);
1203                    }
1204                    else if (typeName.equalsIgnoreCase("Binary")) {
1205                        type = 2;
1206                    }
1207                    else if (typeName.equalsIgnoreCase("Octal")) {
1208                        type = 8;
1209                    }
1210                    else if (typeName.equalsIgnoreCase("Hexadecimal")) {
1211                        type = 16;
1212                    }
1213                    else if (typeName.equalsIgnoreCase("Decimal")) {
1214                        type = 10;
1215                    }
1216
1217                    showUserBlockAs(type);
1218                }
1219            });
1220
1221            Label dummyLabel = new Label(shell, SWT.RIGHT);
1222            dummyLabel.setFont(curFont);
1223            dummyLabel.setText("");
1224            dummyLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
1225
1226            Label sizeLabel = new Label(shell, SWT.RIGHT);
1227            sizeLabel.setFont(curFont);
1228            sizeLabel.setText("Header Size (Bytes): 0");
1229
1230            jamButton = new Button(shell, SWT.PUSH);
1231            jamButton.setFont(curFont);
1232            jamButton.setText("Save User Block");
1233            jamButton.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
1234            jamButton.addSelectionListener(new SelectionAdapter() {
1235                @Override
1236                public void widgetSelected(SelectionEvent e) {
1237                    writeUserBlock();
1238                }
1239            });
1240
1241            ScrolledComposite userBlockScroller = new ScrolledComposite(shell, SWT.V_SCROLL | SWT.BORDER);
1242            userBlockScroller.setExpandHorizontal(true);
1243            userBlockScroller.setExpandVertical(true);
1244            userBlockScroller.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 5, 1));
1245
1246            userBlockArea = new Text(userBlockScroller, SWT.MULTI | SWT.WRAP);
1247            userBlockArea.setEditable(true);
1248            userBlockArea.setFont(curFont);
1249            userBlockScroller.setContent(userBlockArea);
1250
1251            Button closeButton = new Button(shell, SWT.CENTER);
1252            closeButton.setFont(curFont);
1253            closeButton.setText(" &Close ");
1254            closeButton.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, false, 5, 1));
1255            closeButton.addSelectionListener(new SelectionAdapter() {
1256                @Override
1257                public void widgetSelected(SelectionEvent e) {
1258                    shell.dispose();
1259                }
1260            });
1261
1262            if (userBlock != null) {
1263                int headSize = showUserBlockAs(0);
1264                sizeLabel.setText("Header Size (Bytes): " + headSize);
1265            }
1266            else {
1267                userBlockDisplayChoice.setEnabled(false);
1268            }
1269
1270            shell.pack();
1271
1272            Rectangle parentBounds = openParent.getBounds();
1273
1274            Point shellSize = new Point((int) (0.5 * parentBounds.width), (int) (0.5 * parentBounds.height));
1275            shell.setSize(shellSize);
1276
1277            shell.setLocation((parentBounds.x + (parentBounds.width / 2)) - (shellSize.x / 2),
1278                    (parentBounds.y + (parentBounds.height / 2)) - (shellSize.y / 2));
1279
1280            shell.open();
1281
1282            Display openDisplay = openParent.getDisplay();
1283            while (!shell.isDisposed()) {
1284                if (!openDisplay.readAndDispatch()) openDisplay.sleep();
1285            }
1286        }
1287
1288        private int showUserBlockAs(int radix) {
1289            if (userBlock == null) return 0;
1290
1291            int headerSize = 0;
1292
1293            String userBlockInfo = null;
1294            if ((radix == 2) || (radix == 8) || (radix == 16) || (radix == 10)) {
1295                StringBuilder sb = new StringBuilder();
1296                for (headerSize = 0; headerSize < userBlock.length; headerSize++) {
1297                    int intValue = userBlock[headerSize];
1298                    if (intValue < 0) {
1299                        intValue += 256;
1300                    }
1301                    else if (intValue == 0) {
1302                        break; // null end
1303                    }
1304
1305                    sb.append(Integer.toString(intValue, radix)).append(" ");
1306                }
1307                userBlockInfo = sb.toString();
1308            }
1309            else {
1310                userBlockInfo = new String(userBlock).trim();
1311                if (userBlockInfo != null) {
1312                    headerSize = userBlockInfo.length();
1313                }
1314            }
1315
1316            userBlockArea.setText(userBlockInfo);
1317
1318            return headerSize;
1319        }
1320
1321        private void writeUserBlock() {
1322            if (!obj.getFileFormat().isThisType(FileFormat.getFileFormat(FileFormat.FILE_TYPE_HDF5))) {
1323                return;
1324            }
1325
1326            int blkSize0 = 0;
1327            if (userBlock != null) {
1328                blkSize0 = userBlock.length;
1329                // The super block space is allocated by offset 0, 512, 1024, 2048, etc
1330                if (blkSize0 > 0) {
1331                    int offset = 512;
1332                    while (offset < blkSize0) {
1333                        offset *= 2;
1334                    }
1335                    blkSize0 = offset;
1336                }
1337            }
1338
1339            int blkSize1 = 0;
1340            String userBlockStr = userBlockArea.getText();
1341            if (userBlockStr == null) {
1342                if (blkSize0 <= 0) {
1343                    return; // nothing to write
1344                }
1345                else {
1346                    userBlockStr = " "; // want to wipe out old userblock content
1347                }
1348            }
1349            byte[] buf = null;
1350            buf = userBlockStr.getBytes();
1351
1352            blkSize1 = buf.length;
1353            if (blkSize1 <= blkSize0) {
1354                java.io.RandomAccessFile raf = null;
1355                try {
1356                    raf = new java.io.RandomAccessFile(obj.getFile(), "rw");
1357                }
1358                catch (Exception ex) {
1359                    Tools.showError(shell, "Save", "Can't open output file: " + obj.getFile());
1360                    return;
1361                }
1362
1363                try {
1364                    raf.seek(0);
1365                    raf.write(buf, 0, buf.length);
1366                    raf.seek(buf.length);
1367                    if (blkSize0 > buf.length) {
1368                        byte[] padBuf = new byte[blkSize0 - buf.length];
1369                        raf.write(padBuf, 0, padBuf.length);
1370                    }
1371                }
1372                catch (Exception ex) {
1373                    log.debug("raf write:", ex);
1374                }
1375                try {
1376                    raf.close();
1377                }
1378                catch (Exception ex) {
1379                    log.debug("raf close:", ex);
1380                }
1381
1382                Tools.showInformation(shell, "Save", "Saving user block is successful.");
1383            }
1384            else {
1385                // must rewrite the whole file
1386                MessageDialog confirm = new MessageDialog(shell, "Save", null,
1387                        "The user block to write is " + blkSize1 + " (bytes),\n"
1388                                + "which is larger than the user block space in file " + blkSize0 + " (bytes).\n"
1389                                + "To expand the user block, the file must be rewritten.\n\n"
1390                                + "Do you want to replace the current file? Click "
1391                                + "\n\"Yes\" to replace the current file," + "\n\"No\" to save to a different file, "
1392                                + "\n\"Cancel\" to quit without saving the change.\n\n ",
1393                                MessageDialog.QUESTION_WITH_CANCEL, new String[] { "Yes", "No", "Cancel" }, 0);
1394                int op = confirm.open();
1395
1396                if (op == 2) return;
1397
1398                String fin = obj.getFile();
1399
1400                String fout = fin + "~copy.h5";
1401                if (fin.endsWith(".h5")) {
1402                    fout = fin.substring(0, fin.length() - 3) + "~copy.h5";
1403                }
1404                else if (fin.endsWith(".hdf5")) {
1405                    fout = fin.substring(0, fin.length() - 5) + "~copy.h5";
1406                }
1407
1408                File outFile = null;
1409
1410                if (op == 1) {
1411                    FileDialog fChooser = new FileDialog(shell, SWT.SAVE);
1412                    fChooser.setFileName(fout);
1413
1414                    DefaultFileFilter filter = DefaultFileFilter.getFileFilterHDF5();
1415                    fChooser.setFilterExtensions(new String[] { "*", filter.getExtensions() });
1416                    fChooser.setFilterNames(new String[] { "All Files", filter.getDescription() });
1417                    fChooser.setFilterIndex(1);
1418
1419                    if (fChooser.open() == null) return;
1420
1421                    File chosenFile = new File(fChooser.getFileName());
1422
1423                    outFile = chosenFile;
1424                    fout = outFile.getAbsolutePath();
1425                }
1426                else {
1427                    outFile = new File(fout);
1428                }
1429
1430                if (!outFile.exists()) {
1431                    try {
1432                        if (!outFile.createNewFile())
1433                            log.debug("Error creating file {}", fout);
1434                    }
1435                    catch (Exception ex) {
1436                        Tools.showError(shell, "Save", "Failed to write user block into file.");
1437                        return;
1438                    }
1439                }
1440
1441                // close the file
1442                TreeView view = viewManager.getTreeView();
1443
1444                try {
1445                    view.closeFile(view.getSelectedFile());
1446                }
1447                catch (Exception ex) {
1448                    log.debug("Error closing file {}", fin);
1449                }
1450
1451                if (Tools.setHDF5UserBlock(fin, fout, buf)) {
1452                    if (op == 1) {
1453                        fin = fout; // open the new file
1454                    }
1455                    else {
1456                        File oldFile = new File(fin);
1457                        boolean status = oldFile.delete();
1458                        if (status) {
1459                            if (!outFile.renameTo(oldFile))
1460                                log.debug("Error renaming file {}", fout);
1461                        }
1462                        else {
1463                            Tools.showError(shell, "Save", "Cannot replace the current file.\nPlease save to a different file.");
1464                            outFile.delete();
1465                        }
1466                    }
1467                }
1468                else {
1469                    Tools.showError(shell, "Save", "Failed to write user block into file.");
1470                    outFile.delete();
1471                }
1472
1473                // reopen the file
1474                shell.dispose();
1475
1476                try {
1477                    view.openFile(fin, ViewProperties.isReadOnly() ? FileFormat.READ : FileFormat.WRITE);
1478                }
1479                catch (Exception ex) {
1480                    log.debug("Error opening file {}", fin);
1481                }
1482            }
1483        }
1484    }
1485}