//
// Template.java
//
/*
OME Notes library for flexible organization and presentation of OME-XML
metadata. Copyright (C) 2006-@year@ Melissa Linkert and Christopher Peterson.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package loci.ome.notes;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.JViewport;
import loci.formats.meta.AggregateMetadata;
/**
* Loads a template from a file, and stores the options associated with this
* template. See template-format.txt for details of how to construct a
* valid template file.
*
* <dl><dt><b>Source code:</b></dt>
* <dd><a href="http://trac.openmicroscopy.org.uk/ome/browser/bioformats.git/components/legacy/ome-notes/src/loci/ome/notes/Template.java">Trac</a>,
* <a href="http://git.openmicroscopy.org/?p=bioformats.git;a=blob;f=components/legacy/ome-notes/src/loci/ome/notes/Template.java;hb=HEAD">Gitweb</a></dd></dl>
*/
public class Template {
// -- Constants --
/** Width to use if a value wasn't specified. */
private static final int DEFAULT_WIDTH = 800;
/** Height to use if a value wasn't specified. */
private static final int DEFAULT_HEIGHT = 700;
/** Font style to use if a value wasn't specified. */
private static final String DEFAULT_FONT = "Arial";
/** Font size to use if a value wasn't specified. */
private static final int DEFAULT_FONT_SIZE = 12;
/** Default font color (black). */
private static final int[] DEFAULT_FONT_COLOR = new int[] {0, 0, 0};
/** Default background color (white). */
private static final int[] DEFAULT_BACKGROUND_COLOR = new int[] {
255, 255, 255};
// -- Fields --
/** Tabs associated with this template. */
private TemplateTab[] tabs;
/** Additional GUI options defined by this template. */
private Hashtable options;
// -- Constructor --
/** Constructs a new Template from the given filename. */
public Template(String file) throws IOException {
this(new File(file).getAbsoluteFile());
}
/** Constructs a new Template from the given file. */
public Template(File file) throws IOException {
this(new FileInputStream(file));
}
/** Constructs a new Template from the given InputStream. */
public Template(InputStream stream) throws IOException {
options = new Hashtable();
try {
parse(stream);
}
catch (IOException io) {
io.printStackTrace();
}
}
/** Constructs a new Template from TemplateTabs and a list of options. */
public Template(TemplateTab[] tabs, Hashtable options) {
this.tabs = tabs;
this.options = options;
}
// -- Template API methods --
/** Parses the template options from the given InputStream. */
public void parse(InputStream is) throws IOException {
BufferedInputStream bis = new BufferedInputStream(is);
byte[] b = new byte[bis.available()];
bis.read(b);
String s = new String(b);
StringTokenizer lines = new StringTokenizer(s, "\n");
boolean openField = false;
boolean openGroup = false;
boolean openTab = false;
StringBuffer field = new StringBuffer();
Vector parsedFields = new Vector();
TemplateGroup currentGroup = null;
TemplateTab currentTab = null;
Vector tempTabs = new Vector();
while (lines.hasMoreTokens()) {
String line = lines.nextToken().trim();
if (line.startsWith("#") || line.length() == 0) continue;
if (line.startsWith("field")) {
openField = true;
field.append(line);
field.append("\n");
}
else if (openField && line.startsWith("}")) {
openField = false;
field.append(line);
field.append("\n");
TemplateField t = new TemplateField((String) field.toString());
if (currentGroup != null) currentGroup.addField(t);
else if (currentTab != null) currentTab.addField(t);
else {
currentTab = new TemplateTab();
currentTab.addField(t);
}
field.delete(0, field.length());
}
else if (openField) {
field.append(line);
field.append("\n");
}
else if (line.startsWith("group")) {
openGroup = true;
currentGroup = new TemplateGroup();
}
else if (openGroup && line.startsWith("name")) {
line = line.substring(line.indexOf(" ")).trim();
line = line.substring(1, line.length() - 1);
currentGroup.setName(line);
}
else if (openGroup && line.startsWith("count")) {
line = line.substring(line.indexOf(" ")).trim();
line = line.substring(1, line.length() - 1);
currentGroup.setRepetitions(Integer.parseInt(line));
}
else if (openGroup && line.startsWith("}")) {
openGroup = false;
if (currentTab == null) currentTab = new TemplateTab();
currentTab.addGroup(currentGroup);
currentGroup = null;
}
else if (line.startsWith("tab")) {
openTab = true;
currentTab = new TemplateTab();
}
else if (openTab && line.startsWith("name")) {
line = line.substring(line.indexOf(" ")).trim();
line = line.substring(1, line.length() - 1);
currentTab.setName(line);
}
else if (openTab && line.startsWith("grid")) {
line = line.substring(line.indexOf(" ")).trim();
line = line.substring(1, line.length() - 1);
int r = Integer.parseInt(line.substring(0, line.indexOf(",")));
int c = Integer.parseInt(line.substring(line.indexOf(",") + 1));
currentTab.setRows(r);
currentTab.setColumns(c);
}
else if (openTab && line.startsWith("}")) {
openTab = false;
tempTabs.add(currentTab);
currentTab = null;
}
else {
String key = line.substring(0, line.indexOf(" ")).trim();
String value = line.substring(line.indexOf(" ")).trim();
value = value.substring(1, value.length() - 1);
options.put(key, value);
}
}
tabs = new TemplateTab[tempTabs.size()];
tempTabs.toArray(tabs);
}
/**
* Populates the fields using the given OME root node and the
* template's OME-CA mapping.
*/
public void populateFields(AggregateMetadata store) {
if (store == null) return;
try {
for (int i=0; i<tabs.length; i++) {
for (int j=0; j<tabs[i].getNumGroups(); j++) {
TemplateGroup group = tabs[i].getGroup(j);
for (int k=0; k<group.getNumFields(); k++) {
if (group.getRepetitions() == 0) {
String map = group.getField(0, k).getValueMap();
group.setRepetitions(TemplateTools.getNodeCount(store, map));
}
for (int r=0; r<group.getRepetitions(); r++) {
populateField(store, group.getField(r, k));
}
}
}
for (int j=0; j<tabs[i].getNumFields(); j++) {
if (!tabs[i].getField(j).getType().equals("thumbnail")) {
populateField(store, tabs[i].getField(j));
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
/** Save fields to given OME root node. */
public void saveFields(AggregateMetadata store) {
if (store == null) return;
try {
for (int i=0; i<tabs.length; i++) {
for (int j=0; j<tabs[i].getNumGroups(); j++) {
TemplateGroup group = tabs[i].getGroup(j);
for (int k=0; k<group.getNumFields(); k++) {
for (int r=0; r<group.getRepetitions(); r++) {
saveField(store, group.getField(r, k));
}
}
}
for (int j=0; j<tabs[i].getNumFields(); j++) {
saveField(store, tabs[i].getField(j));
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
/** Save the template to a file. */
public void save(String filename) throws IOException {
BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
if (options != null) {
Enumeration keys = options.keys();
while (keys.hasMoreElements()) {
String key = keys.nextElement().toString();
writer.write(key + " \"" + options.get(key) + "\"\n");
}
}
if (tabs == null) return;
for (int i=0; i<tabs.length; i++) {
writer.write("tab {\n");
writer.write(" name \"" + tabs[i].getName() + "\"\n");
for (int j=0; j<tabs[i].getNumFields(); j++) {
TemplateField t = tabs[i].getField(j);
writer.write(" field {\n");
writer.write(" name \"" + t.getName() + "\"\n");
writer.write(" type \"" + t.getType() + "\"\n");
if (t.getValueMap() != null) {
writer.write(" valueMap \"" + t.getValueMap() + "\"\n");
}
if (t.getNameMap() != null) {
writer.write(" nameMap \"" + t.getNameMap() + "\"\n");
}
if (t.getDefaultValue() != null) {
writer.write(" default \"" + t.getDefaultValue() + "\"\n");
}
writer.write(" grid \"" + t.getRow() + "," + t.getColumn() + "\"\n");
writer.write(" span \"" + t.getWidth() + "," +
t.getHeight() + "\"\n");
writer.write(" repeated \"" + t.isRepeated() + "\"\n");
String[] enums = t.getEnums();
if (enums != null && enums.length > 0) {
writer.write(" values {");
for (int k=0; k<enums.length; k++) {
writer.write("\"" + enums[k] + "\"");
if (k < enums.length - 1) writer.write(", ");
}
writer.write("}\n");
}
writer.write(" }\n");
}
for (int j=0; j<tabs[i].getNumGroups(); j++) {
TemplateGroup g = tabs[i].getGroup(j);
writer.write(" group {\n");
writer.write(" count \"" + g.getRepetitions() + "\"\n");
writer.write(" name \"" + g.getName() + "\"\n");
for (int k=0; k<g.getNumFields(); k++) {
TemplateField t = g.getField(0, k);
writer.write(" field {\n");
writer.write(" name \"" + t.getName() + "\"\n");
writer.write(" type \"" + t.getType() + "\"\n");
if (t.getValueMap() != null) {
writer.write(" valueMap \"" + t.getValueMap() + "\"\n");
}
if (t.getNameMap() != null) {
writer.write(" nameMap \"" + t.getNameMap() + "\"\n");
}
if (t.getDefaultValue() != null) {
writer.write(" default \"" + t.getDefaultValue() + "\"\n");
}
writer.write(" grid \"" + t.getRow() + "," +
t.getColumn() + "\"\n");
writer.write(" span \"" + t.getWidth() + "," +
t.getHeight() + "\"\n");
writer.write(" repeated \"" + t.isRepeated() + "\"\n");
String[] enums = t.getEnums();
if (enums != null && enums.length > 0) {
writer.write(" values {");
for (int m=0; m<enums.length; m++) {
writer.write("\"" + enums[m] + "\"");
if (m < enums.length - 1) writer.write(", ");
}
writer.write("}\n");
}
writer.write(" }\n");
}
writer.write(" }\n");
}
writer.write("}\n");
}
writer.close();
}
/** Determine number of repetitions for each repeatable field. */
public void initializeFields(AggregateMetadata store) {
for (int i=0; i<tabs.length; i++) {
int fields = tabs[i].getNumFields();
for (int j=0; j<fields; j++) {
if (tabs[i].getField(j).isRepeated()) {
String map = tabs[i].getField(j).getValueMap();
try {
int nodeCount = TemplateTools.getNodeCount(store, map);
for (int k=1; k<nodeCount; k++) {
TemplateField f = tabs[i].getField(j).copy();
if (map.indexOf("OriginalMetadata") != -1) {
f.setValueMap(map + "," + k);
f.setNameMap(f.getNameMap() + "," + k);
}
else {
int paren = map.indexOf("(");
if (paren == -1) f.setValueMap(map + "(" + k + ")");
else {
String prefix = map.substring(0, map.indexOf(")"));
f.setValueMap(prefix + "," + k + ")");
}
String nameMap = f.getNameMap();
paren = nameMap.indexOf("(");
if (paren == -1) f.setNameMap(nameMap + "(" + k + ")");
else {
String prefix = nameMap.substring(0, nameMap.indexOf(")"));
f.setNameMap(prefix + "," + k + ")");
}
}
f.setRow(tabs[i].getField(j).getRow() + k);
tabs[i].addField(f);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
for (int j=0; j<tabs[i].getNumGroups(); j++) {
TemplateGroup g = tabs[i].getGroup(j);
fields = g.getNumFields();
for (int k=0; k<fields; k++) {
TemplateField f = g.getField(0, k).copy();
if (f.isRepeated()) {
String map = f.getValueMap();
try {
for (int m=1; m<TemplateTools.getNodeCount(store, map); m++) {
if (map.indexOf("OriginalMetadata") != -1) {
f.setValueMap(map + "," + k);
f.setNameMap(f.getNameMap() + "," + k);
}
else {
int paren = map.indexOf("(");
if (paren == -1) f.setValueMap(map + "(" + k + ")");
else {
String prefix = map.substring(0, map.indexOf(")"));
f.setValueMap(prefix + "," + k + ")");
}
String nameMap = f.getNameMap();
paren = nameMap.indexOf("(");
if (paren == -1) f.setNameMap(nameMap + "(" + k + ")");
else {
String prefix = nameMap.substring(0, nameMap.indexOf(")"));
f.setNameMap(prefix + "," + k + ")");
}
}
f.setRow(g.getField(0, k).getRow() + m);
g.addField(f);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
}
/** Change the value of an option. */
public void changeValue(String key, String value) {
options.put(key, value);
}
/** Get the tabs defined by this template. */
public TemplateTab[] getTabs() { return tabs; }
/** Get the font style defined by this template. */
public String getFontStyle() {
String style = (String) options.get("font-style");
return style == null ? DEFAULT_FONT : style;
}
/** Get the font size defined by this template. */
public int getFontSize() {
String size = (String) options.get("font-size");
return size == null ? DEFAULT_FONT_SIZE : Integer.parseInt(size);
}
/** Get the font color defined by this template. */
public int[] getFontColor() {
String tuple = (String) options.get("font-color");
if (tuple == null) return DEFAULT_FONT_COLOR;
int comma = tuple.indexOf(",");
String red = tuple.substring(0, comma);
String green = tuple.substring(comma + 1, tuple.indexOf(",", comma + 1));
String blue = tuple.substring(tuple.indexOf(",", comma + 1) + 1);
return new int[] {
Integer.parseInt(red), Integer.parseInt(green), Integer.parseInt(blue)};
}
/** Get the background color defined by this template. */
public int[] getBackgroundColor() {
String tuple = (String) options.get("background-color");
if (tuple == null) return DEFAULT_BACKGROUND_COLOR;
int comma = tuple.indexOf(",");
String red = tuple.substring(0, comma);
String green = tuple.substring(comma + 1, tuple.indexOf(",", comma + 1));
String blue = tuple.substring(tuple.indexOf(",", comma + 1) + 1);
return new int[] {
Integer.parseInt(red), Integer.parseInt(green), Integer.parseInt(blue)};
}
/** Get the default window width defined by this template. */
public int getDefaultWidth() {
String width = (String) options.get("default-width");
return width == null ? DEFAULT_WIDTH : Integer.parseInt(width);
}
/** Get the default window height defined by this template. */
public int getDefaultHeight() {
String height = (String) options.get("default-height");
return height == null ? DEFAULT_HEIGHT : Integer.parseInt(height);
}
/** Returns whether or not fields in this template can be edited. */
public boolean isEditable() {
String editable = (String) options.get("editable");
return editable == null ? true : editable.toLowerCase().equals("true");
}
/**
* Returns whether or not the companion-file metadata should take precedence.
*/
public boolean preferCompanion() {
String prefer = (String) options.get("prefer-companion");
return prefer == null ? true : prefer.toLowerCase().equals("true");
}
/** Returns true if we can edit this template on-the-fly. */
public boolean editTemplateFields() {
String fields = (String) options.get("edit-template-fields");
return fields == null ? false : fields.toLowerCase().equals("true");
}
/** Returns true if we can edit the OME-CA mapping on-the-fly. */
public boolean editMapping() {
String mapping = (String) options.get("edit-mapping");
return mapping == null ? false : mapping.toLowerCase().equals("true");
}
// -- Helper methods --
/** Populate the given TemplateField's name, if necessary. */
private void populateName(AggregateMetadata store, TemplateField t)
throws Exception
{
if (t.getNameMap() != null) {
t.setName(TemplateTools.getString(store, t.getNameMap(), false));
}
}
/** Populate the given TemplateField. */
private void populateField(AggregateMetadata store, TemplateField t)
throws Exception
{
setComponentValue(t, t.getComponent(),
TemplateTools.getString(store, t.getValueMap(), true));
populateName(store, t);
}
/** Save the given TemplateField. */
private void saveField(AggregateMetadata store, TemplateField t)
throws Exception
{
String map = t.getValueMap();
JComponent c = t.getComponent();
Object value = TemplateTools.getComponentValue(c);
if (map == null || map.length() == 0) return;
String method = null, params = null;
int ndx = map.indexOf("(");
if (ndx != -1) {
method = map.substring(0, ndx);
params = map.substring(ndx + 1, map.length() - 1);
}
else method = map;
int[] indices = null;
if (params != null) {
StringTokenizer s = new StringTokenizer(params, ",");
int count = s.countTokens();
indices = new int[count];
for (int i=0; i<count; i++) {
indices[i] = Integer.parseInt(s.nextToken().trim());
}
}
method = "set" + method;
Vector v = new Vector();
Method[] methods = AggregateMetadata.class.getMethods();
v.addAll(Arrays.asList(methods));
methods = AggregateMetadata.class.getDeclaredMethods();
v.addAll(Arrays.asList(methods));
Method m;
for (int i=0; i<v.size(); i++) {
m = (Method) v.get(i);
if (m.getName().equals(method)) {
Class[] paramTypes = m.getParameterTypes();
Object[] o = new Object[paramTypes.length];
// convert value to appropriate type
if (paramTypes[0].isAssignableFrom(value.getClass())) o[0] = value;
else {
Constructor ctor =
paramTypes[0].getConstructor(new Class[] {value.getClass()});
o[0] = ctor.newInstance(new Object[] {value});
}
for (int j=1; j<o.length; j++) {
if (indices == null) o[j] = new Integer(0);
else o[j] = new Integer(indices[j]);
}
m.invoke(store, o);
break;
}
}
}
/** Sets the value of the given component, based on the given string. */
private void setComponentValue(TemplateField t, JComponent component,
String v)
{
if (v == null) return;
if (component instanceof JCheckBox) {
((JCheckBox) component).setSelected(v.startsWith("t"));
}
else if (component instanceof JComboBox) {
String[] enums = t.getEnums();
for (int k=0; k<enums.length; k++) {
if (enums[k].toLowerCase().equals(v.toLowerCase())) {
((JComboBox) component).setSelectedIndex(k);
break;
}
}
}
else if (component instanceof JScrollPane) {
JScrollPane scroll = (JScrollPane) component;
JViewport view = scroll.getViewport();
((JTextArea) view.getView()).setText(v);
}
else if (component instanceof JSpinner) {
((JSpinner) component).setValue(new Integer(v));
}
}
}