001package com.hfg.math;
002
003import java.math.BigDecimal;
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.List;
008
009import com.hfg.exception.ProgrammingException;
010import com.hfg.util.CompareUtil;
011import com.hfg.util.collection.CollectionUtil;
012
013//------------------------------------------------------------------------------
014/**
015 Generic range object.
016 <p></p>
017 @author J. Alex Taylor, hairyfatguy.com
018 */
019//------------------------------------------------------------------------------
020// com.hfg Library
021//
022// This library is free software; you can redistribute it and/or
023// modify it under the terms of the GNU Lesser General Public
024// License as published by the Free Software Foundation; either
025// version 2.1 of the License, or (at your option) any later version.
026//
027// This library is distributed in the hope that it will be useful,
028// but WITHOUT ANY WARRANTY; without even the implied warranty of
029// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
030// Lesser General Public License for more details.
031//
032// You should have received a copy of the GNU Lesser General Public
033// License along with this library; if not, write to the Free Software
034// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
035//
036// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
037// jataylor@hairyfatguy.com
038//------------------------------------------------------------------------------
039public class Range<T extends Number> implements Cloneable, Comparable<Range<T>>
040{
041   private T mStart;
042   private T mEnd;
043
044   // Cached values
045   private Boolean mEndValueIsExclusive;
046
047   //###########################################################################
048   // CONSTRUCTORS
049   //###########################################################################
050
051   //--------------------------------------------------------------------------
052   public Range()
053   {
054   }
055
056   //--------------------------------------------------------------------------
057   public Range(T inStart, T inEnd)
058   {
059      mStart = inStart;
060      mEnd   = inEnd;
061   }
062
063   //###########################################################################
064   // PUBLIC METHODS
065   //###########################################################################
066
067   //--------------------------------------------------------------------------
068   public Range<T> setStart(T inValue)
069   {
070      mStart = inValue;
071      return this;
072   }
073
074   //--------------------------------------------------------------------------
075   public T getStart()
076   {
077      return mStart;
078   }
079
080
081   //--------------------------------------------------------------------------
082   public Range<T> setEnd(T inValue)
083   {
084      mEnd = inValue;
085      return this;
086   }
087
088   //--------------------------------------------------------------------------
089   public T getEnd()
090   {
091      return mEnd;
092   }
093
094
095   //--------------------------------------------------------------------------
096   @Override
097   public String toString()
098   {
099      return String.format("[%s, %s]",
100            getStart() != null ? getStart().toString() : null,
101            getEnd() != null ? getEnd().toString() : null);
102   }
103
104   //---------------------------------------------------------------------------
105   @Override
106   public Range<T> clone()
107   {
108      Range<T>  cloneObj;
109      try
110      {
111         cloneObj = (Range<T>) super.clone();
112      }
113      catch (CloneNotSupportedException e)
114      {
115         throw new ProgrammingException(e);
116      }
117
118      return cloneObj;
119   }
120
121   //--------------------------------------------------------------------------
122   @Override
123   public int hashCode()
124   {
125      int hashCode = (getStart() != null ? getStart().hashCode() : 0);
126      hashCode += 31 * (getEnd() != null ? getEnd().hashCode() : 0);
127
128      return hashCode;
129   }
130
131   //--------------------------------------------------------------------------
132   @Override
133   public boolean equals(Object inObj2)
134   {
135      boolean result = false;
136
137      if (inObj2 != null
138            && inObj2 instanceof Range)
139      {
140         result = (0 == compareTo((Range<T>)inObj2));
141      }
142
143      return result;
144   }
145
146   //--------------------------------------------------------------------------
147   @Override
148   public int compareTo(Range<T> inObj2)
149   {
150      int result = 0;
151
152      if (null == inObj2)
153      {
154         result = 1;
155      }
156      else
157      {
158         // Some custom work here to ensure that null starts sort first and
159         // null ends sort last
160         if (getStart() != null)
161         {
162            if (inObj2.getStart() != null)
163            {
164               result = CompareUtil.compare(getStart(), inObj2.getStart());
165            }
166         }
167         else if (inObj2.getStart() != null)
168         {
169            result = -1;
170         }
171
172         if (0 == result)
173         {
174            if (getEnd() != null)
175            {
176               if (inObj2.getEnd() != null)
177               {
178                  result = CompareUtil.compare(getEnd(), inObj2.getEnd());
179               }
180            }
181            else if (inObj2.getEnd() != null)
182            {
183               result = 1;
184            }
185         }
186      }
187
188      return result;
189   }
190
191   //--------------------------------------------------------------------------
192   public Double length()
193   {
194      Double length = null;
195      if (getStart() != null
196            && getEnd() != null)
197      {
198         length = getEnd().doubleValue() - getStart().doubleValue();
199
200         // Is the end value inclusive?
201         if (! endValueIsExclusive())
202         {
203            length += 1;
204         }
205      }
206
207      return length;
208   }
209
210   //--------------------------------------------------------------------------
211   public boolean contains(Number inValue)
212   {
213      return ((inValue != null
214               && ((getStart() != null
215                && inValue.doubleValue() >= getStart().doubleValue()
216                && getEnd() != null
217                && (inValue.doubleValue() < getEnd().doubleValue()
218                   || (! endValueIsExclusive()
219                       && inValue.doubleValue() == getEnd().doubleValue())))
220               || (null == getStart()
221                  && getEnd() != null
222                  && (inValue.doubleValue() < getEnd().doubleValue()
223                      || (! endValueIsExclusive()
224                          && inValue.doubleValue() == getEnd().doubleValue())))
225               || (null == getEnd()
226                  && getStart() != null
227                  && inValue.doubleValue() >= getStart().doubleValue()))
228               || (null == getStart()
229                   && null == getEnd()))
230              || (null == getStart()
231                  && null == getEnd()));
232   }
233
234   //--------------------------------------------------------------------------
235   public boolean contains(int inValue)
236   {
237      return ((getStart() != null
238               && inValue >= getStart().doubleValue()
239               && getEnd() != null
240               && (inValue < getEnd().doubleValue()
241                   || (! endValueIsExclusive()
242                       && inValue == getEnd().doubleValue())))
243              || (null == getStart()
244                  && getEnd() != null
245                  && (inValue < getEnd().doubleValue()
246                      || (! endValueIsExclusive()
247                          && inValue == getEnd().doubleValue())))
248              || (null == getEnd()
249                  && getStart() != null
250                  && inValue >= getStart().doubleValue())
251              || (null == getStart()
252                  && null == getEnd()));
253   }
254
255   //--------------------------------------------------------------------------
256   public boolean contains(float inValue)
257   {
258      return ((getStart() != null
259               && inValue >= getStart().doubleValue()
260               && getEnd() != null
261               && (inValue < getEnd().doubleValue()
262                   || (! endValueIsExclusive()
263                       && inValue == getEnd().doubleValue())))
264              || (null == getStart()
265                  && getEnd() != null
266                  && (inValue < getEnd().doubleValue()
267                      || (! endValueIsExclusive()
268                          && inValue == getEnd().doubleValue())))
269              || (null == getEnd()
270                  && getStart() != null
271                  && inValue >= getStart().doubleValue())
272              || (null == getStart()
273                  && null == getEnd()));
274   }
275
276   //--------------------------------------------------------------------------
277   public boolean contains(double inValue)
278   {
279      return ((getStart() != null
280               && inValue >= getStart().doubleValue()
281               && getEnd() != null
282               && (inValue < getEnd().doubleValue()
283                   || (! endValueIsExclusive()
284                       && inValue == getEnd().doubleValue())))
285              || (null == getStart()
286                  && getEnd() != null
287                  && (inValue < getEnd().doubleValue()
288                      || (! endValueIsExclusive()
289                          && inValue == getEnd().doubleValue())))
290              || (null == getEnd()
291                  && getStart() != null
292                  && inValue >= getStart().doubleValue())
293              || (null == getStart()
294                  && null == getEnd()));
295   }
296
297   //--------------------------------------------------------------------------
298   public boolean contains(Range<T> inRange2)
299   {
300      return (contains(inRange2.getStart().doubleValue())
301              && contains(inRange2.getEnd().doubleValue()));
302   }
303
304   //---------------------------------------------------------------------------
305   /**
306    Collapses a collection of specified ranges.
307    <p>
308    Ex: given three ranges: [1,4], [2,10], and [14,15] the result would be two ranges: [1,10] and [14,15].
309    </p>
310    @param inOrigList a collection of Ranges to be combined.
311    @return a list of Ranges that represents the union of the specified Ranges.
312    */
313   public static <T extends Number> List<Range<T>> union(Collection<Range<T>> inOrigList)
314   {
315      List<Range<T>> condensedRanges = new ArrayList<>(inOrigList);
316
317      boolean overlap = true;
318      while (overlap)
319      {
320         overlap = false;
321         for (int i = 0; i < condensedRanges.size() - 1; i++)
322         {
323            Range<T> range1 = condensedRanges.get(i);
324
325            for (int j = i + 1; j < condensedRanges.size(); j++)
326            {
327               Range<T> range2 = condensedRanges.get(j);
328
329               if (range1.intersects(range2)
330                     || (! range1.endValueIsExclusive()
331                         && ((range1.getStart() != null
332                              && range2.getEnd() != null
333                              && 1 == range1.getStart().longValue() - range2.getEnd().longValue())
334                             || (range1.getEnd() != null
335                                 && range2.getStart() != null
336                                 && 1 == range2.getStart().longValue() - range1.getEnd().longValue()))))
337               {
338                  T condensedStart = null;
339                  if (range1.getStart() != null
340                        && range2.getStart() != null)
341                  {
342                     int startComparison = CompareUtil.compare(range1.getStart(), range2.getStart());
343                     condensedStart = startComparison > 0 ? range2.getStart() : range1.getStart();
344                  }
345
346                  T condensedEnd = null;
347                  if (range1.getEnd() != null
348                        && range2.getEnd() != null)
349                  {
350                     int endComparison = CompareUtil.compare(range1.getEnd(), range2.getEnd());
351                     condensedEnd = endComparison > 0 ? range1.getEnd() : range2.getEnd();
352                  }
353
354                  Range<T> condensedRange = new Range<T>(condensedStart, condensedEnd);
355
356                  condensedRanges.remove(j);
357                  condensedRanges.remove(i);
358                  condensedRanges.add(condensedRange);
359                  overlap = true;
360                  break;
361               }
362            }
363
364            if (overlap)
365            {
366               break;
367            }
368         }
369      }
370
371      Collections.sort(condensedRanges);
372
373      return condensedRanges;
374   }
375
376   //---------------------------------------------------------------------------
377   /**
378    Returns a single Range that encompasses the collection of input ranges and ignores internal gaps.
379    @param inOrigList a collection of input ranges
380    @return a single Range that encompasses the input ranges and ignores internal gaps
381    */
382   public static <T extends Number> Range<T> superUnion(Collection<Range<T>> inOrigList)
383   {
384      Range<T> superUnion = null;
385
386      if (CollectionUtil.hasValues(inOrigList))
387      {
388         for (Range<T> range : inOrigList)
389         {
390            if (null == superUnion)
391            {
392               superUnion = range;
393            }
394            else
395            {
396               superUnion = superUnion.superUnion(range);
397            }
398         }
399      }
400
401      return superUnion;
402   }
403
404   //---------------------------------------------------------------------------
405   /**
406    Returns a single Range that encompasses both input ranges and ignores internal gaps.
407    @param inRange2 the second Range to union with the current range
408    @return a single Range that encompasses both input ranges and ignores internal gaps
409    */
410   public Range<T> superUnion(Range<T> inRange2)
411   {
412      T start = null;
413      T end = null;
414
415      if (inRange2 != null)
416      {
417         if (getStart() != null
418             && inRange2.getStart() != null)
419         {
420            start = (getStart().doubleValue() < inRange2.getStart().doubleValue() ? getStart() : inRange2.getStart());
421         }
422
423         if (getEnd() != null
424               && inRange2.getEnd() != null)
425         {
426            end = (getEnd().doubleValue() > inRange2.getEnd().doubleValue() ? getEnd() : inRange2.getEnd());
427         }
428      }
429
430      return new Range<>(start, end);
431   }
432
433   //---------------------------------------------------------------------------
434   public List<Range<T>> subtract(Range<T> inRange2)
435   {
436      List<Range<T>> resultRanges = new ArrayList<>(2);
437
438      Range<T> intersection = intersection(inRange2);
439      if (intersection != null)
440      {
441         // Right chunk?
442         Range<T> leftChunk;
443         if (null == getStart())
444         {
445            if (intersection.getStart() != null)
446            {
447               leftChunk = new Range<>();
448
449               if (endValueIsExclusive())
450               {
451                  leftChunk.setEnd(intersection.getStart());
452               }
453               else
454               {
455                  if (intersection.getStart() instanceof Long)
456                  {
457                     leftChunk.setEnd((T) new Long(intersection.getStart().longValue() - 1));
458                  }
459                  else if (intersection.getStart() instanceof Integer)
460                  {
461                     leftChunk.setEnd((T) new Integer(intersection.getStart().intValue() - 1));
462                  }
463               }
464
465               resultRanges.add(leftChunk);
466            }
467         }
468         else if (CompareUtil.compare(intersection.getStart(), getStart()) > 0)
469         {
470            leftChunk = new Range<>();
471            leftChunk.setStart(getStart());
472
473            if (endValueIsExclusive())
474            {
475               leftChunk.setEnd(intersection.getStart());
476            }
477            else
478            {
479               if (intersection.getStart() instanceof Long)
480               {
481                  leftChunk.setEnd((T) new Long(intersection.getStart().longValue() - 1));
482               }
483               else if (intersection.getStart() instanceof Integer)
484               {
485                  leftChunk.setEnd((T) new Integer(intersection.getStart().intValue() - 1));
486               }
487            }
488
489            resultRanges.add(leftChunk);
490         }
491
492         // Right chunk?
493         Range<T> rightChunk;
494         if (null == getEnd())
495         {
496            if (intersection.getEnd() != null)
497            {
498               rightChunk = new Range<>();
499
500               if (endValueIsExclusive())
501               {
502                  rightChunk.setStart(intersection.getEnd());
503               }
504               else
505               {
506                  if (intersection.getEnd() instanceof Long)
507                  {
508                     rightChunk.setStart((T) new Long(intersection.getEnd().longValue() + 1));
509                  }
510                  else if (intersection.getEnd() instanceof Integer)
511                  {
512                     rightChunk.setStart((T) new Integer(intersection.getEnd().intValue() + 1));
513                  }
514               }
515
516               resultRanges.add(rightChunk);
517            }
518         }
519         else if (CompareUtil.compare(intersection.getEnd(), getEnd()) < 0)
520         {
521            rightChunk = new Range<>();
522            rightChunk.setEnd(getEnd());
523
524            if (endValueIsExclusive())
525            {
526               rightChunk.setStart(intersection.getEnd());
527            }
528            else
529            {
530               if (intersection.getStart() instanceof Long)
531               {
532                  rightChunk.setStart((T) new Long(intersection.getEnd().longValue() + 1));
533               }
534               else if (intersection.getStart() instanceof Integer)
535               {
536                  rightChunk.setStart((T) new Integer(intersection.getEnd().intValue() + 1));
537               }
538            }
539
540            resultRanges.add(rightChunk);
541         }
542
543
544      }
545      else
546      {
547         // There was no intersection, hence return the orig.
548         resultRanges.add(this);
549      }
550
551      return resultRanges;
552   }
553
554   //---------------------------------------------------------------------------
555   public boolean endValueIsExclusive()
556   {
557      if (null == mEndValueIsExclusive)
558      {
559         // We can't interrogate T directly so we have to look at the values.
560         T end = getEnd();
561         if (end != null)
562         {
563            mEndValueIsExclusive = (end instanceof Double || end instanceof Float || end instanceof BigDecimal);
564         }
565         else
566         {
567            T start = getStart();
568            if (start != null)
569            {
570               mEndValueIsExclusive = (start instanceof Double || start instanceof Float || start instanceof BigDecimal);
571            }
572         }
573      }
574
575      return mEndValueIsExclusive;
576   }
577
578   //--------------------------------------------------------------------------
579   public Range<T> intersection(Range<T> inRange2)
580   {
581      Range<T> intersection = null;
582
583      if (inRange2 != null)
584      {
585         if (getStart() != null)
586         {
587            if (inRange2.getStart() != null)
588            {
589               if (getEnd() != null)
590               {
591                  if (inRange2.getEnd() != null)
592                  {
593                     if (getStart().doubleValue() <= inRange2.getEnd().doubleValue()
594                           && getEnd().doubleValue() >= inRange2.getStart().doubleValue())
595                     {
596                        //          -----
597                        //             -----
598
599                        intersection = new Range<T>()
600                              .setStart(getStart().doubleValue() < inRange2.getStart().doubleValue() ? inRange2.getStart() : getStart())
601                              .setEnd(getEnd().doubleValue() > inRange2.getEnd().doubleValue() ? inRange2.getEnd() : getEnd());
602                     }
603                  }
604                  else // Range 2 extends infinitely to the right
605                  {
606                     //          -----
607                     //             ----->
608                     if (inRange2.contains(getStart().doubleValue()))
609                     {
610                        intersection = new Range<T>()
611                              .setStart(getStart().doubleValue() < inRange2.getStart().doubleValue() ? inRange2.getStart() : getStart())
612                              .setEnd(getEnd());
613                     }
614                  }
615               }
616               else if (inRange2.getEnd() != null) // Range 1 extends infinitely to the right
617               {
618                  //             ----->
619                  //          -----
620
621                  if (getStart().doubleValue() <= inRange2.getEnd().doubleValue())
622                  {
623                     intersection = new Range<T>()
624                           .setStart(getStart().doubleValue() < inRange2.getStart().doubleValue() ? inRange2.getStart() : getStart())
625                           .setEnd(inRange2.getEnd());
626                  }
627               }
628               else
629               {
630                  // Both ranges extend infinitely to the right
631                  //               ----->
632                  //             ------->
633                  intersection = new Range<T>()
634                        .setStart(getStart().doubleValue() > inRange2.getStart().doubleValue() ? inRange2.getStart() : getStart())
635                        .setEnd(getStart().doubleValue() > inRange2.getStart().doubleValue() ? getStart() : inRange2.getStart());
636               }
637            }
638            else // Range 2 extends infinitely to the left
639            {
640               if (getEnd() != null)
641               {
642                  if (inRange2.getEnd() != null)
643                  {  //               -----
644                     //             <-------
645                     if (getStart().doubleValue() <= inRange2.getEnd().doubleValue())
646                     {
647                        intersection = new Range<T>()
648                              .setStart(getStart())
649                              .setEnd(getEnd().doubleValue() > inRange2.getEnd().doubleValue() ? inRange2.getEnd() : getEnd());
650                     }
651                  }
652                  else // Range 2 extends infinitely to the left & right
653                  {    //               -----
654                     //             <------->
655                     intersection = clone();
656                  }
657               }
658               else if (inRange2.getEnd() != null) // Range 1 extends infinitely to the right
659               {   //             ----->
660                  //          <---
661                  if (inRange2.contains(getStart().doubleValue()))
662                  {
663                     intersection = new Range<T>()
664                           .setStart(getStart())
665                           .setEnd(inRange2.getEnd());
666                  }
667               }
668               else
669               {
670                  // Both ranges extend infinitely to the right
671                  //             ----->
672                  //          <------->
673                  intersection = new Range<T>()
674                        .setStart(getStart());
675               }
676            }
677         }
678         else if (inRange2.getStart() != null)
679         {
680            if (getEnd() != null)
681            {
682               if (inRange2.getEnd() != null)
683               {
684                  //             <-----
685                  //               -------
686                  if (inRange2.getStart().doubleValue() <= getEnd().doubleValue())
687                  {
688                     intersection = new Range<T>()
689                           .setStart(inRange2.getStart())
690                           .setEnd(getEnd().doubleValue() <= inRange2.getEnd().doubleValue() ? getEnd() : inRange2.getEnd());
691                  }
692               }
693               else
694               {
695                  //             <-----
696                  //               ------->
697                  if (inRange2.getStart().doubleValue() <= getEnd().doubleValue())
698                  {
699                     intersection = new Range<T>()
700                           .setStart(inRange2.getStart())
701                           .setEnd(getEnd());
702                  }
703               }
704            }
705            else
706            {
707               if (inRange2.getEnd() != null)
708               {
709                  //         <----->
710                  //            ---
711                  intersection = inRange2.clone();
712               }
713               else
714               {
715                  //         <----->
716                  //            --->
717                  intersection = new Range<T>()
718                        .setStart(inRange2.getStart());
719
720               }
721            }
722         }
723         else
724         {
725            if (getEnd() != null)
726            {
727               if (inRange2.getEnd() != null)
728               {
729                  //         <-----
730                  //         <---
731                  intersection = new Range<T>()
732                        .setEnd(getEnd().doubleValue() <= inRange2.getEnd().doubleValue() ? getEnd() : inRange2.getEnd());
733               }
734               else
735               {
736                  //         <-----
737                  //         <-------->
738                  intersection = clone();
739               }
740            }
741            else
742            {
743               if (inRange2.getEnd() != null)
744               {
745                  //         <----->
746                  //         <---
747                  intersection = inRange2.clone();
748               }
749               else
750               {
751                  //         <----->
752                  //         <----->
753                  intersection = clone();
754               }
755            }
756         }
757      }
758
759      return intersection;
760   }
761
762   //--------------------------------------------------------------------------
763   public boolean intersects(Range<T> inRange2)
764   {
765      boolean result = false;
766
767      if (getStart() != null)
768      {
769         if (inRange2.getStart() != null)
770         {
771            if (getEnd() != null)
772            {
773               if (inRange2.getEnd() != null)
774               {
775                  if (endValueIsExclusive())
776                  {
777                     result = getStart().doubleValue() < inRange2.getEnd().doubleValue()
778                              && getEnd().doubleValue() > inRange2.getStart().doubleValue();
779                  }
780                  else
781                  {
782                     result = getStart().doubleValue() <= inRange2.getEnd().doubleValue()
783                           && getEnd().doubleValue() >= inRange2.getStart().doubleValue();
784                  }
785               }
786               else // Range 2 extends infinitely to the right
787               {
788                  result = inRange2.getStart().doubleValue() <= getEnd().doubleValue();
789               }
790            }
791            else if (inRange2.getEnd() != null)
792            {  // Range 1 extends infinitely to the right
793               result = inRange2.getEnd().doubleValue() >= getStart().doubleValue();
794            }
795            else
796            {
797               // Both ranges extend infinitely to the right
798               result = true;
799            }
800         }
801         else // Range 2 extends infinitely to the left
802         {
803            if (inRange2.getEnd() != null)
804            {
805               if (endValueIsExclusive())
806               {
807                  result = getStart().doubleValue() < inRange2.getEnd().doubleValue();
808               }
809               else
810               {
811                  result = getStart().doubleValue() <= inRange2.getEnd().doubleValue();
812               }
813            }
814            else
815            {
816               // Range2 is infinite
817               result = true;
818            }
819         }
820      }
821      else if (inRange2.getStart() != null)
822      {
823         if (getEnd() != null)
824         {
825            if (endValueIsExclusive())
826            {
827               result = inRange2.getStart().doubleValue() < getEnd().doubleValue();
828            }
829            else
830            {
831               result = inRange2.getStart().doubleValue() <= getEnd().doubleValue();
832            }
833         }
834         else
835         {
836            result = true;
837         }
838      }
839      else
840      {
841         // Both ranges extend infinitely to the left
842         result = true;
843      }
844
845      return result;
846   }
847
848   //--------------------------------------------------------------------------
849   public void swapStartEndValues()
850   {
851      T tmp = getEnd();
852      setEnd(getStart());
853      setStart(tmp);
854   }
855}