/* Copyright (C) 2003-2011 JabRef contributors. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package net.sf.jabref; import java.io.*; import java.util.*; import net.sf.jabref.groups.GroupTreeNode; import net.sf.jabref.groups.VersionHandling; import net.sf.jabref.labelPattern.LabelPattern; import net.sf.jabref.labelPattern.LabelPatternUtil; import net.sf.jabref.sql.DBStrings; public class MetaData implements Iterable<String> { private static final String PREFIX_KEYPATTERN = "keypattern_"; private static final String KEYPATTERNDEFAULT = "keypatterndefault"; private HashMap<String, Vector<String>> metaData = new HashMap<String, Vector<String>>(); private StringReader data; private GroupTreeNode groupsRoot = null; private File file = null; // The File where this base gets saved. private boolean groupTreeValid = true; private LabelPattern labelPattern = null; private DBStrings dbStrings = new DBStrings(); /** * The MetaData object stores all meta data sets in Vectors. To ensure that * the data is written correctly to string, the user of a meta data Vector * must simply make sure the appropriate changes are reflected in the Vector * it has been passed. */ public MetaData(HashMap<String, String> inData, BibtexDatabase db) { boolean groupsTreePresent = false; Vector<String> flatGroupsData = null; Vector<String> treeGroupsData = null; // The first version (0) lacked a version specification, // thus this value defaults to 0. int groupsVersionOnDisk = 0; if (inData != null) for (String key : inData.keySet()){ data = new StringReader(inData.get(key)); String unit; Vector<String> orderedData = new Vector<String>(); // We must allow for ; and \ in escape sequences. try { while ((unit = getNextUnit(data)) != null) { orderedData.add(unit); } } catch (IOException ex) { System.err.println("Weird error while parsing meta data."); } if (key.equals("groupsversion")) { if (orderedData.size() >= 1) groupsVersionOnDisk = Integer.parseInt(orderedData.firstElement().toString()); } else if (key.equals("groupstree")) { groupsTreePresent = true; treeGroupsData = orderedData; // save for later user // actual import operation is handled later because "groupsversion" // tag might not yet have been read } else if (key.equals("groups")) { flatGroupsData = orderedData; } else { putData(key, orderedData); } } // this possibly handles import of a previous groups version if (groupsTreePresent) putGroups(treeGroupsData, db, groupsVersionOnDisk); if (!groupsTreePresent && flatGroupsData != null) { try { groupsRoot = VersionHandling.importFlatGroups(flatGroupsData); groupTreeValid = true; } catch (IllegalArgumentException ex) { groupTreeValid = true; } } } /** * The MetaData object can be constructed with no data in it. */ public MetaData() { } /** * Add default metadata for new database: */ public void initializeNewDatabase() { metaData.put(Globals.SELECTOR_META_PREFIX + "keywords", new Vector<String>()); metaData.put(Globals.SELECTOR_META_PREFIX + "author", new Vector<String>()); metaData.put(Globals.SELECTOR_META_PREFIX + "journal", new Vector<String>()); metaData.put(Globals.SELECTOR_META_PREFIX + "publisher", new Vector<String>()); metaData.put(Globals.SELECTOR_META_PREFIX + "review", new Vector<String>()); } /** * @return Iterator on all keys stored in the metadata */ public Iterator<String> iterator() { return metaData.keySet().iterator(); } /** * Retrieves the stored meta data. * * @param key the key to look up * @return null if no data is found */ public Vector<String> getData(String key) { return metaData.get(key); } /** * Removes the given key from metadata. * Nothing is done if key is not found. * * @param key the key to remove */ public void remove(String key) { metaData.remove(key); } /** * Stores the specified data in this object, using the specified key. For * certain keys (e.g. "groupstree"), the objects in orderedData are * reconstructed from their textual (String) representation if they are of * type String, and stored as an actual instance. */ public void putData(String key, Vector<String> orderedData) { metaData.put(key, orderedData); } /** * Look up the directory set up for the given field type for this database. * If no directory is set up, return that defined in global preferences. * @param fieldName The field type * @return The default directory for this field type. */ public String[] getFileDirectory(String fieldName) { // There can be up to three directory definitions for these files - the database's // metadata can specify a general directory and/or a user-specific directory, or // the preferences can specify one. The settings are prioritized in the following // order and the first defined setting is used: metadata user-specific directory, // metadata general directory, preferences directory. String key = Globals.prefs.get("userFileDirIndividual"); List<String> dirs = new ArrayList<String>(); Vector<String> vec = getData(key); if (vec == null) { key = Globals.prefs.get("userFileDir"); vec = getData(key); } if ((vec != null) && (vec.size() > 0)) { String dir; dir = vec.get(0); // If this directory is relative, we try to interpret it as relative to // the file path of this bib file: if (!(new File(dir)).isAbsolute() && (file != null)) { String relDir; if (dir.equals(".")) { // if dir is only "current" directory, just use its parent (== real current directory) as path relDir = file.getParent().toString(); } else { relDir = new StringBuffer(file.getParent()). append(System.getProperty("file.separator")). append(dir).toString(); } // If this directory actually exists, it is very likely that the // user wants us to use it: if ((new File(relDir)).exists()) dir = relDir; } dirs.add(dir); } else { String dir = Globals.prefs.get(fieldName + "Directory"); if (dir != null) dirs.add(dir); } // Check if the bib file location should be included, and if so, if it is set: if (Globals.prefs.getBoolean("bibLocationAsFileDir") && getFile() != null) { // Check if we should add it as primary file dir (first in the list) or not: if (Globals.prefs.getBoolean("bibLocAsPrimaryDir")) dirs.add(0, getFile().getParent()); else dirs.add(getFile().getParent()); } return dirs.toArray(new String[dirs.size()]); } /** * Parse the groups metadata string * @param orderedData The vector of metadata strings * @param db The BibtexDatabase this metadata belongs to * @param version The group tree version * @return true if parsing was successful, false otherwise */ private void putGroups(Vector<String> orderedData, BibtexDatabase db, int version) { try { groupsRoot = VersionHandling.importGroups(orderedData, db, version); groupTreeValid = true; } catch (Exception e) { // we cannot really do anything about this here System.err.println(e); groupTreeValid = false; } } public GroupTreeNode getGroups() { return groupsRoot; } /** * Sets a new group root node. <b>WARNING </b>: This invalidates everything * returned by getGroups() so far!!! */ public void setGroups(GroupTreeNode root) { groupsRoot = root; groupTreeValid = true; } /** * Writes all data to the specified writer, using each object's toString() * method. */ public void writeMetaData(Writer out) throws IOException { // write all meta data except groups for (Iterator<String> i = metaData.keySet().iterator(); i.hasNext();) { String key = i.next(); StringBuffer sb = new StringBuffer(); Vector<String> orderedData = metaData.get(key); if (orderedData.size() >= 0) { sb.append("@comment{").append(GUIGlobals.META_FLAG).append(key).append(":"); for (int j = 0; j < orderedData.size(); j++) { sb.append(Util.quote(orderedData.elementAt(j), ";", '\\')).append(";"); } sb.append("}"); } wrapStringBuffer(sb, Globals.METADATA_LINE_LENGTH); sb.append(Globals.NEWLINE); sb.append(Globals.NEWLINE); out.write(sb.toString()); } // write groups if present. skip this if only the root node exists // (which is always the AllEntriesGroup). if (groupsRoot != null && groupsRoot.getChildCount() > 0) { StringBuffer sb = new StringBuffer(); // write version first sb.append("@comment{").append(GUIGlobals.META_FLAG).append("groupsversion:"); sb.append(""+VersionHandling.CURRENT_VERSION+";"); sb.append("}"); sb.append(Globals.NEWLINE); sb.append(Globals.NEWLINE); out.write(sb.toString()); // now write actual groups sb = new StringBuffer(); sb.append("@comment{").append(GUIGlobals.META_FLAG).append("groupstree:"); sb.append(Globals.NEWLINE); // GroupsTreeNode.toString() uses "\n" for separation StringTokenizer tok = new StringTokenizer(groupsRoot.getTreeAsString(),Globals.NEWLINE); while (tok.hasMoreTokens()) { StringBuffer s = new StringBuffer(Util.quote(tok.nextToken(), ";", '\\') + ";"); wrapStringBuffer(s, Globals.METADATA_LINE_LENGTH); sb.append(s); sb.append(Globals.NEWLINE); } sb.append("}"); sb.append(Globals.NEWLINE); sb.append(Globals.NEWLINE); out.write(sb.toString()); } } private void wrapStringBuffer(StringBuffer sb, int lineLength) { for (int i=lineLength; i<sb.length(); i+=lineLength+Globals.NEWLINE_LENGTH) { sb.insert(i, Globals.NEWLINE); } } /** * Reads the next unit. Units are delimited by ';'. */ private String getNextUnit(Reader reader) throws IOException { int c; boolean escape = false; StringBuffer res = new StringBuffer(); while ((c = reader.read()) != -1) { if (escape) { res.append((char)c); escape = false; } else if (c == '\\') { escape = true; } else if (c == ';') { break; } else { res.append((char)c); } } if (res.length() > 0) return res.toString(); return null; } public File getFile() { return file; } public void setFile(File file) { this.file = file; } public DBStrings getDBStrings() { return dbStrings; } public void setDBStrings(DBStrings dbStrings) { this.dbStrings = dbStrings; } public boolean isGroupTreeValid() { return groupTreeValid; } /** * @return the stored label patterns */ public LabelPattern getLabelPattern() { if (labelPattern != null) { return labelPattern; } labelPattern = new LabelPattern(); // the parent label pattern of a BibTeX data base is the global pattern stored in the preferences labelPattern.setParent(Globals.prefs.getKeyPattern()); Iterator<String> iterator = iterator(); while (iterator.hasNext()) { String key = iterator.next(); if (key.startsWith(PREFIX_KEYPATTERN)) { Vector<String> value = getData(key); String type = key.substring(PREFIX_KEYPATTERN.length()); labelPattern.addLabelPattern(type, value.get(0)); } } Vector<String> defaultPattern = getData(KEYPATTERNDEFAULT); if (defaultPattern != null) { labelPattern.setDefaultValue(defaultPattern.get(0)); } return labelPattern; } /** * Updates the stored key patterns to the given key patterns. * * @param labelPattern the key patterns to update to. <br /> * A reference to this object is stored internally and is returned at getLabelPattern(); */ public void setLabelPattern(LabelPattern labelPattern) { // remove all keypatterns from metadata Iterator<String> iterator = this.iterator(); while (iterator.hasNext()) { String key = iterator.next(); if (key.startsWith(PREFIX_KEYPATTERN)) { iterator.remove(); } } // set new value if it is not a default value for (String key : labelPattern.keySet()) { String metaDataKey = PREFIX_KEYPATTERN + key; ArrayList<String> value = labelPattern.get(key); if (value != null) { Vector<String> data = new Vector<String>(); data.add(value.get(0)); this.putData(metaDataKey, data); } } // store default pattern if (labelPattern.getDefaultValue() == null) { this.remove(KEYPATTERNDEFAULT); } else { Vector<String> data = new Vector<String>(); data.add(labelPattern.getDefaultValue().get(0)); this.putData(KEYPATTERNDEFAULT, data); } this.labelPattern = labelPattern; } }