| /* |
| * 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.lang.ref.WeakReference; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.apache.geode.DataSerializer; |
| import org.apache.geode.annotations.Immutable; |
| import org.apache.geode.internal.DataSerializableFixedID; |
| import org.apache.geode.internal.HeapDataOutputStream; |
| import org.apache.geode.internal.InternalDataSerializer; |
| import org.apache.geode.internal.Version; |
| import org.apache.geode.pdx.PdxInstance; |
| import org.apache.geode.pdx.PdxSerializationException; |
| import org.apache.geode.pdx.WritablePdxInstance; |
| |
| public class EnumInfo implements DataSerializableFixedID { |
| private String clazz; |
| private String name; |
| // The ordinal field is only used to support comparison. |
| // It is not used for equals or hashcode. |
| private int ordinal; |
| private transient volatile WeakReference<Enum<?>> enumCache = null; |
| |
| public EnumInfo(Enum<?> e) { |
| this.clazz = e.getDeclaringClass().getName(); |
| this.name = e.name(); |
| this.ordinal = e.ordinal(); |
| this.enumCache = new WeakReference<Enum<?>>(e); |
| } |
| |
| public EnumInfo(String clazz, String name, int enumOrdinal) { |
| this.clazz = clazz; |
| this.name = name; |
| this.ordinal = enumOrdinal; |
| } |
| |
| public EnumInfo() {} |
| |
| @Override |
| public int getDSFID() { |
| return ENUM_INFO; |
| } |
| |
| public String getClassName() { |
| return this.clazz; |
| } |
| |
| // This method is used by the "pdx rename" command. |
| public void setClassName(String v) { |
| this.clazz = v; |
| } |
| |
| @Override |
| public void toData(DataOutput out) throws IOException { |
| DataSerializer.writeString(this.clazz, out); |
| DataSerializer.writeString(this.name, out); |
| DataSerializer.writePrimitiveInt(this.ordinal, out); |
| } |
| |
| @Override |
| public void fromData(DataInput in) throws IOException, ClassNotFoundException { |
| this.clazz = DataSerializer.readString(in); |
| this.name = DataSerializer.readString(in); |
| this.ordinal = DataSerializer.readPrimitiveInt(in); |
| } |
| |
| public void flushCache() { |
| synchronized (this) { |
| this.enumCache = null; |
| } |
| } |
| |
| public int compareTo(EnumInfo other) { |
| int result = this.clazz.compareTo(other.clazz); |
| if (result == 0) { |
| result = this.name.compareTo(other.name); |
| if (result == 0) { |
| result = other.ordinal - this.ordinal; |
| } |
| } |
| return result; |
| } |
| |
| public Enum<?> getEnum() throws ClassNotFoundException { |
| Enum<?> result; |
| if (InternalDataSerializer.LOAD_CLASS_EACH_TIME) { |
| result = loadEnum(); |
| } else { |
| result = getExistingEnum(); |
| if (result == null) { |
| synchronized (this) { |
| result = getExistingEnum(); |
| if (result == null) { |
| result = loadEnum(); |
| this.enumCache = new WeakReference<Enum<?>>(result); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| public int getOrdinal() { |
| return this.ordinal; |
| } |
| |
| private Enum<?> getExistingEnum() { |
| WeakReference<Enum<?>> wr = this.enumCache; |
| if (wr != null) { |
| return wr.get(); |
| } |
| return null; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Enum<?> loadEnum() throws ClassNotFoundException { |
| @SuppressWarnings("rawtypes") |
| Class c = InternalDataSerializer.getCachedClass(this.clazz); |
| try { |
| return Enum.valueOf(c, this.name); |
| } catch (IllegalArgumentException ex) { |
| throw new PdxSerializationException("PDX enum field could not be read because \"" + this.name |
| + "\" is not a valid name in enum class " + c, ex); |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + ((clazz == null) ? 0 : clazz.hashCode()); |
| result = prime * result + ((name == null) ? 0 : name.hashCode()); |
| return result; |
| } |
| |
| @Override |
| public String toString() { |
| return clazz + "." + name; |
| } |
| |
| public String toFormattedString() { |
| return getClass().getSimpleName() + "[\n " + clazz + "." + name + "]"; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) |
| return true; |
| if (obj == null) |
| return false; |
| if (getClass() != obj.getClass()) |
| return false; |
| EnumInfo other = (EnumInfo) obj; |
| if (clazz == null) { |
| if (other.clazz != null) |
| return false; |
| } else if (!clazz.equals(other.clazz)) |
| return false; |
| if (name == null) { |
| if (other.name != null) |
| return false; |
| } else if (!name.equals(other.name)) |
| return false; |
| if (this.ordinal != other.ordinal) { |
| throw new PdxSerializationException("The ordinal value for the enum " + this.name |
| + " on class " + this.clazz |
| + " can not be changed. Pdx only allows new enum constants to be added to the end of the enum."); |
| } |
| return true; |
| } |
| |
| public PdxInstance getPdxInstance(int enumId) { |
| return new PdxInstanceEnumInfo(enumId, this); |
| } |
| |
| public static class PdxInstanceEnumInfo |
| implements InternalPdxInstance, ComparableEnum { |
| private static final long serialVersionUID = 7907582104525106416L; |
| private final int enumId; |
| private final EnumInfo ei; |
| |
| public PdxInstanceEnumInfo(int enumId, EnumInfo ei) { |
| this.enumId = enumId; |
| this.ei = ei; |
| } |
| |
| @Override |
| public String getClassName() { |
| return this.ei.clazz; |
| } |
| |
| @Override |
| public String getName() { |
| return this.ei.name; |
| } |
| |
| @Override |
| public int getOrdinal() { |
| return this.ei.ordinal; |
| } |
| |
| @Override |
| public boolean isEnum() { |
| return true; |
| } |
| |
| @Override |
| public Object getObject() { |
| try { |
| return this.ei.getEnum(); |
| } catch (ClassNotFoundException ex) { |
| throw new PdxSerializationException( |
| String.format("Could not create an instance of a class %s", |
| getClassName()), |
| ex); |
| } |
| } |
| |
| @Override |
| public boolean hasField(String fieldName) { |
| return getFieldNames().contains(fieldName); |
| } |
| |
| @Immutable |
| private static final List<String> fieldNames; |
| static { |
| ArrayList<String> tmp = new ArrayList<String>(2); |
| tmp.add("name"); |
| tmp.add("ordinal"); |
| fieldNames = Collections.unmodifiableList(tmp); |
| } |
| |
| @Override |
| public List<String> getFieldNames() { |
| return fieldNames; |
| } |
| |
| @Override |
| public boolean isIdentityField(String fieldName) { |
| return false; |
| } |
| |
| @Override |
| public Object getField(String fieldName) { |
| if ("name".equals(fieldName)) { |
| return getName(); |
| } else if ("ordinal".equals(fieldName)) { |
| return getOrdinal(); |
| } |
| return null; |
| } |
| |
| @Override |
| public WritablePdxInstance createWriter() { |
| throw new IllegalStateException("PdxInstances that are an enum can not be modified."); |
| } |
| |
| @Override |
| public void sendTo(DataOutput out) throws IOException { |
| InternalDataSerializer.writePdxEnumId(this.enumId, out); |
| } |
| |
| @Override |
| public int hashCode() { |
| // this hashCode needs to be kept consistent with PdxInstanceEnum |
| final int prime = 31; |
| int result = 1; |
| String className = getClassName(); |
| String enumName = getName(); |
| result = prime * result + ((className == null) ? 0 : className.hashCode()); |
| result = prime * result + ((enumName == null) ? 0 : enumName.hashCode()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) |
| return true; |
| if (obj == null) |
| return false; |
| if (!(obj instanceof ComparableEnum)) |
| return false; |
| ComparableEnum other = (ComparableEnum) obj; |
| String className = getClassName(); |
| if (className == null) { |
| if (other.getClassName() != null) |
| return false; |
| } else if (!className.equals(other.getClassName())) |
| return false; |
| String enumName = getName(); |
| if (enumName == null) { |
| if (other.getName() != null) |
| return false; |
| } else if (!enumName.equals(other.getName())) |
| return false; |
| return true; |
| } |
| |
| @Override |
| public String toString() { |
| return this.ei.name; |
| } |
| |
| @Override |
| public byte[] toBytes() throws IOException { |
| HeapDataOutputStream hdos = new HeapDataOutputStream(16, Version.CURRENT); |
| sendTo(hdos); |
| return hdos.toByteArray(); |
| } |
| |
| @Override |
| public int compareTo(Object o) { |
| if (o instanceof ComparableEnum) { |
| ComparableEnum other = (ComparableEnum) o; |
| if (!getClassName().equals(other.getClassName())) { |
| throw new ClassCastException( |
| "Can not compare a " + getClassName() + " to a " + other.getClassName()); |
| } |
| return getOrdinal() - other.getOrdinal(); |
| } else { |
| throw new ClassCastException( |
| "Can not compare an instance of " + o.getClass() + " to a " + this.getClass()); |
| } |
| } |
| } |
| |
| @Override |
| public Version[] getSerializationVersions() { |
| return null; |
| } |
| |
| public void toStream(PrintStream printStream) { |
| printStream.print(" "); |
| printStream.print(this.clazz); |
| printStream.print('.'); |
| printStream.print(this.name); |
| printStream.println(); |
| } |
| } |