/* * 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.internal.ClassPathLoader; import org.apache.geode.internal.DSCODE; import org.apache.geode.internal.DataSerializableFixedID; import org.apache.geode.internal.HeapDataOutputStream; import org.apache.geode.internal.InternalDataSerializer; import org.apache.geode.internal.Sendable; import org.apache.geode.internal.Version; import org.apache.geode.internal.i18n.LocalizedStrings; 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() {} 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; } public void toData(DataOutput out) throws IOException { DataSerializer.writeString(this.clazz, out); DataSerializer.writeString(this.name, out); DataSerializer.writePrimitiveInt(this.ordinal, out); } 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 PdxInstance, Sendable, ConvertableToBytes, 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; } public String getClassName() { return this.ei.clazz; } public String getName() { return this.ei.name; } public int getOrdinal() { return this.ei.ordinal; } public boolean isEnum() { return true; } public Object getObject() { try { return this.ei.getEnum(); } catch (ClassNotFoundException ex) { throw new PdxSerializationException( LocalizedStrings.DataSerializer_COULD_NOT_CREATE_AN_INSTANCE_OF_A_CLASS_0 .toLocalizedString(getClassName()), ex); } } public boolean hasField(String fieldName) { return getFieldNames().contains(fieldName); } static private final List<String> fieldNames; static { ArrayList<String> tmp = new ArrayList<String>(2); tmp.add("name"); tmp.add("ordinal"); fieldNames = Collections.unmodifiableList(tmp); } public List<String> getFieldNames() { return fieldNames; } public boolean isIdentityField(String fieldName) { return false; } public Object getField(String fieldName) { if ("name".equals(fieldName)) { return getName(); } else if ("ordinal".equals(fieldName)) { return getOrdinal(); } return null; } public WritablePdxInstance createWriter() { throw new IllegalStateException("PdxInstances that are an enum can not be modified."); } 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; } public byte[] toBytes() throws IOException { HeapDataOutputStream hdos = new HeapDataOutputStream(16, Version.CURRENT); sendTo(hdos); return hdos.toByteArray(); } 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(); } }