001 /* 002 * Copyright 2001-2009 Stephen Colebourne 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.joda.time; 017 018 import java.io.Serializable; 019 import java.util.ArrayList; 020 import java.util.Arrays; 021 import java.util.HashMap; 022 import java.util.List; 023 import java.util.Map; 024 025 import org.joda.time.field.FieldUtils; 026 027 /** 028 * Controls a period implementation by specifying which duration fields are to be used. 029 * <p> 030 * The following implementations are provided: 031 * <ul> 032 * <li>Standard - years, months, weeks, days, hours, minutes, seconds, millis 033 * <li>YearMonthDayTime - years, months, days, hours, minutes, seconds, millis 034 * <li>YearMonthDay - years, months, days 035 * <li>YearWeekDayTime - years, weeks, days, hours, minutes, seconds, millis 036 * <li>YearWeekDay - years, weeks, days 037 * <li>YearDayTime - years, days, hours, minutes, seconds, millis 038 * <li>YearDay - years, days, hours 039 * <li>DayTime - days, hours, minutes, seconds, millis 040 * <li>Time - hours, minutes, seconds, millis 041 * <li>plus one for each single type 042 * </ul> 043 * 044 * <p> 045 * PeriodType is thread-safe and immutable, and all subclasses must be as well. 046 * 047 * @author Brian S O'Neill 048 * @author Stephen Colebourne 049 * @since 1.0 050 */ 051 public class PeriodType implements Serializable { 052 /** Serialization version */ 053 private static final long serialVersionUID = 2274324892792009998L; 054 055 /** Cache of all the known types. */ 056 private static final Map<PeriodType, Object> cTypes = new HashMap<PeriodType, Object>(32); 057 058 static int YEAR_INDEX = 0; 059 static int MONTH_INDEX = 1; 060 static int WEEK_INDEX = 2; 061 static int DAY_INDEX = 3; 062 static int HOUR_INDEX = 4; 063 static int MINUTE_INDEX = 5; 064 static int SECOND_INDEX = 6; 065 static int MILLI_INDEX = 7; 066 067 private static PeriodType cStandard; 068 private static PeriodType cYMDTime; 069 private static PeriodType cYMD; 070 private static PeriodType cYWDTime; 071 private static PeriodType cYWD; 072 private static PeriodType cYDTime; 073 private static PeriodType cYD; 074 private static PeriodType cDTime; 075 private static PeriodType cTime; 076 077 private static PeriodType cYears; 078 private static PeriodType cMonths; 079 private static PeriodType cWeeks; 080 private static PeriodType cDays; 081 private static PeriodType cHours; 082 private static PeriodType cMinutes; 083 private static PeriodType cSeconds; 084 private static PeriodType cMillis; 085 086 /** 087 * Gets a type that defines all standard fields. 088 * <ul> 089 * <li>years 090 * <li>months 091 * <li>weeks 092 * <li>days 093 * <li>hours 094 * <li>minutes 095 * <li>seconds 096 * <li>milliseconds 097 * </ul> 098 * 099 * @return the period type 100 */ 101 public static PeriodType standard() { 102 PeriodType type = cStandard; 103 if (type == null) { 104 type = new PeriodType( 105 "Standard", 106 new DurationFieldType[] { 107 DurationFieldType.years(), DurationFieldType.months(), 108 DurationFieldType.weeks(), DurationFieldType.days(), 109 DurationFieldType.hours(), DurationFieldType.minutes(), 110 DurationFieldType.seconds(), DurationFieldType.millis(), 111 }, 112 new int[] { 0, 1, 2, 3, 4, 5, 6, 7, } 113 ); 114 cStandard = type; 115 } 116 return type; 117 } 118 119 /** 120 * Gets a type that defines all standard fields except weeks. 121 * <ul> 122 * <li>years 123 * <li>months 124 * <li>days 125 * <li>hours 126 * <li>minutes 127 * <li>seconds 128 * <li>milliseconds 129 * </ul> 130 * 131 * @return the period type 132 */ 133 public static PeriodType yearMonthDayTime() { 134 PeriodType type = cYMDTime; 135 if (type == null) { 136 type = new PeriodType( 137 "YearMonthDayTime", 138 new DurationFieldType[] { 139 DurationFieldType.years(), DurationFieldType.months(), 140 DurationFieldType.days(), 141 DurationFieldType.hours(), DurationFieldType.minutes(), 142 DurationFieldType.seconds(), DurationFieldType.millis(), 143 }, 144 new int[] { 0, 1, -1, 2, 3, 4, 5, 6, } 145 ); 146 cYMDTime = type; 147 } 148 return type; 149 } 150 151 /** 152 * Gets a type that defines the year, month and day fields. 153 * <ul> 154 * <li>years 155 * <li>months 156 * <li>days 157 * </ul> 158 * 159 * @return the period type 160 * @since 1.1 161 */ 162 public static PeriodType yearMonthDay() { 163 PeriodType type = cYMD; 164 if (type == null) { 165 type = new PeriodType( 166 "YearMonthDay", 167 new DurationFieldType[] { 168 DurationFieldType.years(), DurationFieldType.months(), 169 DurationFieldType.days(), 170 }, 171 new int[] { 0, 1, -1, 2, -1, -1, -1, -1, } 172 ); 173 cYMD = type; 174 } 175 return type; 176 } 177 178 /** 179 * Gets a type that defines all standard fields except months. 180 * <ul> 181 * <li>years 182 * <li>weeks 183 * <li>days 184 * <li>hours 185 * <li>minutes 186 * <li>seconds 187 * <li>milliseconds 188 * </ul> 189 * 190 * @return the period type 191 */ 192 public static PeriodType yearWeekDayTime() { 193 PeriodType type = cYWDTime; 194 if (type == null) { 195 type = new PeriodType( 196 "YearWeekDayTime", 197 new DurationFieldType[] { 198 DurationFieldType.years(), 199 DurationFieldType.weeks(), DurationFieldType.days(), 200 DurationFieldType.hours(), DurationFieldType.minutes(), 201 DurationFieldType.seconds(), DurationFieldType.millis(), 202 }, 203 new int[] { 0, -1, 1, 2, 3, 4, 5, 6, } 204 ); 205 cYWDTime = type; 206 } 207 return type; 208 } 209 210 /** 211 * Gets a type that defines year, week and day fields. 212 * <ul> 213 * <li>years 214 * <li>weeks 215 * <li>days 216 * </ul> 217 * 218 * @return the period type 219 * @since 1.1 220 */ 221 public static PeriodType yearWeekDay() { 222 PeriodType type = cYWD; 223 if (type == null) { 224 type = new PeriodType( 225 "YearWeekDay", 226 new DurationFieldType[] { 227 DurationFieldType.years(), 228 DurationFieldType.weeks(), DurationFieldType.days(), 229 }, 230 new int[] { 0, -1, 1, 2, -1, -1, -1, -1, } 231 ); 232 cYWD = type; 233 } 234 return type; 235 } 236 237 /** 238 * Gets a type that defines all standard fields except months and weeks. 239 * <ul> 240 * <li>years 241 * <li>days 242 * <li>hours 243 * <li>minutes 244 * <li>seconds 245 * <li>milliseconds 246 * </ul> 247 * 248 * @return the period type 249 */ 250 public static PeriodType yearDayTime() { 251 PeriodType type = cYDTime; 252 if (type == null) { 253 type = new PeriodType( 254 "YearDayTime", 255 new DurationFieldType[] { 256 DurationFieldType.years(), DurationFieldType.days(), 257 DurationFieldType.hours(), DurationFieldType.minutes(), 258 DurationFieldType.seconds(), DurationFieldType.millis(), 259 }, 260 new int[] { 0, -1, -1, 1, 2, 3, 4, 5, } 261 ); 262 cYDTime = type; 263 } 264 return type; 265 } 266 267 /** 268 * Gets a type that defines the year and day fields. 269 * <ul> 270 * <li>years 271 * <li>days 272 * </ul> 273 * 274 * @return the period type 275 * @since 1.1 276 */ 277 public static PeriodType yearDay() { 278 PeriodType type = cYD; 279 if (type == null) { 280 type = new PeriodType( 281 "YearDay", 282 new DurationFieldType[] { 283 DurationFieldType.years(), DurationFieldType.days(), 284 }, 285 new int[] { 0, -1, -1, 1, -1, -1, -1, -1, } 286 ); 287 cYD = type; 288 } 289 return type; 290 } 291 292 /** 293 * Gets a type that defines all standard fields from days downwards. 294 * <ul> 295 * <li>days 296 * <li>hours 297 * <li>minutes 298 * <li>seconds 299 * <li>milliseconds 300 * </ul> 301 * 302 * @return the period type 303 */ 304 public static PeriodType dayTime() { 305 PeriodType type = cDTime; 306 if (type == null) { 307 type = new PeriodType( 308 "DayTime", 309 new DurationFieldType[] { 310 DurationFieldType.days(), 311 DurationFieldType.hours(), DurationFieldType.minutes(), 312 DurationFieldType.seconds(), DurationFieldType.millis(), 313 }, 314 new int[] { -1, -1, -1, 0, 1, 2, 3, 4, } 315 ); 316 cDTime = type; 317 } 318 return type; 319 } 320 321 /** 322 * Gets a type that defines all standard time fields. 323 * <ul> 324 * <li>hours 325 * <li>minutes 326 * <li>seconds 327 * <li>milliseconds 328 * </ul> 329 * 330 * @return the period type 331 */ 332 public static PeriodType time() { 333 PeriodType type = cTime; 334 if (type == null) { 335 type = new PeriodType( 336 "Time", 337 new DurationFieldType[] { 338 DurationFieldType.hours(), DurationFieldType.minutes(), 339 DurationFieldType.seconds(), DurationFieldType.millis(), 340 }, 341 new int[] { -1, -1, -1, -1, 0, 1, 2, 3, } 342 ); 343 cTime = type; 344 } 345 return type; 346 } 347 348 /** 349 * Gets a type that defines just the years field. 350 * 351 * @return the period type 352 */ 353 public static PeriodType years() { 354 PeriodType type = cYears; 355 if (type == null) { 356 type = new PeriodType( 357 "Years", 358 new DurationFieldType[] { DurationFieldType.years() }, 359 new int[] { 0, -1, -1, -1, -1, -1, -1, -1, } 360 ); 361 cYears = type; 362 } 363 return type; 364 } 365 366 /** 367 * Gets a type that defines just the months field. 368 * 369 * @return the period type 370 */ 371 public static PeriodType months() { 372 PeriodType type = cMonths; 373 if (type == null) { 374 type = new PeriodType( 375 "Months", 376 new DurationFieldType[] { DurationFieldType.months() }, 377 new int[] { -1, 0, -1, -1, -1, -1, -1, -1, } 378 ); 379 cMonths = type; 380 } 381 return type; 382 } 383 384 /** 385 * Gets a type that defines just the weeks field. 386 * 387 * @return the period type 388 */ 389 public static PeriodType weeks() { 390 PeriodType type = cWeeks; 391 if (type == null) { 392 type = new PeriodType( 393 "Weeks", 394 new DurationFieldType[] { DurationFieldType.weeks() }, 395 new int[] { -1, -1, 0, -1, -1, -1, -1, -1, } 396 ); 397 cWeeks = type; 398 } 399 return type; 400 } 401 402 /** 403 * Gets a type that defines just the days field. 404 * 405 * @return the period type 406 */ 407 public static PeriodType days() { 408 PeriodType type = cDays; 409 if (type == null) { 410 type = new PeriodType( 411 "Days", 412 new DurationFieldType[] { DurationFieldType.days() }, 413 new int[] { -1, -1, -1, 0, -1, -1, -1, -1, } 414 ); 415 cDays = type; 416 } 417 return type; 418 } 419 420 /** 421 * Gets a type that defines just the hours field. 422 * 423 * @return the period type 424 */ 425 public static PeriodType hours() { 426 PeriodType type = cHours; 427 if (type == null) { 428 type = new PeriodType( 429 "Hours", 430 new DurationFieldType[] { DurationFieldType.hours() }, 431 new int[] { -1, -1, -1, -1, 0, -1, -1, -1, } 432 ); 433 cHours = type; 434 } 435 return type; 436 } 437 438 /** 439 * Gets a type that defines just the minutes field. 440 * 441 * @return the period type 442 */ 443 public static PeriodType minutes() { 444 PeriodType type = cMinutes; 445 if (type == null) { 446 type = new PeriodType( 447 "Minutes", 448 new DurationFieldType[] { DurationFieldType.minutes() }, 449 new int[] { -1, -1, -1, -1, -1, 0, -1, -1, } 450 ); 451 cMinutes = type; 452 } 453 return type; 454 } 455 456 /** 457 * Gets a type that defines just the seconds field. 458 * 459 * @return the period type 460 */ 461 public static PeriodType seconds() { 462 PeriodType type = cSeconds; 463 if (type == null) { 464 type = new PeriodType( 465 "Seconds", 466 new DurationFieldType[] { DurationFieldType.seconds() }, 467 new int[] { -1, -1, -1, -1, -1, -1, 0, -1, } 468 ); 469 cSeconds = type; 470 } 471 return type; 472 } 473 474 /** 475 * Gets a type that defines just the millis field. 476 * 477 * @return the period type 478 */ 479 public static PeriodType millis() { 480 PeriodType type = cMillis; 481 if (type == null) { 482 type = new PeriodType( 483 "Millis", 484 new DurationFieldType[] { DurationFieldType.millis() }, 485 new int[] { -1, -1, -1, -1, -1, -1, -1, 0, } 486 ); 487 cMillis = type; 488 } 489 return type; 490 } 491 492 /** 493 * Gets a period type that contains the duration types of the array. 494 * <p> 495 * Only the 8 standard duration field types are supported. 496 * 497 * @param types the types to include in the array. 498 * @return the period type 499 * @since 1.1 500 */ 501 public static synchronized PeriodType forFields(DurationFieldType[] types) { 502 if (types == null || types.length == 0) { 503 throw new IllegalArgumentException("Types array must not be null or empty"); 504 } 505 for (int i = 0; i < types.length; i++) { 506 if (types[i] == null) { 507 throw new IllegalArgumentException("Types array must not contain null"); 508 } 509 } 510 Map<PeriodType, Object> cache = cTypes; 511 if (cache.isEmpty()) { 512 cache.put(standard(), standard()); 513 cache.put(yearMonthDayTime(), yearMonthDayTime()); 514 cache.put(yearMonthDay(), yearMonthDay()); 515 cache.put(yearWeekDayTime(), yearWeekDayTime()); 516 cache.put(yearWeekDay(), yearWeekDay()); 517 cache.put(yearDayTime(), yearDayTime()); 518 cache.put(yearDay(), yearDay()); 519 cache.put(dayTime(), dayTime()); 520 cache.put(time(), time()); 521 cache.put(years(), years()); 522 cache.put(months(), months()); 523 cache.put(weeks(), weeks()); 524 cache.put(days(), days()); 525 cache.put(hours(), hours()); 526 cache.put(minutes(), minutes()); 527 cache.put(seconds(), seconds()); 528 cache.put(millis(), millis()); 529 } 530 PeriodType inPartType = new PeriodType(null, types, null); 531 Object cached = cache.get(inPartType); 532 if (cached instanceof PeriodType) { 533 return (PeriodType) cached; 534 } 535 if (cached != null) { 536 throw new IllegalArgumentException("PeriodType does not support fields: " + cached); 537 } 538 PeriodType type = standard(); 539 List<DurationFieldType> list = new ArrayList<DurationFieldType>(Arrays.asList(types)); 540 if (list.remove(DurationFieldType.years()) == false) { 541 type = type.withYearsRemoved(); 542 } 543 if (list.remove(DurationFieldType.months()) == false) { 544 type = type.withMonthsRemoved(); 545 } 546 if (list.remove(DurationFieldType.weeks()) == false) { 547 type = type.withWeeksRemoved(); 548 } 549 if (list.remove(DurationFieldType.days()) == false) { 550 type = type.withDaysRemoved(); 551 } 552 if (list.remove(DurationFieldType.hours()) == false) { 553 type = type.withHoursRemoved(); 554 } 555 if (list.remove(DurationFieldType.minutes()) == false) { 556 type = type.withMinutesRemoved(); 557 } 558 if (list.remove(DurationFieldType.seconds()) == false) { 559 type = type.withSecondsRemoved(); 560 } 561 if (list.remove(DurationFieldType.millis()) == false) { 562 type = type.withMillisRemoved(); 563 } 564 if (list.size() > 0) { 565 cache.put(inPartType, list); 566 throw new IllegalArgumentException("PeriodType does not support fields: " + list); 567 } 568 // recheck cache in case initial array order was wrong 569 PeriodType checkPartType = new PeriodType(null, type.iTypes, null); 570 PeriodType checkedType = (PeriodType) cache.get(checkPartType); 571 if (checkedType != null) { 572 cache.put(checkPartType, checkedType); 573 return checkedType; 574 } 575 cache.put(checkPartType, type); 576 return type; 577 } 578 579 //----------------------------------------------------------------------- 580 /** The name of the type */ 581 private final String iName; 582 /** The array of types */ 583 private final DurationFieldType[] iTypes; 584 /** The array of indices */ 585 private final int[] iIndices; 586 587 /** 588 * Constructor. 589 * 590 * @param name the name 591 * @param types the types 592 * @param indices the indices 593 */ 594 protected PeriodType(String name, DurationFieldType[] types, int[] indices) { 595 super(); 596 iName = name; 597 iTypes = types; 598 iIndices = indices; 599 } 600 601 //----------------------------------------------------------------------- 602 /** 603 * Gets the name of the period type. 604 * 605 * @return the name 606 */ 607 public String getName() { 608 return iName; 609 } 610 611 /** 612 * Gets the number of fields in the period type. 613 * 614 * @return the number of fields 615 */ 616 public int size() { 617 return iTypes.length; 618 } 619 620 /** 621 * Gets the field type by index. 622 * 623 * @param index the index to retrieve 624 * @return the field type 625 * @throws IndexOutOfBoundsException if the index is invalid 626 */ 627 public DurationFieldType getFieldType(int index) { 628 return iTypes[index]; 629 } 630 631 /** 632 * Checks whether the field specified is supported by this period. 633 * 634 * @param type the type to check, may be null which returns false 635 * @return true if the field is supported 636 */ 637 public boolean isSupported(DurationFieldType type) { 638 return (indexOf(type) >= 0); 639 } 640 641 /** 642 * Gets the index of the field in this period. 643 * 644 * @param type the type to check, may be null which returns -1 645 * @return the index of -1 if not supported 646 */ 647 public int indexOf(DurationFieldType type) { 648 for (int i = 0, isize = size(); i < isize; i++) { 649 if (iTypes[i] == type) { 650 return i; 651 } 652 } 653 return -1; 654 } 655 656 /** 657 * Gets a debugging to string. 658 * 659 * @return a string 660 */ 661 public String toString() { 662 return "PeriodType[" + getName() + "]"; 663 } 664 665 //----------------------------------------------------------------------- 666 /** 667 * Gets the indexed field part of the period. 668 * 669 * @param period the period to query 670 * @param index the index to use 671 * @return the value of the field, zero if unsupported 672 */ 673 int getIndexedField(ReadablePeriod period, int index) { 674 int realIndex = iIndices[index]; 675 return (realIndex == -1 ? 0 : period.getValue(realIndex)); 676 } 677 678 /** 679 * Sets the indexed field part of the period. 680 * 681 * @param period the period to query 682 * @param index the index to use 683 * @param values the array to populate 684 * @param newValue the value to set 685 * @throws UnsupportedOperationException if not supported 686 */ 687 boolean setIndexedField(ReadablePeriod period, int index, int[] values, int newValue) { 688 int realIndex = iIndices[index]; 689 if (realIndex == -1) { 690 throw new UnsupportedOperationException("Field is not supported"); 691 } 692 values[realIndex] = newValue; 693 return true; 694 } 695 696 /** 697 * Adds to the indexed field part of the period. 698 * 699 * @param period the period to query 700 * @param index the index to use 701 * @param values the array to populate 702 * @param valueToAdd the value to add 703 * @return true if the array is updated 704 * @throws UnsupportedOperationException if not supported 705 */ 706 boolean addIndexedField(ReadablePeriod period, int index, int[] values, int valueToAdd) { 707 if (valueToAdd == 0) { 708 return false; 709 } 710 int realIndex = iIndices[index]; 711 if (realIndex == -1) { 712 throw new UnsupportedOperationException("Field is not supported"); 713 } 714 values[realIndex] = FieldUtils.safeAdd(values[realIndex], valueToAdd); 715 return true; 716 } 717 718 //----------------------------------------------------------------------- 719 /** 720 * Returns a version of this PeriodType instance that does not support years. 721 * 722 * @return a new period type that supports the original set of fields except years 723 */ 724 public PeriodType withYearsRemoved() { 725 return withFieldRemoved(0, "NoYears"); 726 } 727 728 /** 729 * Returns a version of this PeriodType instance that does not support months. 730 * 731 * @return a new period type that supports the original set of fields except months 732 */ 733 public PeriodType withMonthsRemoved() { 734 return withFieldRemoved(1, "NoMonths"); 735 } 736 737 /** 738 * Returns a version of this PeriodType instance that does not support weeks. 739 * 740 * @return a new period type that supports the original set of fields except weeks 741 */ 742 public PeriodType withWeeksRemoved() { 743 return withFieldRemoved(2, "NoWeeks"); 744 } 745 746 /** 747 * Returns a version of this PeriodType instance that does not support days. 748 * 749 * @return a new period type that supports the original set of fields except days 750 */ 751 public PeriodType withDaysRemoved() { 752 return withFieldRemoved(3, "NoDays"); 753 } 754 755 /** 756 * Returns a version of this PeriodType instance that does not support hours. 757 * 758 * @return a new period type that supports the original set of fields except hours 759 */ 760 public PeriodType withHoursRemoved() { 761 return withFieldRemoved(4, "NoHours"); 762 } 763 764 /** 765 * Returns a version of this PeriodType instance that does not support minutes. 766 * 767 * @return a new period type that supports the original set of fields except minutes 768 */ 769 public PeriodType withMinutesRemoved() { 770 return withFieldRemoved(5, "NoMinutes"); 771 } 772 773 /** 774 * Returns a version of this PeriodType instance that does not support seconds. 775 * 776 * @return a new period type that supports the original set of fields except seconds 777 */ 778 public PeriodType withSecondsRemoved() { 779 return withFieldRemoved(6, "NoSeconds"); 780 } 781 782 /** 783 * Returns a version of this PeriodType instance that does not support milliseconds. 784 * 785 * @return a new period type that supports the original set of fields except milliseconds 786 */ 787 public PeriodType withMillisRemoved() { 788 return withFieldRemoved(7, "NoMillis"); 789 } 790 791 /** 792 * Removes the field specified by indices index. 793 * 794 * @param indicesIndex the index to remove 795 * @param name the name addition 796 * @return the new type 797 */ 798 private PeriodType withFieldRemoved(int indicesIndex, String name) { 799 int fieldIndex = iIndices[indicesIndex]; 800 if (fieldIndex == -1) { 801 return this; 802 } 803 804 DurationFieldType[] types = new DurationFieldType[size() - 1]; 805 for (int i = 0; i < iTypes.length; i++) { 806 if (i < fieldIndex) { 807 types[i] = iTypes[i]; 808 } else if (i > fieldIndex) { 809 types[i - 1] = iTypes[i]; 810 } 811 } 812 813 int[] indices = new int[8]; 814 for (int i = 0; i < indices.length; i++) { 815 if (i < indicesIndex) { 816 indices[i] = iIndices[i]; 817 } else if (i > indicesIndex) { 818 indices[i] = (iIndices[i] == -1 ? -1 : iIndices[i] - 1); 819 } else { 820 indices[i] = -1; 821 } 822 } 823 return new PeriodType(getName() + name, types, indices); 824 } 825 826 //----------------------------------------------------------------------- 827 /** 828 * Compares this type to another object. 829 * To be equal, the object must be a PeriodType with the same set of fields. 830 * 831 * @param obj the object to compare to 832 * @return true if equal 833 */ 834 public boolean equals(Object obj) { 835 if (this == obj) { 836 return true; 837 } 838 if (obj instanceof PeriodType == false) { 839 return false; 840 } 841 PeriodType other = (PeriodType) obj; 842 return (Arrays.equals(iTypes, other.iTypes)); 843 } 844 845 /** 846 * Returns a hashcode based on the field types. 847 * 848 * @return a suitable hashcode 849 */ 850 public int hashCode() { 851 int hash = 0; 852 for (int i = 0; i < iTypes.length; i++) { 853 hash += iTypes[i].hashCode(); 854 } 855 return hash; 856 } 857 858 }