blob: 95931cc95982471383c109e9045366c8cb13e897 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.pdx.internal;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.geode.DataSerializable;
import org.apache.geode.DataSerializer;
import org.apache.geode.internal.InternalDataSerializer;
import org.apache.geode.internal.cache.tier.sockets.OldClientSupportService;
import org.apache.geode.internal.serialization.Version;
import org.apache.geode.pdx.PdxFieldAlreadyExistsException;
import org.apache.geode.pdx.internal.AutoSerializableManager.AutoClassInfo;
public class PdxType implements DataSerializable {
private static final long serialVersionUID = -1950047949756115279L;
private int cachedHash = 0;
private int typeId;
private String className;
private boolean noDomainClass;
/**
* Will be set to true if any fields on this type have been deleted.
*
* @since GemFire 8.1
*/
private boolean hasDeletedField;
/**
* A count of the total number of variable length field offsets.
*/
private int vlfCount;
private final ArrayList<PdxField> fields = new ArrayList<PdxField>();
private final transient Map<String, PdxField> fieldsMap = new HashMap<String, PdxField>();
private transient volatile SortedSet<PdxField> sortedIdentityFields;
public PdxType() {
// for deserialization
}
public PdxType(String name, boolean expectDomainClass) {
this.className = name;
this.noDomainClass = !expectDomainClass;
swizzleGemFireClassNames();
}
public PdxType(PdxType copy) {
this.typeId = copy.typeId;
this.className = copy.className;
this.noDomainClass = copy.noDomainClass;
this.vlfCount = copy.vlfCount;
for (PdxField ft : copy.fields) {
addField(ft);
}
}
private static final byte NO_DOMAIN_CLASS_BIT = 1;
private static final byte HAS_DELETED_FIELD_BIT = 2;
private void swizzleGemFireClassNames() {
OldClientSupportService svc = InternalDataSerializer.getOldClientSupportService();
if (svc != null) {
this.className = svc.processIncomingClassName(this.className);
}
}
@Override
public void fromData(DataInput in) throws IOException, ClassNotFoundException {
this.className = DataSerializer.readString(in);
swizzleGemFireClassNames();
{
byte bits = in.readByte();
this.noDomainClass = (bits & NO_DOMAIN_CLASS_BIT) != 0;
this.hasDeletedField = (bits & HAS_DELETED_FIELD_BIT) != 0;
}
this.typeId = in.readInt();
this.vlfCount = in.readInt();
int arrayLen = InternalDataSerializer.readArrayLength(in);
for (int i = 0; i < arrayLen; i++) {
PdxField vft = new PdxField();
vft.fromData(in);
addField(vft);
}
}
@Override
public void toData(DataOutput out) throws IOException {
DataSerializer.writeString(this.className, out);
{
// pre 8.1 we wrote a single boolean
// 8.1 and after we write a byte whose bits are:
// 1: noDomainClass
// 2: hasDeletedField
byte bits = 0;
if (this.noDomainClass) {
bits |= NO_DOMAIN_CLASS_BIT;
}
// Note that this code attempts to only set the HAS_DELETED_FIELD_BIT
// if serializing for 8.1 or later.
// But in some cases 8.1 serialized data may be sent to a pre 8.1 member.
// In that case if this bit is set it will cause the pre 8.1 member
// to set noDomainClass to true.
// For this reason the pdx delete-field command should only be used after
// all member have been upgraded to 8.1 or later.
Version sourceVersion = InternalDataSerializer.getVersionForDataStream(out);
if (sourceVersion.compareTo(Version.GFE_81) >= 0) {
if (this.hasDeletedField) {
bits |= HAS_DELETED_FIELD_BIT;
}
}
out.writeByte(bits);
}
out.writeInt(this.typeId);
out.writeInt(this.vlfCount);
InternalDataSerializer.writeArrayLength(this.fields.size(), out);
for (int i = 0; i < this.fields.size(); i++) {
PdxField vft = this.fields.get(i);
vft.toData(out);
}
}
@Override
public int hashCode() {
int hash = cachedHash;
if (hash == 0) {
hash = 1;
hash = hash * 31 + this.className.hashCode();
for (PdxField field : this.fields) {
hash = hash * 31 + field.hashCode();
}
if (hash == 0) {
hash = 1;
}
cachedHash = hash;
}
return cachedHash;
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other == null || !(other instanceof PdxType)) {
return false;
}
// Note: do not compare type id in equals
PdxType otherVT = (PdxType) other;
if (!(this.className.equals(otherVT.className))) {
return false;
}
if (this.noDomainClass != otherVT.noDomainClass) {
return false;
}
if (otherVT.fields.size() != this.fields.size() || otherVT.vlfCount != this.vlfCount) {
return false;
}
for (int i = 0; i < this.fields.size(); i++) {
if (!this.fields.get(i).equals(otherVT.fields.get(i))) {
return false;
}
}
return true;
}
/**
* Return true if two pdx types have same class name and the same fields but, unlike equals, field
* order does not matter. Note a type that expects a domain class can be compatible with one that
* does not expect a domain class.
*
* @param other the other pdx type
* @return true if two pdx types are compatible.
*/
public boolean compatible(PdxType other) {
if (other == null)
return false;
if (!getClassName().equals(other.getClassName())) {
return false;
}
Collection<PdxField> myFields = getSortedFields();
Collection<PdxField> otherFields = other.getSortedFields();
return myFields.equals(otherFields);
}
public int getVariableLengthFieldCount() {
return this.vlfCount;
}
public String getClassName() {
return this.className;
}
public boolean getNoDomainClass() {
return this.noDomainClass;
}
public void setNoDomainClass(boolean noDomainClass) {
this.noDomainClass = noDomainClass;
}
public int getTypeId() {
return this.typeId;
}
public int getDSId() {
return this.typeId >> 24 & 0xFF;
}
public int getTypeNum() {
return this.typeId & 0x00FFFFFF;
}
public void setTypeId(int tId) {
this.typeId = tId;
}
/*
* This method is use to create Pdxtype for which classname is not available; while creating
* PdxInstance
*/
public void setClassName(String className) {
this.className = className;
}
public void addField(PdxField ft) {
if (this.fieldsMap.put(ft.getFieldName(), ft) != null) {
throw new PdxFieldAlreadyExistsException(
"The field \"" + ft.getFieldName() + "\" already exists.");
}
this.fields.add(ft);
}
public void initialize(PdxWriterImpl writer) {
this.vlfCount = writer.getVlfCount();
int size = this.fields.size();
int fixedLenFieldOffset = 0;
boolean seenVariableLenType = false;
for (int i = 0; i < size; i++) {
PdxField vft = this.fields.get(i);
// System.out.println(i + ": " + vft);
if (vft.isVariableLengthType()) {
if (seenVariableLenType) {
vft.setVlfOffsetIndex(vft.getVarLenFieldSeqId());
} else {
vft.setRelativeOffset(fixedLenFieldOffset);
vft.setVlfOffsetIndex(-1);
}
seenVariableLenType = true;
} else if (seenVariableLenType) {
PdxField tmp = null;
int minusOffset = vft.getFieldType().getWidth();
for (int j = (i + 1); j < size; j++) {
tmp = this.fields.get(j);
if (tmp.isVariableLengthType()) {
break;
} else {
minusOffset += tmp.getFieldType().getWidth();
}
}
if (tmp != null && tmp.isVariableLengthType()) {
vft.setRelativeOffset(-minusOffset);
vft.setVlfOffsetIndex(tmp.getVarLenFieldSeqId());
} else {
vft.setRelativeOffset(-minusOffset);
vft.setVlfOffsetIndex(-1); // From the byte after the last field data byte
}
} else {
vft.setRelativeOffset(fixedLenFieldOffset);
fixedLenFieldOffset += vft.getFieldType().getWidth();
}
}
// no longer mark identity fields implicitly. Fixes bug 42976.
// System.out.println("Printing the position array:");
// for (int i = 0; i < this.positionArray.length; i++) {
// System.out.println("[" + i + "][0]=" + this.positionArray[i][0] + ", ["
// + i + "][1]=" + this.positionArray[i][1]);
// }
}
public PdxField getPdxField(String fieldName) {
PdxField result = this.fieldsMap.get(fieldName);
if (result != null && result.isDeleted()) {
result = null;
}
return result;
}
public List<PdxField> getFields() {
return Collections.unmodifiableList(this.fields);
}
public PdxField getPdxFieldByIndex(int index) {
return this.fields.get(index);
}
public int getFieldCount() {
return this.fields.size();
}
public int getUndeletedFieldCount() {
if (!getHasDeletedField()) {
return 0;
}
int result = this.fields.size();
for (PdxField f : this.fields) {
if (f.isDeleted()) {
result--;
}
}
return result;
}
public String toFormattedString() {
StringBuffer sb = new StringBuffer("PdxType[");
sb.append("dsid=").append(getDSId());
sb.append(", typenum=").append(getTypeNum());
sb.append("\n name=").append(this.className);
sb.append("\n fields=[");
for (PdxField vft : fields) {
sb.append("\n ");
sb.append(/* vft.getFieldName() + ":" + vft.getTypeId() */ vft.toString());
}
sb.append("]]");
return sb.toString();
}
public String toString() {
StringBuffer sb = new StringBuffer("PdxType[");
sb.append("dsid=").append(getDSId());
sb.append(",typenum=").append(getTypeNum());
sb.append(",name=").append(this.className);
sb.append(",fields=[");
for (PdxField vft : fields) {
sb.append(/* vft.getFieldName() + ":" + vft.getTypeId() */ vft.toString()).append(", ");
}
sb.append("]]");
return sb.toString();
}
/**
*
* @param readFields the fields that have been read
* @return a List of fields that have not been read (may be empty).
*/
public List<Integer> getUnreadFieldIndexes(List<String> readFields) {
ArrayList<Integer> result = new ArrayList<Integer>();
for (PdxField ft : this.fields) {
if (!ft.isDeleted() && !readFields.contains(ft.getFieldName())) {
result.add(ft.getFieldIndex());
}
}
return result;
}
/**
* Return true if the this type has a field that the other type does not have.
*
* @param other the type we are comparing to
* @return true if the this type has a field that the other type does not have.
*/
public boolean hasExtraFields(PdxType other) {
for (PdxField ft : this.fields) {
if (!ft.isDeleted() && other.getPdxField(ft.getFieldName()) == null) {
return true;
}
}
return false;
}
// Result does not include deleted fields
public SortedSet<PdxField> getSortedIdentityFields() {
if (this.sortedIdentityFields == null) {
TreeSet<PdxField> sortedSet = new TreeSet<PdxField>();
for (PdxField field : fields) {
if (field.isIdentityField() && !field.isDeleted()) {
sortedSet.add(field);
}
}
// If we don't find any marked identity fields, use all of the fields.
if (sortedSet.isEmpty()) {
for (PdxField field : fields) {
if (!field.isDeleted()) {
sortedSet.add(field);
}
}
}
this.sortedIdentityFields = sortedSet;
}
return this.sortedIdentityFields;
}
// Result does not include deleted fields
public Collection<PdxField> getSortedFields() {
TreeSet<PdxField> sortedSet = new TreeSet<PdxField>();
for (PdxField pf : this.fields) {
if (!pf.isDeleted()) {
sortedSet.add(pf);
}
}
return new ArrayList<PdxField>(sortedSet);
}
// Result does not include deleted fields
public List<String> getFieldNames() {
ArrayList<String> result = new ArrayList<String>(this.fields.size());
for (PdxField f : this.fields) {
if (!f.isDeleted()) {
result.add(f.getFieldName());
}
}
return Collections.unmodifiableList(result);
}
/**
* Used to optimize auto deserialization
*/
private final transient AtomicReference<AutoClassInfo> autoClassInfo =
new AtomicReference<AutoClassInfo>();
public void setAutoInfo(AutoClassInfo autoClassInfo) {
this.autoClassInfo.set(autoClassInfo);
}
public AutoClassInfo getAutoInfo(Class<?> c) {
AutoClassInfo ci = this.autoClassInfo.get();
if (ci != null) {
Class<?> lastClassAutoSerialized = ci.getInfoClass();
if (c.equals(lastClassAutoSerialized)) {
return ci;
} else {
if (lastClassAutoSerialized == null) {
this.autoClassInfo.compareAndSet(ci, null);
}
}
}
return null;
}
public void toStream(PrintStream printStream, boolean printFields) {
printStream.print(" ");
printStream.print(getClassName());
printStream.print(": ");
printStream.print("id=");
printStream.print(getTypeNum());
if (getDSId() != 0) {
printStream.print(" dsId=");
printStream.print(getDSId());
}
printStream.println();
if (printFields) {
for (PdxField field : this.fields) {
field.toStream(printStream);
}
}
}
public boolean getHasDeletedField() {
return this.hasDeletedField;
}
public void setHasDeletedField(boolean b) {
this.hasDeletedField = b;
}
}