/* * Copyright (C) NetStruxr, Inc. All rights reserved. * * This software is published under the terms of the NetStruxr * Public Software License version 0.5, a copy of which has been * included with this distribution in the LICENSE.NPL file. */ package er.extensions.eof; import java.math.BigDecimal; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSData; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSKeyValueCoding; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSPropertyListSerialization; import er.extensions.foundation.ERXArrayUtilities; /** * General purpose constant class, useful when you want reference object that are not * bytes or strings in the DB like what you get with the factory classes. * If you use objects of this class, you might be able to completely remove the EOSharedEditingContext * (the google search term for "why does my app lock up"). * <p> * To use the Number constants, you need to add an entry <code>ERXConstantClassName=Test.Status</code> to the attribute's userInfo * in question and your EO's class description needs to be a {@link ERXEntityClassDescription}, also * you must enable the {@link er.extensions.jdbc.ERXJDBCAdaptor}. * <p> * The String and Byte based constants can be used with a custom class type:<pre><code> * * ERCMailMessage.plist: * ... * { * columnName = MAIL_STATE_ID; * name = state; * prototypeName = osType; * adaptorValueConversionMethodName = value; * factoryMethodArgumentType = EOFactoryMethodArgumentIsNSString; * valueClassName = er.corebusinesslogic.ERCMailState; * valueFactoryMethodName = mailState; * } * ... * * public class ERCMailMessage extends EOGenericRecord { * ... * public ERCMailState state() { * return (ERCMailState) storedValueForKey("state"); * } * * public void setState(ERCMailState value) { * takeStoredValueForKey(value, "state"); * } * ... * } * * public class ERCMailState extends ERXConstant.StringConstant { * * public ERCMailState(String key, String name) { * super(key, name); * } * * public static ERCMailState mailState(String key) { * return (ERCMailState) constantForClassNamed(key, ERCMailState.class.getName()); * } * * public static ERCMailState EXCEPTION_STATE = new ERCMailState ("xcpt", "Exception"); * public static ERCMailState READY_TO_BE_SENT_STATE = new ERCMailState("rtbs", "Ready to be sent"); * public static ERCMailState SENT_STATE = new ERCMailState("sent", "Sent"); * public static ERCMailState RECEIVED_STATE = new ERCMailState("rcvd", "Received"); * public static ERCMailState WAIT_STATE = new ERCMailState("wait", "Wait"); * public static ERCMailState PROCESSING_STATE = new ERCMailState("proc", "Processing"); * } * </code></pre> * An example would be: * <pre><code> * public class Test extends EOGenericRecord { * // your "status" attribute need a userInfo entry * // "ERXConstantClassName" = "Test.Status"; * // Normally, the class name would be "Test$Status", this form is used to help you use EOGenerator * public static class Status extends ERXConstant.NumberConstant { * protected Status(int value, String name) { * super(value, name); * } * } * * public Status OFF = new Status(0, "Off"); * public Status ON = new Status(1, "On"); * * public Test() { * super(); * } * * public Status status() { * return (Status)storedValueForKey("status"); * } * * public void setStatus(Constant aValue) { * takeStoredValueForKey(aValue, "status"); * } * * public boolean isOn() { * return status() == ON; * } * } * * Test test = (Test)EOUtilities.createAndInsertInstance(ec, "Test"); * test.setTest(Test.Status.OFF); * test = (Test)EOUtilities.createAndInsertInstance(ec, "Test"); * test.setStatus(Test.Status.ON); * ec.saveChanges(); * * NSArray objects; * NSArray all = EOUtilities.objectsForEntityNamed(ec, "Test"); * EOQualifier q; * * objects = EOUtilities.objectsMatchingKeyAndValue(ec, "Test", "status", Test.Status.OFF); * log.info("Test.Status.OFF: " + objects); * q = new EOKeyValueQualifier("status", EOQualifier.QualifierOperatorEqual, Test.Status.OFF); * log.info("Test.Status.OFF: " + EOQualifier.filteredArrayWithQualifier(all, q)); * * // this might be a problem: equal number values match in the DB, but not in memory * objects = EOUtilities.objectsMatchingKeyAndValue(ec, "Test", "status", ERXConstant.OneInteger); * log.info("Number.OFF: " + objects); * q = new EOKeyValueQualifier("status", EOQualifier.QualifierOperatorEqual, ERXConstant.OneInteger); * log.info("Number.OFF: " + EOQualifier.filteredArrayWithQualifier(all, q)); * * // you can compare by equality * test.getStatus() == Test.Status.ON * </code></pre> * Note that upon class initialization 2500 Integers will be created and cached, from 0 - 2499. */ public abstract class ERXConstant { private static final Logger log = LoggerFactory.getLogger(ERXConstant.class); /** * Holds the value store, grouped by class name. */ private static final Map _store = new HashMap(); /** * Retrieves all constants for the given class name ordered by value. An empty * NSArray is returned if the class isn't found. * @param clazzName */ public static NSArray constantsForClassName(String clazzName) { synchronized (_store) { Map map = keyMap(clazzName, false); NSMutableArray array = new NSMutableArray(map.values().toArray()); ERXArrayUtilities.sortArrayWithKey(array, "sortOrder"); return array; } } public static interface Constant { public int sortOrder(); public String name(); public Object value(); } /** * Retrieves the constant for the given class name and value. Null is returned * if either class or value isn't found. * @param value * @param clazzName */ public static Constant constantForClassNamed(Object value, String clazzName) { synchronized (_store) { Map classMap = keyMap(clazzName, false); Constant result = (Constant) classMap.get(value); log.debug("Getting {} for {} and {}", result, clazzName, value); return result; } } private static int globalSortOrder = 0; /** * Retrieves the key map for the class name. * @param name * @param create */ private static Map keyMap(String name, boolean create) { Map map = (Map) _store.get(name); if(map == null) { if(create) { map = new HashMap(); _store.put(name, map); name = name.replace('$', '.'); _store.put(name, map); } else { map = Collections.EMPTY_MAP; } } return map; } private static void registerConstant(Object key, Constant value, Class clazz) { synchronized (_store) { String className = clazz.getName(); Map classMap = keyMap(className, true); log.debug("Putting {} for {}", key, className); classMap.put(key, value); } } public static class NumberConstant extends Number implements Constant { /** * Do I need to update serialVersionUID? * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a> */ private static final long serialVersionUID = 1L; /** * Holds the value. */ private int _value; /** * Holds the name. */ private String _name; /** * Holds the name. */ private int _sortOrder; /** * Sets the value and puts the object in the store keyed by class name and value. * @param value */ protected NumberConstant(int value, String name) { _value = value; _name = name; _sortOrder = globalSortOrder++; ERXConstant.registerConstant(integerForInt(value), this, getClass()); } /** * Returns the sort order of the value. */ public int sortOrder() { return _sortOrder; } /** * Number interface implementation, returns the value. */ @Override public final double doubleValue() { return intValue(); } /** * Number interface implementation, returns the value. */ @Override public final float floatValue() { return intValue(); } /** * Number interface implementation, returns the value. */ @Override public final int intValue() { return _value; } /** * Number interface implementation, returns the value. */ @Override public final long longValue() { return intValue(); } /** * Returns the value. */ @Override public final int hashCode() { return _value; } public String name() { return _name; } public String userPresentableDescription() { return name() + " (" + intValue() + ")"; } @Override public String toString() { return getClass().getName() + ": " + userPresentableDescription(); } public Number value() { return integerForInt(intValue()); } /** * Overridden to compare by value. */ @Override public final boolean equals(Object otherObject) { if(otherObject == null) { return false; }/* AK: we would violate the equals contract here, but we may need this with D2W later? if((otherObject instanceof Number) && !(otherObject instanceof ERXConstant)) { return ((Number)otherObject).intValue() == intValue(); }*/ if(otherObject.getClass() != getClass()) { return false; } return ((NumberConstant)otherObject).intValue() == intValue(); } /** * Retrieves the constant for the given class name and value. Null is returned * if either class or value isn't found. Note that in case of inner classes, the * name should be <code>Test.Status</code>, not <code>Test$Status</code>. * @param value * @param clazzName */ public static NumberConstant constantForClassNamed(int value, String clazzName) { return constantForClassNamed(integerForInt(value), clazzName); } /** * Retrieves the constant for the given class name and value. Null is returned * if either class or value isn't found. * @param value * @param clazzName */ public static NumberConstant constantForClassNamed(Number value, String clazzName) { return (NumberConstant) ERXConstant.constantForClassNamed(value, clazzName); } } /** * Constant class that can be used with strings in the DB. * @author ak * */ public static abstract class StringConstant implements Constant { private String _value; private String _name; private int _sortOrder; public StringConstant(String value, String name) { _value = value; _name = name; _sortOrder = globalSortOrder++; ERXConstant.registerConstant(value, this, getClass()); } public String name() { return _name; } public int sortOrder() { return _sortOrder; } public String value() { return _value; } public String userPresentableDescription() { return name() + " (" + value() + ")"; } @Override public String toString() { return getClass().getName() + ": " + userPresentableDescription(); } /** * Retrieves the constant for the given class name and value. Null is returned * if either class or value isn't found. * @param value * @param clazzName */ public static StringConstant constantForClassNamed(String value, String clazzName) { return (StringConstant) ERXConstant.constantForClassNamed(value, clazzName); } } /** * Constant class that can be used with bytes or NSData in the DB. * @author ak * */ public static abstract class ByteConstant implements Constant { private NSData _value; private String _name; private int _sortOrder; public ByteConstant(String value, String name) { //AK: making a lot of assumptions here... the value is a <hex data>, the result is a valid NSData this((NSData)NSPropertyListSerialization.propertyListFromString(value.toString()),name); } public ByteConstant(byte value[], String name) { this(new NSData(value),name); } public ByteConstant(NSData value, String name) { _value = value; _name = name; _sortOrder = globalSortOrder++; ERXConstant.registerConstant(value, this, getClass()); } public String name() { return _name; } public int sortOrder() { return _sortOrder; } public NSData value() { return _value; } public String userPresentableDescription() { return name(); } @Override public String toString() { return getClass().getName() + ": " + userPresentableDescription(); } /** * Retrieves the constant for the given class name and value. Null is returned * if either class or value isn't found. * @param value * @param clazzName */ public static ByteConstant constantForClassNamed(NSData value, String clazzName) { return (ByteConstant) ERXConstant.constantForClassNamed(value, clazzName); } /** * Retrieves the constant for the given class name and value. Null is returned * if either class or value isn't found. * @param value * @param clazzName */ public static ByteConstant constantForClassNamed(byte value[], String clazzName) { return (ByteConstant) ERXConstant.constantForClassNamed(new NSData(value), clazzName); } } public static final int MAX_INT=2500; protected static Integer[] INTEGERS=new Integer[MAX_INT]; static { for (int i=0; i<MAX_INT; i++) INTEGERS[i]=Integer.valueOf(i); } public static final Object EmptyObject = new Object(); public static final String EmptyString = ""; public static final NSArray EmptyArray = NSArray.EmptyArray; public static final NSArray SingleNullValueArray = new NSArray(NSKeyValueCoding.NullValue); public static final NSDictionary EmptyDictionary = NSDictionary.EmptyDictionary; public static final Integer MinusOneInteger = Integer.valueOf(-1); public static final Integer OneInteger = integerForInt(1); public static final Integer ZeroInteger = integerForInt(0); public static final Integer TwoInteger = integerForInt(2); public static final Integer ThreeInteger = integerForInt(3); public static final BigDecimal ZeroBigDecimal = new BigDecimal(0.00); public static final BigDecimal OneBigDecimal = new BigDecimal(1.00); public static final Class[] EmptyClassArray = new Class[0]; public static final Class[] NotificationClassArray = { com.webobjects.foundation.NSNotification.class }; public static final Class[] ObjectClassArray = { Object.class }; public static final Class[] StringClassArray = new Class[] { String.class }; public static final Object[] EmptyObjectArray = new Object[] {}; /** an empty gif image */ public static final NSData EmptyImage = (NSData) NSPropertyListSerialization.propertyListFromString("<47494638396101000100800000ffffff00000021f90401000000002c00000000010001000002024401003b00>"); /** * Returns an Integer for a given int * @return potentially cache Integer for a given int */ public static Integer integerForInt(int i) { return (i>=0 && i<MAX_INT) ? INTEGERS[i] : Integer.valueOf(i); } }