/* * JBoss, Home of Professional Open Source * Copyright 2006, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. * See the copyright.txt in the distribution for a * full listing of individual contributors. * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * (C) 2005-2006, * @author JBoss Inc. */ /* * Copyright (C) 1998, 1999, 2000, 2001, * * Arjuna Solutions Limited, * Newcastle upon Tyne, * Tyne and Wear, * UK. * * $Id: RecordList.java 2342 2006-03-30 13:06:17Z $ */ package com.arjuna.ats.arjuna.coordinator; import java.io.PrintWriter; import com.arjuna.ats.arjuna.logging.tsLogger; /** * This class manages instances of the classes derived from AbstractRecord in * the form of an ordered doubly-linked list. The ordering and insertion * criteria are not particularly standard - see the comment on 'insert' for the * actual algorithm used in insertion. The algorithm assumes that one or more * different record type instances (LockRecords, RecoveryRecords, etc.) will be * inserted into the list at different times. Each such record contains specific * information managing certain properties of any particular object. As * execution progresses newly created records may need to be merged with, * replace entirely, or be added to existing records that relate to an object. * Note, the methods of this class do not need to be synchronized because * instances of this class are only used from within synchronized classes. * Applications should not use this class. * * @author Mark Little (mark@arjuna.com) * @version $Id: RecordList.java 2342 2006-03-30 13:06:17Z $ * @since JTS 1.0. */ public class RecordList { public RecordList() { listHead = null; listTail = null; noEntries = 0; } public RecordList(RecordList copy) { listHead = copy.listHead; listTail = copy.listTail; noEntries = copy.noEntries; } /** * Remove and return the element at the front of the list. * * @return the front element. */ public final AbstractRecord getFront () { AbstractRecord temp = listHead; if (noEntries == 1) { listHead = listTail = null; noEntries = 0; } else if (noEntries > 1) { listHead = listHead.getNext(); listHead.setPrevious(null); temp.setNext(null); temp.setPrevious(null); noEntries--; } return temp; } /** * Remove and return the element at the tail of the list. * * @return the last element. */ public final AbstractRecord getRear () { AbstractRecord temp = listTail; if (noEntries == 1) { listHead = listTail = null; noEntries = 0; } else if (noEntries > 1) { listTail = listTail.getPrevious(); listTail.setNext(null); temp.setPrevious(null); noEntries--; } return temp; } public AbstractRecord getNext (AbstractRecord current) { AbstractRecord rec = current.getNext(); if (remove(rec)) return rec; else return null; } /** * Insert the entry at the head of the list. */ public final boolean insert (AbstractRecord newRecord) { /* Do the insert starting at the head of the list */ return insert(newRecord, listHead); } public final void print (PrintWriter strm) { AbstractRecord arp = listHead; for (int i = 0; i < noEntries; i++) { strm.print(arp); arp = arp.getNext(); } } /** * Explicit push onto front of list. */ public final void putFront (AbstractRecord newRecord) { if (listHead == null) { listHead = listTail = newRecord; newRecord.setNext(null); newRecord.setPrevious(null); } else { listHead.setPrevious(newRecord); newRecord.setPrevious(null); newRecord.setNext(listHead); listHead = newRecord; } noEntries++; } /** * Explicit push onto rear of list. */ public final void putRear (AbstractRecord newRecord) { if (listTail == null) { listHead = listTail = newRecord; newRecord.setNext(null); newRecord.setPrevious(null); } else { listTail.setNext(newRecord); newRecord.setPrevious(listTail); newRecord.setNext(null); listTail = newRecord; } noEntries++; } public final AbstractRecord peekFront () { return listHead; } public final AbstractRecord peekRear () { return listTail; } public final AbstractRecord peekNext (AbstractRecord curr) { return curr.getNext(); } /* * Assume it's in this list! */ public final boolean remove (AbstractRecord oldRecord) { if (oldRecord == null) return false; if (noEntries == 1) { listHead = listTail = null; noEntries = 0; } else if (noEntries > 1) { if (listHead == oldRecord) { listHead = listHead.getNext(); if (listHead != null) listHead.setPrevious(null); oldRecord.setNext(null); oldRecord.setPrevious(null); } else { if (listTail == oldRecord) { listTail = listTail.getPrevious(); if (listTail != null) listTail.setNext(null); oldRecord.setNext(null); oldRecord.setPrevious(null); } else { if (oldRecord.getPrevious() != null) oldRecord.getPrevious().setNext(oldRecord.getNext()); if (oldRecord.getNext() != null) oldRecord.getNext() .setPrevious(oldRecord.getPrevious()); } } noEntries--; } return true; } /** * @return the number of items in the current list. */ public final int size () { return noEntries; } public String toString () { AbstractRecord rec = listHead; String s = "RecordList:"; if (rec == null) s += " empty"; else { while (rec != null) { s += " " + rec.order(); rec = rec.getNext(); } } return s; } /** * This is a variation on ordered insertion. Insertion obeys the following * algorithm. Starting at the record indicated by 'startat' examine each * entry in the list in turn and perform the following code 1) If the new * record should be merged with the old, call nr.merge passing the old * record as an argument and then INSERT the new record IN PLACE OF the old * and exit 2) If the new record should replace the old then INSERT the new * record IN PLACE OF the old and exit 3) If the new record should be added * in addition to the old then INSERT the new record BEFORE the old and exit * 4) If the two records are the same (determined by the == operator) simply * exit 5) Otherwise determine if the new record should be added here * regardless due to the ordering constraints on the list and if so add and * exit, otherwise step on to the next element and repeat all the steps. * Steps 1-4 effectively ensure that information maintained in any two * records for the same object is current either by merging in new * information, replacing the old with new, adding in new, or leaving the * old alone. Step 5 ensures that if no existing record exists insertion * takes place at the correct point * * @return <code>true</code> if insertion/replacement took place, * <code>false</code> otherwise. */ private final boolean insert (AbstractRecord newRecord, AbstractRecord startAt) { AbstractRecord current = startAt; /* * Step through the existing list one record at a time */ while (current != null) { if (newRecord.shouldMerge(current)) { if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("RecordList::insert("+this+") : merging "+newRecord.type()+ " and "+current.type()+" for "+newRecord.order()); } newRecord.merge(current); replace(newRecord, current); return true; } else { if (newRecord.shouldReplace(current)) { if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("RecordList::insert("+this+") : replacing "+current.type()+ " and "+newRecord.type()+" for "+newRecord.order()); } replace(newRecord, current); return true; } else { if (newRecord.shouldAdd(current)) { if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("RecordList::insert("+this+") : adding extra record of type "+ newRecord.type()+" before "+current.type()+" for "+newRecord.order()); } insertBefore(newRecord, current); return true; } else { if (newRecord.shouldAlter(current)) newRecord.alter(current); if (newRecord.equals(current)) { return false; } else if (newRecord.lessThan(current)) { if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("RecordList::insert("+this+") : inserting "+ newRecord.type()+" for "+newRecord.order()+" before "+current.type()); } insertBefore(newRecord, current); return true; } current = current.getNext(); } } } } if (current == null) { if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("RecordList::insert("+this+") : appending "+newRecord.type()+" for "+newRecord.order()); } putRear(newRecord); } else { if (tsLogger.logger.isTraceEnabled()) { tsLogger.logger.trace("RecordList::insert("+this+") : inserting "+newRecord.type()+" for "+newRecord.order()+ " before "+current.type()+" for "+current.order()); } insertBefore(newRecord, current); } return true; } /** * Insert the first parameter before the second in the list. */ private final void insertBefore (AbstractRecord newRecord, AbstractRecord before) { /* first do the main link chaining */ newRecord.setPrevious(before.getPrevious()); newRecord.setNext(before); before.setPrevious(newRecord); /* determine if insert was at list head */ if (newRecord.getPrevious() != null) (newRecord.getPrevious()).setNext(newRecord); else /* must be pointing to the head of the list */ listHead = newRecord; noEntries++; } private final void replace (AbstractRecord newRecord, AbstractRecord oldRecord) { newRecord.setPrevious(oldRecord.getPrevious()); newRecord.setNext(oldRecord.getNext()); if (newRecord.getPrevious() != null) (newRecord.getPrevious()).setNext(newRecord); else listHead = newRecord; if (newRecord.getNext() != null) (newRecord.getNext()).setPrevious(newRecord); else listTail = newRecord; oldRecord = null; } protected AbstractRecord listHead; private AbstractRecord listTail; private int noEntries; }