001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.openwire; 018 019import java.io.DataInput; 020import java.io.DataOutput; 021import java.io.IOException; 022import java.lang.reflect.Method; 023import java.util.HashMap; 024import java.util.Map; 025 026import org.apache.activemq.command.CommandTypes; 027import org.apache.activemq.command.DataStructure; 028import org.apache.activemq.command.WireFormatInfo; 029import org.apache.activemq.util.ByteSequence; 030import org.apache.activemq.util.ByteSequenceData; 031import org.apache.activemq.util.DataByteArrayInputStream; 032import org.apache.activemq.util.DataByteArrayOutputStream; 033import org.apache.activemq.util.IOExceptionSupport; 034import org.apache.activemq.wireformat.WireFormat; 035 036/** 037 * 038 * 039 */ 040public final class OpenWireFormat implements WireFormat { 041 042 public static final int DEFAULT_STORE_VERSION = CommandTypes.PROTOCOL_STORE_VERSION; 043 public static final int DEFAULT_WIRE_VERSION = CommandTypes.PROTOCOL_VERSION; 044 public static final int DEFAULT_LEGACY_VERSION = CommandTypes.PROTOCOL_LEGACY_STORE_VERSION; 045 public static final long DEFAULT_MAX_FRAME_SIZE = Long.MAX_VALUE; 046 047 static final byte NULL_TYPE = CommandTypes.NULL; 048 private static final int MARSHAL_CACHE_SIZE = Short.MAX_VALUE / 2; 049 private static final int MARSHAL_CACHE_FREE_SPACE = 100; 050 051 private DataStreamMarshaller dataMarshallers[]; 052 private int version; 053 private boolean stackTraceEnabled; 054 private boolean tcpNoDelayEnabled; 055 private boolean cacheEnabled; 056 private boolean tightEncodingEnabled; 057 private boolean sizePrefixDisabled; 058 private boolean maxFrameSizeEnabled = true; 059 private long maxFrameSize = DEFAULT_MAX_FRAME_SIZE; 060 061 // The following fields are used for value caching 062 private short nextMarshallCacheIndex; 063 private short nextMarshallCacheEvictionIndex; 064 private Map<DataStructure, Short> marshallCacheMap = new HashMap<DataStructure, Short>(); 065 private DataStructure marshallCache[] = null; 066 private DataStructure unmarshallCache[] = null; 067 private DataByteArrayOutputStream bytesOut = new DataByteArrayOutputStream(); 068 private DataByteArrayInputStream bytesIn = new DataByteArrayInputStream(); 069 private WireFormatInfo preferedWireFormatInfo; 070 071 public OpenWireFormat() { 072 this(DEFAULT_STORE_VERSION); 073 } 074 075 public OpenWireFormat(int i) { 076 setVersion(i); 077 } 078 079 @Override 080 public int hashCode() { 081 return version ^ (cacheEnabled ? 0x10000000 : 0x20000000) 082 ^ (stackTraceEnabled ? 0x01000000 : 0x02000000) 083 ^ (tightEncodingEnabled ? 0x00100000 : 0x00200000) 084 ^ (sizePrefixDisabled ? 0x00010000 : 0x00020000) 085 ^ (maxFrameSizeEnabled ? 0x00010000 : 0x00020000); 086 } 087 088 public OpenWireFormat copy() { 089 OpenWireFormat answer = new OpenWireFormat(version); 090 answer.stackTraceEnabled = stackTraceEnabled; 091 answer.tcpNoDelayEnabled = tcpNoDelayEnabled; 092 answer.cacheEnabled = cacheEnabled; 093 answer.tightEncodingEnabled = tightEncodingEnabled; 094 answer.sizePrefixDisabled = sizePrefixDisabled; 095 answer.preferedWireFormatInfo = preferedWireFormatInfo; 096 answer.maxFrameSizeEnabled = maxFrameSizeEnabled; 097 return answer; 098 } 099 100 @Override 101 public boolean equals(Object object) { 102 if (object == null) { 103 return false; 104 } 105 OpenWireFormat o = (OpenWireFormat)object; 106 return o.stackTraceEnabled == stackTraceEnabled && o.cacheEnabled == cacheEnabled 107 && o.version == version && o.tightEncodingEnabled == tightEncodingEnabled 108 && o.sizePrefixDisabled == sizePrefixDisabled 109 && o.maxFrameSizeEnabled == maxFrameSizeEnabled; 110 } 111 112 113 @Override 114 public String toString() { 115 return "OpenWireFormat{version=" + version + ", cacheEnabled=" + cacheEnabled + ", stackTraceEnabled=" + stackTraceEnabled + ", tightEncodingEnabled=" 116 + tightEncodingEnabled + ", sizePrefixDisabled=" + sizePrefixDisabled + ", maxFrameSize=" + maxFrameSize + ", maxFrameSizeEnabled=" + maxFrameSizeEnabled + "}"; 117 // return "OpenWireFormat{id="+id+", 118 // tightEncodingEnabled="+tightEncodingEnabled+"}"; 119 } 120 121 @Override 122 public int getVersion() { 123 return version; 124 } 125 126 @Override 127 public synchronized ByteSequence marshal(Object command) throws IOException { 128 129 if (cacheEnabled) { 130 runMarshallCacheEvictionSweep(); 131 } 132 133 ByteSequence sequence = null; 134 int size = 1; 135 if (command != null) { 136 137 DataStructure c = (DataStructure)command; 138 byte type = c.getDataStructureType(); 139 DataStreamMarshaller dsm = dataMarshallers[type & 0xFF]; 140 if (dsm == null) { 141 throw new IOException("Unknown data type: " + type); 142 } 143 if (tightEncodingEnabled) { 144 145 BooleanStream bs = new BooleanStream(); 146 size += dsm.tightMarshal1(this, c, bs); 147 size += bs.marshalledSize(); 148 149 if(maxFrameSizeEnabled && size > maxFrameSize) { 150 throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize); 151 } 152 153 bytesOut.restart(size); 154 if (!sizePrefixDisabled) { 155 bytesOut.writeInt(size); 156 } 157 bytesOut.writeByte(type); 158 bs.marshal(bytesOut); 159 dsm.tightMarshal2(this, c, bytesOut, bs); 160 sequence = bytesOut.toByteSequence(); 161 162 } else { 163 bytesOut.restart(); 164 if (!sizePrefixDisabled) { 165 bytesOut.writeInt(0); // we don't know the final size 166 // yet but write this here for 167 // now. 168 } 169 bytesOut.writeByte(type); 170 dsm.looseMarshal(this, c, bytesOut); 171 sequence = bytesOut.toByteSequence(); 172 173 if (!sizePrefixDisabled) { 174 size = sequence.getLength() - 4; 175 int pos = sequence.offset; 176 ByteSequenceData.writeIntBig(sequence, size); 177 sequence.offset = pos; 178 } 179 } 180 181 } else { 182 bytesOut.restart(5); 183 bytesOut.writeInt(size); 184 bytesOut.writeByte(NULL_TYPE); 185 sequence = bytesOut.toByteSequence(); 186 } 187 188 return sequence; 189 } 190 191 @Override 192 public synchronized Object unmarshal(ByteSequence sequence) throws IOException { 193 bytesIn.restart(sequence); 194 // DataInputStream dis = new DataInputStream(new 195 // ByteArrayInputStream(sequence)); 196 197 if (!sizePrefixDisabled) { 198 int size = bytesIn.readInt(); 199 if (sequence.getLength() - 4 != size) { 200 // throw new IOException("Packet size does not match marshaled 201 // size"); 202 } 203 204 if (maxFrameSizeEnabled && size > maxFrameSize) { 205 throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize); 206 } 207 } 208 209 Object command = doUnmarshal(bytesIn); 210 // if( !cacheEnabled && ((DataStructure)command).isMarshallAware() ) { 211 // ((MarshallAware) command).setCachedMarshalledForm(this, sequence); 212 // } 213 return command; 214 } 215 216 @Override 217 public synchronized void marshal(Object o, DataOutput dataOut) throws IOException { 218 219 if (cacheEnabled) { 220 runMarshallCacheEvictionSweep(); 221 } 222 223 int size = 1; 224 if (o != null) { 225 226 DataStructure c = (DataStructure)o; 227 byte type = c.getDataStructureType(); 228 DataStreamMarshaller dsm = dataMarshallers[type & 0xFF]; 229 if (dsm == null) { 230 throw new IOException("Unknown data type: " + type); 231 } 232 if (tightEncodingEnabled) { 233 BooleanStream bs = new BooleanStream(); 234 size += dsm.tightMarshal1(this, c, bs); 235 size += bs.marshalledSize(); 236 237 if(maxFrameSizeEnabled && size > maxFrameSize) { 238 throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize); 239 } 240 241 if (!sizePrefixDisabled) { 242 dataOut.writeInt(size); 243 } 244 245 dataOut.writeByte(type); 246 bs.marshal(dataOut); 247 dsm.tightMarshal2(this, c, dataOut, bs); 248 249 } else { 250 DataOutput looseOut = dataOut; 251 252 if (!sizePrefixDisabled) { 253 bytesOut.restart(); 254 looseOut = bytesOut; 255 } 256 257 looseOut.writeByte(type); 258 dsm.looseMarshal(this, c, looseOut); 259 260 if (!sizePrefixDisabled) { 261 ByteSequence sequence = bytesOut.toByteSequence(); 262 dataOut.writeInt(sequence.getLength()); 263 dataOut.write(sequence.getData(), sequence.getOffset(), sequence.getLength()); 264 } 265 266 } 267 268 } else { 269 if (!sizePrefixDisabled) { 270 dataOut.writeInt(size); 271 } 272 dataOut.writeByte(NULL_TYPE); 273 } 274 } 275 276 @Override 277 public Object unmarshal(DataInput dis) throws IOException { 278 DataInput dataIn = dis; 279 if (!sizePrefixDisabled) { 280 int size = dis.readInt(); 281 if (maxFrameSizeEnabled && size > maxFrameSize) { 282 throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize); 283 } 284 // int size = dis.readInt(); 285 // byte[] data = new byte[size]; 286 // dis.readFully(data); 287 // bytesIn.restart(data); 288 // dataIn = bytesIn; 289 } 290 return doUnmarshal(dataIn); 291 } 292 293 /** 294 * Used by NIO or AIO transports 295 */ 296 public int tightMarshal1(Object o, BooleanStream bs) throws IOException { 297 int size = 1; 298 if (o != null) { 299 DataStructure c = (DataStructure)o; 300 byte type = c.getDataStructureType(); 301 DataStreamMarshaller dsm = dataMarshallers[type & 0xFF]; 302 if (dsm == null) { 303 throw new IOException("Unknown data type: " + type); 304 } 305 306 size += dsm.tightMarshal1(this, c, bs); 307 size += bs.marshalledSize(); 308 } 309 return size; 310 } 311 312 /** 313 * Used by NIO or AIO transports; note that the size is not written as part 314 * of this method. 315 */ 316 public void tightMarshal2(Object o, DataOutput ds, BooleanStream bs) throws IOException { 317 if (cacheEnabled) { 318 runMarshallCacheEvictionSweep(); 319 } 320 321 if (o != null) { 322 DataStructure c = (DataStructure)o; 323 byte type = c.getDataStructureType(); 324 DataStreamMarshaller dsm = dataMarshallers[type & 0xFF]; 325 if (dsm == null) { 326 throw new IOException("Unknown data type: " + type); 327 } 328 ds.writeByte(type); 329 bs.marshal(ds); 330 dsm.tightMarshal2(this, c, ds, bs); 331 } 332 } 333 334 /** 335 * Allows you to dynamically switch the version of the openwire protocol 336 * being used. 337 * 338 * @param version 339 */ 340 @Override 341 public void setVersion(int version) { 342 String mfName = "org.apache.activemq.openwire.v" + version + ".MarshallerFactory"; 343 Class mfClass; 344 try { 345 mfClass = Class.forName(mfName, false, getClass().getClassLoader()); 346 } catch (ClassNotFoundException e) { 347 throw (IllegalArgumentException)new IllegalArgumentException("Invalid version: " + version 348 + ", could not load " + mfName) 349 .initCause(e); 350 } 351 try { 352 Method method = mfClass.getMethod("createMarshallerMap", new Class[] {OpenWireFormat.class}); 353 dataMarshallers = (DataStreamMarshaller[])method.invoke(null, new Object[] {this}); 354 } catch (Throwable e) { 355 throw (IllegalArgumentException)new IllegalArgumentException( 356 "Invalid version: " 357 + version 358 + ", " 359 + mfName 360 + " does not properly implement the createMarshallerMap method.") 361 .initCause(e); 362 } 363 this.version = version; 364 } 365 366 public Object doUnmarshal(DataInput dis) throws IOException { 367 byte dataType = dis.readByte(); 368 if (dataType != NULL_TYPE) { 369 DataStreamMarshaller dsm = dataMarshallers[dataType & 0xFF]; 370 if (dsm == null) { 371 throw new IOException("Unknown data type: " + dataType); 372 } 373 Object data = dsm.createObject(); 374 if (this.tightEncodingEnabled) { 375 BooleanStream bs = new BooleanStream(); 376 bs.unmarshal(dis); 377 dsm.tightUnmarshal(this, data, dis, bs); 378 } else { 379 dsm.looseUnmarshal(this, data, dis); 380 } 381 return data; 382 } else { 383 return null; 384 } 385 } 386 387 // public void debug(String msg) { 388 // String t = (Thread.currentThread().getName()+" ").substring(0, 40); 389 // System.out.println(t+": "+msg); 390 // } 391 public int tightMarshalNestedObject1(DataStructure o, BooleanStream bs) throws IOException { 392 bs.writeBoolean(o != null); 393 if (o == null) { 394 return 0; 395 } 396 397 if (o.isMarshallAware()) { 398 // MarshallAware ma = (MarshallAware)o; 399 ByteSequence sequence = null; 400 // sequence=ma.getCachedMarshalledForm(this); 401 bs.writeBoolean(sequence != null); 402 if (sequence != null) { 403 return 1 + sequence.getLength(); 404 } 405 } 406 407 byte type = o.getDataStructureType(); 408 DataStreamMarshaller dsm = dataMarshallers[type & 0xFF]; 409 if (dsm == null) { 410 throw new IOException("Unknown data type: " + type); 411 } 412 return 1 + dsm.tightMarshal1(this, o, bs); 413 } 414 415 public void tightMarshalNestedObject2(DataStructure o, DataOutput ds, BooleanStream bs) 416 throws IOException { 417 if (!bs.readBoolean()) { 418 return; 419 } 420 421 byte type = o.getDataStructureType(); 422 ds.writeByte(type); 423 424 if (o.isMarshallAware() && bs.readBoolean()) { 425 426 // We should not be doing any caching 427 throw new IOException("Corrupted stream"); 428 // MarshallAware ma = (MarshallAware) o; 429 // ByteSequence sequence=ma.getCachedMarshalledForm(this); 430 // ds.write(sequence.getData(), sequence.getOffset(), 431 // sequence.getLength()); 432 433 } else { 434 435 DataStreamMarshaller dsm = dataMarshallers[type & 0xFF]; 436 if (dsm == null) { 437 throw new IOException("Unknown data type: " + type); 438 } 439 dsm.tightMarshal2(this, o, ds, bs); 440 441 } 442 } 443 444 public DataStructure tightUnmarshalNestedObject(DataInput dis, BooleanStream bs) throws IOException { 445 if (bs.readBoolean()) { 446 447 byte dataType = dis.readByte(); 448 DataStreamMarshaller dsm = dataMarshallers[dataType & 0xFF]; 449 if (dsm == null) { 450 throw new IOException("Unknown data type: " + dataType); 451 } 452 DataStructure data = dsm.createObject(); 453 454 if (data.isMarshallAware() && bs.readBoolean()) { 455 456 dis.readInt(); 457 dis.readByte(); 458 459 BooleanStream bs2 = new BooleanStream(); 460 bs2.unmarshal(dis); 461 dsm.tightUnmarshal(this, data, dis, bs2); 462 463 // TODO: extract the sequence from the dis and associate it. 464 // MarshallAware ma = (MarshallAware)data 465 // ma.setCachedMarshalledForm(this, sequence); 466 467 } else { 468 dsm.tightUnmarshal(this, data, dis, bs); 469 } 470 471 return data; 472 } else { 473 return null; 474 } 475 } 476 477 public DataStructure looseUnmarshalNestedObject(DataInput dis) throws IOException { 478 if (dis.readBoolean()) { 479 480 byte dataType = dis.readByte(); 481 DataStreamMarshaller dsm = dataMarshallers[dataType & 0xFF]; 482 if (dsm == null) { 483 throw new IOException("Unknown data type: " + dataType); 484 } 485 DataStructure data = dsm.createObject(); 486 dsm.looseUnmarshal(this, data, dis); 487 return data; 488 489 } else { 490 return null; 491 } 492 } 493 494 public void looseMarshalNestedObject(DataStructure o, DataOutput dataOut) throws IOException { 495 dataOut.writeBoolean(o != null); 496 if (o != null) { 497 byte type = o.getDataStructureType(); 498 dataOut.writeByte(type); 499 DataStreamMarshaller dsm = dataMarshallers[type & 0xFF]; 500 if (dsm == null) { 501 throw new IOException("Unknown data type: " + type); 502 } 503 dsm.looseMarshal(this, o, dataOut); 504 } 505 } 506 507 public void runMarshallCacheEvictionSweep() { 508 // Do we need to start evicting?? 509 while (marshallCacheMap.size() > marshallCache.length - MARSHAL_CACHE_FREE_SPACE) { 510 511 marshallCacheMap.remove(marshallCache[nextMarshallCacheEvictionIndex]); 512 marshallCache[nextMarshallCacheEvictionIndex] = null; 513 514 nextMarshallCacheEvictionIndex++; 515 if (nextMarshallCacheEvictionIndex >= marshallCache.length) { 516 nextMarshallCacheEvictionIndex = 0; 517 } 518 519 } 520 } 521 522 public Short getMarshallCacheIndex(DataStructure o) { 523 return marshallCacheMap.get(o); 524 } 525 526 public Short addToMarshallCache(DataStructure o) { 527 short i = nextMarshallCacheIndex++; 528 if (nextMarshallCacheIndex >= marshallCache.length) { 529 nextMarshallCacheIndex = 0; 530 } 531 532 // We can only cache that item if there is space left. 533 if (marshallCacheMap.size() < marshallCache.length) { 534 marshallCache[i] = o; 535 Short index = Short.valueOf(i); 536 marshallCacheMap.put(o, index); 537 return index; 538 } else { 539 // Use -1 to indicate that the value was not cached due to cache 540 // being full. 541 return Short.valueOf((short)-1); 542 } 543 } 544 545 public void setInUnmarshallCache(short index, DataStructure o) { 546 547 // There was no space left in the cache, so we can't 548 // put this in the cache. 549 if (index == -1) { 550 return; 551 } 552 553 unmarshallCache[index] = o; 554 } 555 556 public DataStructure getFromUnmarshallCache(short index) { 557 return unmarshallCache[index]; 558 } 559 560 public void setStackTraceEnabled(boolean b) { 561 stackTraceEnabled = b; 562 } 563 564 public boolean isStackTraceEnabled() { 565 return stackTraceEnabled; 566 } 567 568 public boolean isTcpNoDelayEnabled() { 569 return tcpNoDelayEnabled; 570 } 571 572 public void setTcpNoDelayEnabled(boolean tcpNoDelayEnabled) { 573 this.tcpNoDelayEnabled = tcpNoDelayEnabled; 574 } 575 576 public boolean isCacheEnabled() { 577 return cacheEnabled; 578 } 579 580 public void setCacheEnabled(boolean cacheEnabled) { 581 if(cacheEnabled){ 582 marshallCache = new DataStructure[MARSHAL_CACHE_SIZE]; 583 unmarshallCache = new DataStructure[MARSHAL_CACHE_SIZE]; 584 } 585 this.cacheEnabled = cacheEnabled; 586 } 587 588 public boolean isTightEncodingEnabled() { 589 return tightEncodingEnabled; 590 } 591 592 public void setTightEncodingEnabled(boolean tightEncodingEnabled) { 593 this.tightEncodingEnabled = tightEncodingEnabled; 594 } 595 596 public boolean isSizePrefixDisabled() { 597 return sizePrefixDisabled; 598 } 599 600 public void setSizePrefixDisabled(boolean prefixPacketSize) { 601 this.sizePrefixDisabled = prefixPacketSize; 602 } 603 604 public void setPreferedWireFormatInfo(WireFormatInfo info) { 605 this.preferedWireFormatInfo = info; 606 } 607 608 public WireFormatInfo getPreferedWireFormatInfo() { 609 return preferedWireFormatInfo; 610 } 611 612 public long getMaxFrameSize() { 613 return maxFrameSize; 614 } 615 616 public void setMaxFrameSize(long maxFrameSize) { 617 this.maxFrameSize = maxFrameSize; 618 } 619 620 public boolean isMaxFrameSizeEnabled() { 621 return maxFrameSizeEnabled; 622 } 623 624 /** 625 * Set whether the maxFrameSize check will be enabled. Note this is only applied to this format 626 * and will NOT be negotiated 627 * 628 * @param maxFrameSizeEnabled 629 */ 630 public void setMaxFrameSizeEnabled(boolean maxFrameSizeEnabled) { 631 this.maxFrameSizeEnabled = maxFrameSizeEnabled; 632 } 633 634 public void renegotiateWireFormat(WireFormatInfo info) throws IOException { 635 636 if (preferedWireFormatInfo == null) { 637 throw new IllegalStateException("Wireformat cannot not be renegotiated."); 638 } 639 640 this.setVersion(min(preferedWireFormatInfo.getVersion(), info.getVersion())); 641 info.setVersion(this.getVersion()); 642 643 this.setMaxFrameSize(min(preferedWireFormatInfo.getMaxFrameSize(), info.getMaxFrameSize())); 644 info.setMaxFrameSize(this.getMaxFrameSize()); 645 //Note: Don't negotiate maxFrameSizeEnabled so the client and server can set independently 646 647 this.stackTraceEnabled = info.isStackTraceEnabled() && preferedWireFormatInfo.isStackTraceEnabled(); 648 info.setStackTraceEnabled(this.stackTraceEnabled); 649 650 this.tcpNoDelayEnabled = info.isTcpNoDelayEnabled() && preferedWireFormatInfo.isTcpNoDelayEnabled(); 651 info.setTcpNoDelayEnabled(this.tcpNoDelayEnabled); 652 653 this.cacheEnabled = info.isCacheEnabled() && preferedWireFormatInfo.isCacheEnabled(); 654 info.setCacheEnabled(this.cacheEnabled); 655 656 this.tightEncodingEnabled = info.isTightEncodingEnabled() 657 && preferedWireFormatInfo.isTightEncodingEnabled(); 658 info.setTightEncodingEnabled(this.tightEncodingEnabled); 659 660 this.sizePrefixDisabled = info.isSizePrefixDisabled() 661 && preferedWireFormatInfo.isSizePrefixDisabled(); 662 info.setSizePrefixDisabled(this.sizePrefixDisabled); 663 664 if (cacheEnabled) { 665 666 int size = Math.min(preferedWireFormatInfo.getCacheSize(), info.getCacheSize()); 667 info.setCacheSize(size); 668 669 if (size == 0) { 670 size = MARSHAL_CACHE_SIZE; 671 } 672 673 marshallCache = new DataStructure[size]; 674 unmarshallCache = new DataStructure[size]; 675 nextMarshallCacheIndex = 0; 676 nextMarshallCacheEvictionIndex = 0; 677 marshallCacheMap = new HashMap<DataStructure, Short>(); 678 } else { 679 marshallCache = null; 680 unmarshallCache = null; 681 nextMarshallCacheIndex = 0; 682 nextMarshallCacheEvictionIndex = 0; 683 marshallCacheMap = null; 684 } 685 686 } 687 688 protected int min(int version1, int version2) { 689 if (version1 < version2 && version1 > 0 || version2 <= 0) { 690 return version1; 691 } 692 return version2; 693 } 694 695 protected long min(long version1, long version2) { 696 if (version1 < version2 && version1 > 0 || version2 <= 0) { 697 return version1; 698 } 699 return version2; 700 } 701}