/* * SetOps.java 2005-05-02 * * Dirk Ansorge, FernUniversitaet Hagen * */ package twodsack.operation.setoperation; import twodsack.io.*; import twodsack.set.*; import twodsack.setelement.*; import twodsack.setelement.datatype.*; import twodsack.setelement.datatype.basicdatatype.*; import twodsack.util.collection.*; import twodsack.util.collectiontype.*; import twodsack.util.comparator.*; import twodsack.util.graph.*; import twodsack.util.iterator.*; import twodsack.util.number.*; import java.util.*; import java.lang.reflect.*; import java.io.*; /** * The SetOps class is one of the central classes of the whole 2D-SACK package. * Collected in this class are all * of the genereric set operations the package offers. Sets in 2D-SACK are commonly {@link ElemMultiSet}, i.e. they * are one of {{@link PointMultiSet}, {@link SegMultiSet}, {@link TriMultiSet}}. Most of the operations provided here are made for this * type. Other types are {@link PairMultiSet}, {@link LeftJoinPairMultiSet} and, in one special case a LinkedList.

* A PairMultiSet is the result type of a join performed on two sets. Each element of such a set is an {@link ElemPair} * which holds two elements. A LeftJoinPairMultiSet is the result of an {@link #leftOuterJoin(ElemMultiSet,ElemMultiSet,Method)} and also has pairs * as elements. Such a pair looks like this: (Element x ElemMultiSet). Finally, a LinkedList type * only appears in some sorting algorithm in this class.

* The operations in this class can be roughly divided in four groups:

    *
  1. set operations for ElemMultiSet(s) *
  2. set operations for PairMultiSet(s) *
  3. set operations for LeftJoinPairMultiSet(s) *
  4. supporting operations *

* E.g. the group for set operations for PairMultiSet(s) holds several join operations to construct PairMultiSets * from pairs of ElemMultiSet(s). Then, some other operations can be used for further processing, like {@link #filter(PairMultiSet,Method,boolean)}, * {@link #proj1(PairMultiSet)}, {@link #proj2(PairMultiSet)}, and four different versions of map(...). The operations in the other * groups are quite similar.

* In this class, different implementations can be found for similar operations like join(...) and overlapJoin(...) or * group(...) * and overlapGroup. The overlap prefix indicates a method as using a special mechanism for speeding up * the operation. Whereas a simple join takes quite a long time, the overlapJoin uses a filter&refine technique * to reduce the number of possibly interesting pairs of elements before executing (possibly) expensive following * operations. The only constraint for these overlap versions of methods is, that the passed predicate and operation * for this operation may work only inside of bounding boxes. This means for predicates, that it only holds for * two elements, if their bounding boxes overlap or are adjacent at least. For operations, the result of the operation must * lie inside of the bounding box of the two bounding boxes involved. */ public class SetOps { /* * fields */ static final ElemComparator ELEM_COMPARATOR = new ElemComparator(); static final LeftJoinPairComparator LEFTJOINPAIR_COMPARATOR = new LeftJoinPairComparator(); static final ElemPairComparator ELEMPAIR_COMPARATOR = new ElemPairComparator(); /* * constructors */ /** * The standard constructor. */ public SetOps(){} /* * methods */ /** * Separates the passed PairMultiSet and returns the elements in the two passed ElemMultiSet(s). * Both passed sets ems1,ems2 are cleared before adding new elements. * * @param pms the set that is separated * @param ems1 all first elements are stored in this set * @param ems2 all second elements are stored in this set */ static public void separateSets (PairMultiSet pms, ElemMultiSet ems1, ElemMultiSet ems2) { if (ems1 == null) ems1 = new ElemMultiSet(ELEM_COMPARATOR); if (ems2 == null) ems2 = new ElemMultiSet(ELEM_COMPARATOR); ems1.clear(); ems2.clear(); Iterator it = pms.iterator(); ElemPair actPair; while (it.hasNext()) { actPair = (ElemPair)((MultiSetEntry)it.next()).value; ems1.add(actPair.first); ems2.add(actPair.second); }//while it }//end method separateSets /** * Returns a set of elements which is 'reduced' using the passed predicate and method.

* This is another variant of the normal {@link #reduce(ElemMultiSet,Method,Method)} operation. For the functionality of reduce itself, * have a look at that method.

* Here, a plane sweep algorithm is used for the computation of the reduced set:

* This method is successfully used in the SupportOps.minimal() method. There, it works for sets of segments, * the predicate adjacent() and the method concat(). No other implementations were tested, yet.

* Note, that the signature of all predicates and methods must be
* Element x Element -> boolean and Element x Element -> Element, resp. * * @param ems the input set of elements * @param predicate the method that is used to identify candidates for the second method * @param method the method that is invoked on pairs identified by the predicate * @param meet if true, also objects of ems which have only adjacent bounding boxes are tested with predicate * @return the 'reduced' set of objects */ public static ElemMultiSet overlapReduceSweep (ElemMultiSet ems, Method predicate, Method method, boolean meet) { //construct sweep event structure from ems //the left and right intervals of the bounding boxes of all elements in ems are stored if (ems.isEmpty()) return ems; //get some information about the passed methods int paramTypeCountP = Array.getLength(predicate.getParameterTypes()); int paramTypeCountM = Array.getLength(method.getParameterTypes()); Element[] paramListP = new Element[paramTypeCountP]; Element[] paramListM = new Element[paramTypeCountM]; //the elements of ems are stored in an array for better accessibility Object[] elemStore = ems.toArray(); //declare the sweep event structure (ses) and fill it with the left and right borders of the //bounding boxes of all elements in ems. MultiSet ses = new MultiSet(new IvlComparator(meet)); Object[] sesArr; Element actEl; Rect actRect; boolean buddy; for (int i = 0; i < elemStore.length; i++) { actEl = (Element)elemStore[i]; actRect = actEl.rect(); buddy = actRect.ulx.equal(actRect.urx); ses.add(new Interval(actRect.lly,actRect.uly,"blueleft",actRect.ulx,null,i,buddy)); ses.add(new Interval(actRect.lry,actRect.ury,"blueright",actRect.urx,null,i,buddy)); }//for i //declare the sweep status structure MultiSet sss = new MultiSet(new IvlComparator(meet)); Interval actIvl,sesIvl; Interval sssIvl = null; Iterator it,it2,it3; boolean predTrue,resultIsEmpty,removedIvl,ivlOverlap = false; ElemMultiSet resultSet = new ElemMultiSet(ELEM_COMPARATOR); Element resultElement = null; sesArr = ses.toArray(); //start the sweep for (int sesIdx = 0; sesIdx < sesArr.length; sesIdx++) { //while there are elements left in ses, continue with the sweep while (sesArr[sesIdx] == null) { sesIdx++; }//while actIvl = (Interval)sesArr[sesIdx]; //the actually visited interval can be marked with blueleft or blueright //the first case handled is blueleft if (actIvl.mark == "blueleft") { resultIsEmpty = false; it2 = sss.iterator(); //now, traverse the intervals already stored in sss. while (!resultIsEmpty && it2.hasNext()) { sssIvl = (Interval)(((MultiSetEntry)it2.next()).value); predTrue = false; if (actIvl.right.less(sssIvl.left)) { //break; } else { //define ivlOverlap = true, if actIvl and sssIvl overlap //in case flag meet is true, ivlOverlap = true if the intervals are adjacent, too if (!meet) ivlOverlap = (sssIvl.left.less(actIvl.left) && sssIvl.right.greaterOrEqual(actIvl.left)) || (sssIvl.left.equal(actIvl.left) && sssIvl.right.greater(actIvl.left)) || (sssIvl.left.greater(actIvl.left) && sssIvl.left.less(actIvl.right)); else ivlOverlap = (sssIvl.left.less(actIvl.left) && sssIvl.right.greaterOrEqual(actIvl.left)) || (sssIvl.left.equal(actIvl.left) && sssIvl.right.greater(actIvl.left)) || (sssIvl.left.greater(actIvl.left) && sssIvl.left.less(actIvl.right)) || sssIvl.right.equal(actIvl.left) || sssIvl.left.equal(actIvl.right);; }//else //if both intervals overlap check the predicate if (ivlOverlap) { //now check for predicate //if predicate has one argument if (paramTypeCountP == 1) paramListP[0] = (Element)elemStore[actIvl.number]; //if predicate has two arguments else { paramListP[0] = (Element)elemStore[sssIvl.number]; paramListP[1] = (Element)elemStore[actIvl.number]; }//else //evaluate predicate try { predTrue = ((Boolean)predicate.invoke((Element)elemStore[sssIvl.number],paramListP)).booleanValue(); } catch (Exception e) { System.out.println("Exception in SetOps.overlapReduceSweep when evaluating predicate."); System.out.println("elemStore[sssIvl.number]: "+(Element)elemStore[sssIvl.number]); System.out.println("elemStore[actIvl.number]: "+(Element)elemStore[actIvl.number]); e.printStackTrace(); throw new RuntimeException("An error occurred in the ROSEAlgebra."); }//catch //invoke method on the elements linked to the intervals, if the predicate yields true if (predTrue) { //evaluate method, note that result may be empty if (paramTypeCountM == 1) paramListM[0] = (Element)elemStore[actIvl.number]; else { paramListM[0] = (Element)elemStore[sssIvl.number]; paramListM[1] = (Element)elemStore[actIvl.number]; }//else //evaluate method try { resultElement = (Element)method.invoke((Element)elemStore[sssIvl.number],paramListM); } catch (Exception e) { System.out.println("Exception in SetOps.overlapReduceSweep when evaluating method."); System.out.println("elemStore[sssIvl.number]: "+(Element)elemStore[sssIvl.number]); System.out.println("elemStore[actIvl.number]: "+(Element)elemStore[actIvl.number]); e.printStackTrace(); throw new RuntimeException("An error occurred in the ROSEAlgebra."); }//catch if (resultElement == null) resultIsEmpty = true; //update ref in actEl, update interval borders in actEl elemStore[actIvl.number] = resultElement; actIvl.left = resultElement.rect().lly; actIvl.right = resultElement.rect().uly; //remove the sssIvl from sss and remove right interval with number = sssIvl.number from ses, //i.e. remove the interval also from ses, where its "blueright" interval still exists it2.remove(); removedIvl = false; for (int i = sesIdx; i < sesArr.length; i++) { if ((sesArr[i] != null) &&((Interval)sesArr[i]).number == sssIvl.number) { sesArr[i] = null; removedIvl = true; break; }//if }//for i }//if predTrue }//if ivlOverlap }//while it2 //if the result of the method invocation is not empty, add the interval of the new/changed //object to the sss //otherwise delete the right partner of the left interval from sss if (!resultIsEmpty) sss.add(actIvl); else { //remove the right interval from ses removedIvl = false; for (int i = sesIdx; i < sesArr.length; i++) { if ((sesArr[i] != null) && ((Interval)sesArr[i]).number == sssIvl.number) { sesArr[i] = null; removedIvl = true; break; }//if }//for i }//else }//if blueleft //the actually visited interval is marked with blueright else { //store the object linked to the interval in the result set resultSet.add(elemStore[actIvl.number]); //remove left interval from sss it2 = sss.iterator(); while (it2.hasNext()) { sesIvl = (Interval)(((MultiSetEntry)it2.next()).value); if (sesIvl.number == actIvl.number) { it2.remove(); break; }//if }//while it2 }//else if blueright }//while it return resultSet; }//end method overlapReduceSweep /** * This method is a variant of overlapReduceSweep for methods of type Element x Element -> ElemMultiSet. * More information about this method can be found there. * * @param ems the input set of elements * @param predicate the method that is used to identify candidates for the second method * @param method the method that is invoked on pairs identified by the predicate * @param meet if true, also objects of ems which have only adjacent bounding boxes are tested with predicate * @return the 'reduced' set of objects */ public static ElemMultiSet overlapReduceSweep2 (ElemMultiSet ems, Method predicate, Method method, boolean meet) { //construct sweep event structure (ses) from ems; //the left an right intervals of the bounding boxes of all elements in ems are stored in ses if (ems.isEmpty()) return ems; //get some information about the passed methods int paramTypeCountP = Array.getLength(predicate.getParameterTypes()); int paramTypeCountM = Array.getLength(method.getParameterTypes()); Element[] paramListP = new Element[paramTypeCountP]; Element[] paramListM = new Element[paramTypeCountM]; //the elements of ems are stored in a Vector we need a dynamic structure here in contrast the //ordinary overlapReduceSweep, since the SES is changed during the sweep Vector elemStore = ems.toVector(); int number = ems.size(); //declare the sweep event structure (SES) and fill it with the left and right borders of the //bounding boxes of all elements of ems IvlComparator ivlComp = new IvlComparator(meet); MultiSet ses = new MultiSet(ivlComp); ProLinkedList sesList; Element actEl,actEl2; Rect actRect; boolean buddy; for (int i = 0; i < elemStore.size(); i++) { actEl = (Element)elemStore.get(i); actRect = actEl.rect(); buddy = actRect.ulx.equal(actRect.urx); ses.add(new Interval(actRect.lly,actRect.uly,"blueleft",actRect.ulx,null,i,buddy)); ses.add(new Interval(actRect.lry,actRect.ury,"blueright",actRect.urx,null,i,buddy)); }//for i //declare the sweep status structure SSS MultiSet sss = new MultiSet(new IvlComparator(meet)); Interval actIvl,actIvl2,sesIvl; Interval sssIvl = null; Iterator it2,it4; ProListIterator it,it3; boolean predTrue = false; boolean resultIsEmpty,removedIvl,ivlOverlap = false; ElemMultiSet resultSet = new ElemMultiSet(ELEM_COMPARATOR); ElemMultiSet resultEMS = null; int sweepPointer; sesList = new ProLinkedList(ses,new IvlComparatorSimpleType(meet)); //start the sweep it = sesList.listIterator(0); sweepPointer = -1; //while there are elements left in SES, continue with the sweep while (it.hasNext()) { actIvl = (Interval)it.next(); sweepPointer++; //the actually visited interval is marked with either "blueleft" or "blueright"; //handle "blueleft" first if (actIvl.mark == "blueleft") { resultIsEmpty = false; predTrue = false; it2 = sss.iterator(); //now, traverse the intervals already stored in SSS while (it2.hasNext()) { sssIvl = (Interval)(((MultiSetEntry)it2.next()).value); predTrue = false; if (actIvl.right.less(sssIvl.left)) { //break; } else { //define ivlOverlap = true, if actIvl and sssIvl overlap //in case flag meet is true, ivlOverlap = true if the intervals are adjacent, too if (!meet) ivlOverlap = (sssIvl.left.less(actIvl.left) && sssIvl.right.greaterOrEqual(actIvl.left)) || (sssIvl.left.equal(actIvl.left) && sssIvl.right.greater(actIvl.left)) || (sssIvl.left.greater(actIvl.left) && sssIvl.left.less(actIvl.right)); else ivlOverlap = (sssIvl.left.less(actIvl.left) && sssIvl.right.greaterOrEqual(actIvl.left)) || (sssIvl.left.equal(actIvl.left) && sssIvl.right.greater(actIvl.left)) || (sssIvl.left.greater(actIvl.left) && sssIvl.left.less(actIvl.right)) || sssIvl.right.equal(actIvl.left) || sssIvl.left.equal(actIvl.right);; }//else //if both intervals overlap check the predicate if (ivlOverlap) { //now check for predicate //if predicate has one argument if (paramTypeCountP == 1) paramListP[0] = (Element)elemStore.get(actIvl.number); //if predicate has two arguments else { paramListP[0] = (Element)elemStore.get(sssIvl.number); paramListP[1] = (Element)elemStore.get(actIvl.number); }//else //evaluate predicate try { predTrue = ((Boolean)predicate.invoke((Element)elemStore.get(sssIvl.number),paramListP)).booleanValue(); } catch (Exception e) { System.out.println("Exception in SetOps.overlapReduceSweep2 when evaluating predicate."); System.out.println("elemStore.get(sssIvl.number): "+(Element)elemStore.get(sssIvl.number)); System.out.println("elemStore.get(actIvl.number): "+(Element)elemStore.get(actIvl.number)); e.printStackTrace(); throw new RuntimeException("An error occurred in the ROSEAlgebra."); }//catch //invoke method on the elements linked to the intervals, if the predicate yields true if (predTrue) { //evaluate method, note that result may be empty if (paramTypeCountM == 1) paramListM[0] = (Element)elemStore.get(actIvl.number); else { paramListM[0] = (Element)elemStore.get(sssIvl.number); paramListM[1] = (Element)elemStore.get(actIvl.number); }//else //evaluate method try { resultEMS = (ElemMultiSet)method.invoke((Element)elemStore.get(sssIvl.number),paramListM); } catch (Exception e) { System.out.println("Exception in SetOps.overlapReduceSweep when evaluating method."); System.out.println("elemStore.get(sssIvl.number): "+(Element)elemStore.get(sssIvl.number)); System.out.println("elemStore.get(actIvl.number): "+(Element)elemStore.get(actIvl.number)); e.printStackTrace(); throw new RuntimeException("An error occurred in the ROSEAlgebra."); }//catch if ((resultEMS == null) || resultEMS.isEmpty()) resultIsEmpty = true; else resultIsEmpty = false; //remove the sssIvl from sss and remove both intervals marked with sssIvl.number from SES //System.out.println("\nremove sssIvl and its partner from sss and ses. sssIvl.number: "+sssIvl.number); it2.remove(); it3 = sesList.listIterator(0); int ct = 0; while (it3.hasNext()) { actIvl2 = (Interval)it3.next(); if (actIvl2.number == sssIvl.number) { it3.remove(); ct++; }//if if (ct == 2) break; }//while it3 sweepPointer--; }//if predTrue }//if ivlOverlap //if method was invoked, break if (predTrue) break; }//while it if (predTrue) { //delete actIvl and its partner from SES it3 = sesList.listIterator(0); int ct = 0; int ct1 = 0; while (it3.hasNext()) { actIvl2 = (Interval)it3.next(); ct1++; if (actIvl2.number == actIvl.number) { it3.remove(); ct++; }//if if (ct == 2) break; }//while it if (ct != 2) { System.out.println("\nct = "+ct); System.out.println("\ndelete actIvl and its partner from SES. actIvl.nr: "+actIvl.number); System.out.println("\n********************************"); System.out.println("SESLIST:"); for (int i = 0; i < sesList.size(); i++) { System.out.print("["+i+"] ");((Interval)sesList.get(i)).print(); } System.out.println("********************************"); throw new RuntimeException("An error occurred in the ROSEAlgebra."); } sweepPointer--; //if the result of the method invocation is not empty, add all new objects to SES and elemStore; int pos; if (!resultIsEmpty) { it4 = resultEMS.iterator(); while (it4.hasNext()) { actEl2 = (Element)((MultiSetEntry)it4.next()).value; elemStore.add(actEl2); actRect = actEl2.rect(); buddy = actRect.ulx.equal(actRect.urx); pos = sesList.addSorted(new Interval(actRect.lly,actRect.uly,"blueleft",actRect.ulx,null,number,buddy)); //one or none of the new intervals may have been added BEFORE the actual x; //set pointer to pos-1 or pos-2, resp. if (pos <= sweepPointer) { sweepPointer++; }//if pos = sesList.addSorted(new Interval(actRect.lry,actRect.ury,"blueright",actRect.urx,null,number,buddy)); if (pos <= sweepPointer) { sweepPointer++; }//if number++; }//while it4 it.reset(); for (int i = 0; i < sweepPointer+1; i++) it.next(); } else { //if result is empty, two intervals were deleted, that have positions < X; //decrease sweepPointer by 2 it.reset(); for (int i = 0; i < sweepPointer+1; i++) it.next(); }//else } else { //if predTrue = false; //simply add actIvl to sss sss.add(actIvl); }//else }//if blueleft //the actually visited interval is marked "blueright" else { //store the object linked to the interval in the result set resultSet.add(elemStore.get(actIvl.number)); //remove left interval from sss it2 = sss.iterator(); while (it2.hasNext()) { sesIvl = (Interval)(((MultiSetEntry)it2.next()).value); if (sesIvl.number == actIvl.number) { it2.remove(); break; }//if }//while it2 //remove both intervals from ses it.remove(); //removes right interval it.reset(); while (it.hasNext()) { sesIvl = (Interval)(it.next()); if (sesIvl.number == actIvl.number) { it.remove(); break; }//if }//while it sweepPointer = sweepPointer-2; it.reset(); for (int i = 0; i < sweepPointer+1; i++) if (it.hasNext()) it.next(); }//if blueright }//while it return resultSet; }//end method overlapReduceSweep2 /** * Collects elements from the parameter set and stores them in the resulting set. * Every element of ljpMS consists of a pair (Element x ElemMultiSet). This method traverses the set and * stores the Element in the result set if Element != NULL. If Element == NULL, the ElemMultiSet is stored * in the result set instead. * * @param ljpMS the 'in' set * @return the collected elements of ljpMS stored in an ElemMultiSet */ public static ElemMultiSet collect (LeftJoinPairMultiSet ljpMS) { ElemMultiSet result = new ElemMultiSet(ELEM_COMPARATOR); if (ljpMS.isEmpty()) return result; Iterator it = ljpMS.iterator(); LeftJoinPair actPair; while (it.hasNext()) { actPair = (LeftJoinPair)((MultiSetEntry)it.next()).value; if (actPair.elemSet == null) { result.add(actPair.element); } else result.addAll(actPair.elemSet); }//while it.hasNext return result; }//end method collect /** * Collects elements from the parameter set and stores them in the resulting set. * This method collects only the elements of the ElemMultiSets of every entry of ljpMS. * The first entries, i.e. the Element part of every pair, is ignored. * * @param ljpMS the 'in' set * @return the collected elements stored in a ElemMultiSet */ public static ElemMultiSet collect2nd (LeftJoinPairMultiSet ljpMS) { ElemMultiSet result = new ElemMultiSet(ELEM_COMPARATOR); if (ljpMS.isEmpty()) return result; Iterator it = ljpMS.iterator(); LeftJoinPair actPair; while (it.hasNext()) { actPair = (LeftJoinPair)((MultiSetEntry)it.next()).value; if (actPair.elemSet != null) { result.addAll(actPair.elemSet); }//if }//while return result; }//end method collect2nd /** * For the two passed sets, a LeftJoinpPairMultiSet is computed where every element of the set has the form (Element x ElemMultiSet). * For every pair Element x ElementOfEMS of this set element, the predicate holds. This set is computed as follows:

* The new LeftJoinPairMultiSet is returned, then.

* This is the 'overlap' version of the normal {@link #leftOuterJoin(ElemMultiSet,ElemMultiSet,Method)}. It only works for predicates which solely hold * for elements with overlapping bounding boxes.

* If, in a special case, for one element ex of el1 no element ey of el2 is found, the entry constructed in the * resulting set is (ex x NULL), i.e. the ElemMultiSet is not initialized.

* The predicate must have the signature Element x Element -> boolean. * * @param el1 the first set of elements. These elements can be found again in the resulting LeftJoinPairMultiSet * as the first entry of every (Element x ElemMultiSet). In particular, this means that if el1 is (e1, e2, e3...), * the resulting set is ( (e1 x ems1), (e2 x ems2), (e3 x ems3) ...). * @param el2 the second set of elements. The elements of this set are found in the second entries (i.e. in the sets) * of the resulting LeftJoinPairMultiSet * @param predicate according to this predicate, the ElemMultiSet(s) in the LeftJoinPairMultiSet's entries are built * @param useOvLapPairsMeet this flag is passed to overlappingPairs(); if true objects whith adjacent bounding boxes are * reported, too * @param bboxFilter this flag is passed to overlappingPairs(); if true, in a pre-processing step the number of * candidates is reduced by removing objects of el1,el2 which don't overlap the unified bounding box of el1, * el2 resp. * @param earlyExit if true, an {@link EarlyExit} exception is thrown immediately when the first object of elN is found which * doesn't have a partner in elM, i.e. at least one element of elM which has a bounding box that overlaps the * bounding box of the element of elN. N is the passed setNumber. earlyExit is evaluated in bboxFilter, so if shall * be used, be sure that bboxFilter = true * @param setNumber must be 1 or 2; specifies the set for earlyExit * @return the resulting LeftJoinPairMultiSet * @throws EarlyExit * @see #leftOuterJoin(ElemMultiSet,ElemMultiSet,Method) */ public static LeftJoinPairMultiSet overlapLeftOuterJoin (ElemMultiSet el1, ElemMultiSet el2, Method predicate, boolean useOvLapPairsMeet, boolean bboxFilter, boolean earlyExit, int setNumber) throws EarlyExit { //compute overlapping pairs PairMultiSet pl; try { pl = overlappingPairs(el1,el2,false,useOvLapPairsMeet,bboxFilter,earlyExit,setNumber); } catch (NoOverlappingBoxFoundException nop) { throw new EarlyExit(); }//catch //filter set pl = filter(pl,predicate,true); //make final set; note, that the elements in pl are already sorted LeftJoinPairMultiSet retList = new LeftJoinPairMultiSet(LEFTJOINPAIR_COMPARATOR); Iterator lit1 = el1.iterator(); Iterator lit2; Element act1; ElemPair act2; LeftJoinPair ljp; boolean stillTrue; boolean constructedEMS; int num; while (lit1.hasNext()) { act1 = (Element)((MultiSetEntry)lit1.next()).value; ljp = new LeftJoinPair(); ljp.element = act1; constructedEMS = false; while (pl.size() > 0) { act2 = (ElemPair)pl.first(); num = pl.firstMSE().number; if (act2.first.equal(act1)) { if (!constructedEMS) { ljp.elemSet = new ElemMultiSet(ELEM_COMPARATOR); constructedEMS = true; }//if ljp.elemSet.add(act2.second); pl.removeAllOfThisKind(act2,num); }//if else break; }//while pl.size > 0 retList.add(ljp); }//while return retList; }//end method overlapLeftOuterJoin /** * Invokes the passed method on each pair of elements in every entry of ljpl. * An entry of the passed ljpl has the form (Element x ElemMultiSet). Now, let the ElemMultiSet be (l1, l2 ... ln). * Then, using the passed method subtractSets computes from such a pair (Element x (l1, l2 ... ln)) a new pair * (element x (method(Element,l1), method(Element,l2) ... method(Element,ln))).

* The method must have the signature Element x Element -> Element or * Element x Element -> ElemMultiSet. * * @param ljpl the passed LeftJoinPairMultiSet * @param method the method that is invoked on the elements * @return the result set with the changed ElemMultiSet(s) */ public static LeftJoinPairMultiSet subtractSets (LeftJoinPairMultiSet ljpl, Method method) { Iterator lit1 = ljpl.iterator(); Iterator lit2; LeftJoinPair actLjp; ElemMultiSet actList; Element actElem1; Element actElem2; ElemMultiSet subList; int paramTypeCount = Array.getLength(method.getParameterTypes()); Element[] paramList = new Element[paramTypeCount]; boolean metTypeElement = false; boolean metTypeElemMultiSet = false; //examine passed method try { if (method.getReturnType().isInstance(Class.forName("twodsack.setelement.Element")) || method.getReturnType().getSuperclass().isAssignableFrom(Class.forName("twodsack.setelement.Element"))) { metTypeElement = true; } if (method.getReturnType().isInstance(Class.forName("twodsack.set.ElemMultiSet")) || method.getReturnType().getSuperclass().isAssignableFrom(Class.forName("twodsack.set.ElemMultiSet"))) { metTypeElemMultiSet = true; } }//try catch (Exception e) { System.out.println("Exception: "+e.getClass()+" --- "+e.getMessage()); System.out.println("Error in SetOps.subtractSets: can't examine method."); e.printStackTrace(); throw new RuntimeException("An error occurred in the ROSEAlgebra."); }//catch //traverse element sets while (lit1.hasNext()) { actLjp = (LeftJoinPair)((MultiSetEntry)lit1.next()).value; actElem1 = actLjp.element; actList = actLjp.elemSet; if (!(actList == null)) { lit2 = actList.iterator(); subList = new ElemMultiSet(ELEM_COMPARATOR); while (lit2.hasNext()) { actElem2 = (Element)((MultiSetEntry)lit2.next()).value; //if method has one argument if (paramTypeCount == 1) { paramList[0] = actElem2; } //if method has two arguments else { paramList[0] = actElem1; paramList[1] = actElem2; } try { if (metTypeElement) { subList.add((Element)(method.invoke(actElem1,paramList))); } else if (metTypeElemMultiSet) { subList.addAll((ElemMultiSet)(method.invoke(actElem1,paramList))); } else { System.out.println("Error in SetOps.subtractSets: can't invoke method"); throw new RuntimeException("An error occurred in the ROSEAlgebra."); } }//try catch (Exception e) { System.out.println("Exception: "+e.getClass()+" --- "+e.getMessage()); System.out.println("Error in SetOps.subtractSets: Problem with using method."); e.printStackTrace(); throw new RuntimeException("An error occurred in the ROSEAlgebra."); }//catch }//while //substitute original elemSet with subList actLjp.elemSet = subList; }//if != null }//while return ljpl; }//end method subtractSets /** * Returns a set of elements which is 'reduced' using the passed predicate and method. * This is a variant of the normal {@link #reduce(ElemMultiSet,Method,Method)} operation. For the fuctionality of reduce itself, * have a look at that method.

* This is a recursive algorithm which uses graph algorithms to find groups of candidates for the invocation * of the passed method. First note, that the predicate must be an 'overlap' predicate, i.e. is may only yield * true, if the bounding boxes of both elements overlap or are adjacent at least. Additionally, the method * may only return an object which bounding box lies inside of the bounding box of the bounding box of the * objects it was constructed from (for clarification: the result may not be bigger than the original two * objects.)

* overlapReduce(el,PRED,METH,...) works as follows:

    *
  1. perform overlappingPairs on el to find all pairs of candidates, result is called PL; if earlyExit * flag is set, * throw an EarlyExit exception if neccessary *
  2. filter PL using PRED *
  3. if then, no pairs still exist, return el *
  4. build a graph with vertices (all elements of el) and edges; an edge exists between x,y (of el) if a pair * (x,y) exists in PL *
  5. compute the connected components of that graph *
  6. for every component COMP of the connected components, do the following

* The predicate must have the signature Element x Element -> boolean and method must have the signature * Element x Element -> ElemMultiSet * * @param el the set that shall be reduced * @param predicate according to this predicate, candidates are filtered * @param method this method is invoked on the pairs of elements that were found * @param ovLapPairsMeet this flag is passed to overlappingPairs(); if true, objects with adjacent bounding boxes are * reported, too * @param bboxFilter this flag is passed to overlappingPairs(); if true, in a pre-processing step the number of * candidates is reduced by removing objects of el1,el2 which don't overlap the unified bounding box of el1,el2 resp. * @param earlyExit if true, an EarlyExit exception is thrown immediately when the first object of elN is found which * doesn't have a partner in elM, i.e. at least one element of elM which has a bounding box that overlaps the * bounding box of the element of elN. N is the passed setNumber. earlyExit is evaluated in bboxFilter, so if shall * be used, be sure that bboxFilter = true * @param setNumber must be 1 or 2; specifies the set for earlyExit * @return the 'reduced' set of elements * @throws EarlyExit */ public static ElemMultiSet overlapReduce (ElemMultiSet el, Method predicate, Method method, boolean ovLapPairsMeet, boolean bboxFilter, boolean earlyExit, int setNumber) throws EarlyExit { ElemMultiSet retList = new ElemMultiSet(ELEM_COMPARATOR); PairMultiSet pl; try { pl = overlappingPairs(el,el,true,ovLapPairsMeet,bboxFilter,earlyExit,setNumber); } catch (NoOverlappingBoxFoundException nop) { throw new EarlyExit(); }//catch pl = filter(pl,predicate,true); if (pl.isEmpty()) { return el; } Graph g = new Graph(el,pl); ConnectedComponentsPair ccp = g.connectedComponents(); //the pairs from ccE must not be computed all at once //so compute the set of pairs that may be computed ccp = g.computeReducedPair(ccp); ElemMultiSetList vertices = ccp.verticesToEMSList(); PairMultiSetList edges = ccp.edgesToPairListList(); ListIterator litE = edges.listIterator(0); ListIterator litV = vertices.listIterator(0); PairMultiSet actPL; ElemMultiSet actEL; ElemMultiSet ml = new ElemMultiSet(ELEM_COMPARATOR); while (litE.hasNext()) { actPL = (PairMultiSet)litE.next(); actEL = (ElemMultiSet)litV.next(); //if there are no pairs, get the isolated elements from actEL if (!actPL.isEmpty()) { ml = map(actPL,method); } else { ml = new ElemMultiSet(ELEM_COMPARATOR); } retList.addAll(overlapReduce(disjointUnion(ml,actEL),predicate,method,ovLapPairsMeet,bboxFilter,earlyExit,setNumber)); }//for i rdup(retList); return retList; }//end method overlapReduce /** * Lets through pairs of the set depending on the predicate and the 'keep' flag. * If keep = true, a pair (x,y) is kept if predicate(x,y) = true. All other pairs are deleted. If keep = false, * the inverse set is returned.

* The predicate must have the signature Element x Element -> boolean. * * @param plIn the set of pairs * @param predicate the predicate that is used to filter the pairs * @param keep if true, pairs are kept for which the predicate holds * @return the filtered set */ public static PairMultiSet filter (PairMultiSet plIn, Method predicate, boolean keep) { int paramTypeCount = Array.getLength(predicate.getParameterTypes()); Element[] paramList = new Element[paramTypeCount]; boolean predHolds = false; Iterator it = plIn.iterator(); ElemPair actElem; MultiSetEntry mse; int number; while (it.hasNext()) { actElem = (ElemPair)((MultiSetEntry)it.next()).value; //if predicate is non-static if (paramTypeCount == 1) { paramList[0] = actElem.second; }//if //if predicate is static else { paramList[0] = actElem.first; paramList[1] = actElem.second; }//else try { predHolds = (((Boolean)predicate.invoke(actElem.first,paramList)).booleanValue()); }//try catch (Exception e) { System.out.println("Exception: "+e.getClass()+" in SetOps.filter(PairList,Method,boolean). Can't invoke method '"+predicate+"' on"); actElem.first.print(); actElem.second.print(); e.printStackTrace(); throw new RuntimeException("An error occurred in the ROSEAlgebra."); }//catch if ((predHolds && !keep) || (!predHolds && keep)) { it.remove(); } }//while plIn.recomputeSize(); return plIn; }//end method filter /** * Divides the elements of the passed set in groups according to the parameter predicate.

* This is the 'overlap' variant of the ordinary {@link #group(ElemMultiSet,Method)}. The result of this method is a list of ElemMultiSet(s). * For every pair of elements (x,y) of such a group (ElemMultiSet), predicate(x,y) holds.

* First note, that the passed predicate must be an 'overlap' predicate. Such a predicate may only hold, if * the bounding box of its parameter objects overlap or are adjacent at least.

* This method works as follows: