/* * $Id$ * * Copyright (c) 2006 by the TeXlipse team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package net.sourceforge.texlipse.bibparser; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import net.sourceforge.texlipse.model.ReferenceEntry; /** * A container for BibTeX outlines. Can be sorted in different ways. * * @author Oskar Ojala */ public class BibOutlineContainer { private String startName; private String endName; private String sorting; // TODO ENUMs here private List childContainers; // BibOutlineContainer private List childEntries; // ReferenceEntry private boolean topLevel; // TODO enums public static final String SORTNATURAL = "natural"; public static final String SORTYEAR = "year"; public static final String SORTAUTHOR = "author"; public static final String SORTJOURNAL = "journal"; public static final String SORTINDEX = "index"; private static int MAX_PARTITIONSIZE = 15; /** * Creates a new container * * @param entries The initial entries * @param topLevel Whether or not this represents the top level of the hierarchy */ public BibOutlineContainer(List entries, boolean topLevel) { this.childEntries = entries; this.topLevel = topLevel; this.sorting = SORTNATURAL; } /** * Creates a new container * * @param entries The initial entries * @param sName Name of the first entry * @param eName Name of the last entry */ private BibOutlineContainer(List entries, String sName, String eName) { this.childEntries = entries; this.startName = sName; this.endName = eName; this.topLevel = false; } /** * Makes a copy of this container so that first level children are copied * as new objects but lower levels are not * * @param sorting The type of sorting that the new container will have * @return A copy of this container */ private BibOutlineContainer childCopy(String sorting) { BibOutlineContainer newboc = new BibOutlineContainer(new ArrayList(), topLevel); newboc.sorting = sorting; for (Iterator iter = childEntries.iterator(); iter.hasNext();) { ReferenceEntry re = (ReferenceEntry) iter.next(); newboc.childEntries.add(re.copy()); } return newboc; } /** * Builds a container sorted by authors * * @return New container sorted by authors */ public BibOutlineContainer buildAuthorSort() { // Make a shallow copy BibOutlineContainer newboc = childCopy(SORTAUTHOR); newboc.authorSort(); // Replace the keys with the names of the authors for (Iterator iter = newboc.childEntries.iterator(); iter.hasNext();) { ReferenceEntry re = (ReferenceEntry) iter.next(); re.key = re.author + "; " + re.key; } // Build a tree structure newboc.partition(); return newboc; } //private static final Pattern rmBraces = Pattern.compile("(^|[^\\\\])(:?\\{|\\})"); /** * Does an author sort on this container */ private void authorSort() { // duplicate entries with several authors ArrayList copyChildren = new ArrayList(); for (Iterator iter = childEntries.iterator(); iter.hasNext();) { ReferenceEntry re = (ReferenceEntry) iter.next(); re.author = re.author.replaceAll("\\\\(.)", "$1"); String[] authors = re.author.split(" and "); // TODO fails e.g. on "Foo {Bar (Tutor)}", need to check braces // formats the author so that the last name is first for (int i = 0; i < authors.length; i++) { if (authors[i].indexOf(',') == -1 && !(authors[i].startsWith("{") && authors[i].endsWith("}"))) { int pos = authors[i].lastIndexOf(' '); if (pos != -1) { authors[i] = authors[i].substring(pos+1) + ", " + authors[i].substring(0, pos); } } // Remove braces authors[i] = authors[i].replaceAll("(^|[^\\\\])(:?\\{|\\})", "$1"); } // We make some simple copies of this entry and because they will // only be in the outline, we won't copy every value re.author = authors[0]; for (int i = 1; i < authors.length; i++) { ReferenceEntry copyRe = new ReferenceEntry(re.key); copyRe.position = re.position; copyRe.refFile = re.refFile; copyRe.author = authors[i]; copyChildren.add(copyRe); } } // merge the lists childEntries.addAll(copyChildren); // sort // Collections.sort(this.childEntries, new Comparator<ReferenceEntry>() { // public int compare(ReferenceEntry A, ReferenceEntry B) { // return A.author.compareTo(B.author); // } // }); Collections.sort(this.childEntries, new Comparator() { public int compare(Object A, Object B) { return ((ReferenceEntry) A).author.compareTo(((ReferenceEntry) B).author); } }); } /** * Builds a container sorted by year * * @return New container sorted by year */ public BibOutlineContainer buildYearSort() { BibOutlineContainer newboc = childCopy(SORTYEAR); // Collections.sort(newboc.childEntries, new Comparator<ReferenceEntry>() { // public int compare(ReferenceEntry A, ReferenceEntry B) { // return A.year.compareTo(B.year); // } // }); Collections.sort(newboc.childEntries, new Comparator() { public int compare(Object A, Object B) { return ((ReferenceEntry) A).year.compareTo(((ReferenceEntry) B).year); } }); for (Iterator iter = newboc.childEntries.iterator(); iter.hasNext();) { ReferenceEntry re = (ReferenceEntry) iter.next(); re.key = re.year + "; " + re.key; } newboc.partition(); return newboc; } /** * Builds a container sorted by journal * * @return New container sorted by journal */ public BibOutlineContainer buildJournalSort() { BibOutlineContainer newboc = childCopy(SORTJOURNAL); // Collections.sort(newboc.childEntries, new Comparator<ReferenceEntry>() { // public int compare(ReferenceEntry A, ReferenceEntry B) { // return A.journal.compareTo(B.journal); // } // }); Collections.sort(newboc.childEntries, new Comparator() { public int compare(Object A, Object B) { return ((ReferenceEntry) A).journal.compareTo(((ReferenceEntry) B).journal); } }); for (Iterator iter = newboc.childEntries.iterator(); iter.hasNext();) { ReferenceEntry re = (ReferenceEntry) iter.next(); re.key = re.journal + "; " + re.key; } newboc.partition(); return newboc; } /** * Builds a container sorted by index * * @return New container sorted by index */ public BibOutlineContainer buildIndexSort() { BibOutlineContainer newboc = childCopy(SORTINDEX); // Collections.sort(newboc.childEntries, new Comparator<ReferenceEntry>() { // public int compare(ReferenceEntry A, ReferenceEntry B) { // return A.key.compareTo(B.key); // } // }); Collections.sort(newboc.childEntries, new Comparator() { public int compare(Object A, Object B) { return ((ReferenceEntry) A).key.compareTo(((ReferenceEntry) B).key); } }); newboc.partition(); return newboc; } /** * Calculate the shortest differentiating prefix between * the strings that is at least 4 characters. * * @param s1 First string * @param s2 Second string * @return Differentiating prefix */ private String differentiatingPrefix(String s1, String s2) { int i = 0; int shorter = Math.min(s1.length(), s2.length()); for (; i < shorter; i++) { if (s1.charAt(i) != s2.charAt(i)) { int l = Math.max(4, i+1); return s1.substring(0, l); } } // the other one was shorter if (s1.length() == i) { return s1; } else { return s1.substring(0, i + 1); } } /** * Partitions this container */ public void partition() { if (childEntries.size() < MAX_PARTITIONSIZE) { return; } //childContainers = new ArrayList(); // TODO polish // calculate hierarchy levels and partitions int totalPartitions = (int) Math.ceil((double) childEntries.size() / (double) MAX_PARTITIONSIZE); ArrayList bottomContainers = new ArrayList(); ReferenceEntry[] childArray = new ReferenceEntry[childEntries.size()]; childEntries.toArray(childArray); // total levels // int levels = 1; // for (int entries = MAX_PARTITIONSIZE; entries < totalPartitions; entries *= MAX_PARTITIONSIZE) { // levels++; // } String prevName = ((ReferenceEntry) childEntries.get(0)).key; String nextName = "..."; for (int i = 0; i < totalPartitions; i++) { //int partitionEnd = Math.min(childEntries.size(), MAX_PARTITIONSIZE); int partitionSize = Math.min(childEntries.size() - MAX_PARTITIONSIZE * i, MAX_PARTITIONSIZE); ReferenceEntry[] newChildren = new ReferenceEntry[partitionSize]; System.arraycopy(childArray, MAX_PARTITIONSIZE * i, newChildren, 0, partitionSize); // String pre1 = differentiatingPrefix(((ReferenceEntry) children.get(0)).key, prevName); // prevName = ((ReferenceEntry) children.get(children.size()-1)).key; // nextName = childEntries.size() > 0 ? ((ReferenceEntry) childEntries.get(0)).key : "..."; // String pre2 = differentiatingPrefix(prevName, nextName); String pre1 = differentiatingPrefix(newChildren[0].key, prevName); prevName = newChildren[newChildren.length-1].key; nextName = childEntries.size() > 0 ? childArray[0].key : "..."; String pre2 = differentiatingPrefix(prevName, nextName); //BibOutlineContainer boc = new BibOutlineContainer(children, pre1 + "..." + pre2); BibOutlineContainer boc = new BibOutlineContainer(Arrays.asList(newChildren), pre1, pre2); bottomContainers.add(boc); } // subdivide while (bottomContainers.size() > MAX_PARTITIONSIZE) { ArrayList midContainers = new ArrayList(); while (bottomContainers.size() > 0) { int partitionEnd = Math.min(bottomContainers.size(), MAX_PARTITIONSIZE); // probably doesn't work... //List children = childEntries.subList(0, partitionEnd); //childEntries.removeRange(0, partitionEnd); // if it doesn't work... ArrayList children = new ArrayList(); ListIterator liter = bottomContainers.listIterator(); for (int j = 0; j < partitionEnd; j++) { children.add(liter.next()); liter.remove(); } String sName = ((BibOutlineContainer) children.get(0)).startName; String eName = ((BibOutlineContainer) children.get(0)).endName; BibOutlineContainer boc = new BibOutlineContainer(children, sName, eName); midContainers.add(boc); } bottomContainers = midContainers; } childContainers = bottomContainers; childEntries = null; } /* (non-Javadoc) * @see java.lang.Object#toString() */ public String toString() { return startName + "..." + endName; } /** * @return Returns the childContainers. */ public List getChildContainers() { return childContainers; } /** * @return Returns the childEntries. */ public List getChildEntries() { return childEntries; } /** * @return Returns the sorting. */ public String getSorting() { return sorting; } }