Java tutorial
/* * COPYRIGHT. ALL RIGHTS RESERVED. * * This software is only to be used for the purpose for which it has been * provided. No part of it is to be reproduced, disassembled, transmitted, * stored in a retrieval system nor translated in any human or computer * language in any way or for any other purposes whatsoever without the prior * written consent of loy org. */ /* * Copyright 1999-2004 The Apache Software Foundation * * Licensed 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. */ /* * NOTICE: This source code originally belongs to the ContextBase class of * Apache Commons Chain API. It has been modified to meet specific * requirements. The following changes have been introduced: - The ContextBase * class has been renamed to BaseContext. - The package has been changed to * org.loy.fesf.core - The BaseContext class implements * org.loy.fesf.core.Context. - Methods checkKey and checkValue have been * introduced. - The get, put and remove methods have a call to the methods * checkKey and ChcekValue. */ package org.loy.fesf.core.impl; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.util.AbstractCollection; import java.util.AbstractSet; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.loy.fesf.core.Context; /** * <p> * Convenience base class for {@link Context}implementations. * </p> * * <p> * In addition to the minimal functionality required by the {@link Context} * interface, this class implements the recommended support for * Attribute-Property Transparency * </p> * . This is implemented by analyzing the available JavaBeans properties of * this class (or its subclass), exposes them as key-value pairs in the * <code>Map</code>, with the key being the name of the property itself. * * <p> * <strong>IMPLEMENTATION NOTE</strong> - Because <code>empty</code> is a * read-only property defined by the <code>Map</code> interface, it may not be * utilized as an attribute key or property name. * </p> * * @author Craig R. McClanahan * @version $Revision: 1.4 $ $Date: 2008/02/11 17:19:02IST $ * * <br> * NOTICE:</br> This source code originally belongs to the ContextBase * class of Apache Commons Chain API. It has been modified to meet * specific requirements. <br> * The following changes have been introduced:</br> <li>The * ContextBase class has been renamed to BaseContext.</li> <li>The * package has been changed to org.loy.fesf.core</li> <li>The * BaseContext class implements org.loy.fesf.core.Context.</li> <li> * Methods checkKey and checkValue have been introduced.</li> <li>The * get, put and remove methods have a call to the methods checkKey and * ChcekValue.</li> * */ public class BaseContext implements Context { // --------------------------------------------------------- Private // Classes /** * This HashMap stores all context information put in this class or in any * of its sub-classes. */ private final HashMap<Object, Object> localMap; /** * <p> * Private implementation of <code>Set</code> that implements the semantics * required for the value returned by <code>entrySet()</code>. * </p> */ private class EntrySetImpl extends AbstractSet<Object> { /** * Clears the entries in the inner class EntrySetImpl object. */ @Override public void clear() { BaseContext.this.clear(); } /** * Check for an entry in the inner class EntrySetImpl object. * * @param obj * Object * @return boolean */ @Override public boolean contains(final Object obj) { Map.Entry entry = null; Entry actual = null; if (!(obj instanceof Map.Entry)) { return (false); } entry = (Map.Entry) obj; actual = BaseContext.this.entry(entry.getKey()); if (actual != null) { return (actual.equals(entry)); } return (false); } /** * Check if there is no entry in the inner class EntrySetImpl object. * * @return boolean */ @Override public boolean isEmpty() { return (BaseContext.this.isEmpty()); } /** * Get an iteraor for the inner class EntrySetImpl object entries. * * @return Iterator */ @Override public Iterator iterator() { return (BaseContext.this.entriesIterator()); } /** * Remove an entry from the inner class EntrySetImpl object. * * @param obj * Object * @return boolean */ @Override public boolean remove(final Object obj) { if (obj instanceof Map.Entry) { return (BaseContext.this.remove((Map.Entry) obj)); } return (false); } /** * Get no of entries in the inner class EntrySetImpl object. * * @return int */ @Override public int size() { return (BaseContext.this.size()); } } /** * <p> * Private implementation of <code>Iterator</code> for the <code>Set</code> * returned by <code>entrySet()</code>. * </p> */ private class EntrySetIterator implements Iterator { /** * Map.Entry type identifier. */ private Map.Entry entry = null; /** * Iterator type identifier. */ private final Iterator keys = BaseContext.this.keySet().iterator(); /** * Check if there are any more entries. * * @return boolean */ @Override public boolean hasNext() { return (this.keys.hasNext()); } /** * Get the next entry. * * @return Object */ @Override public Object next() { this.entry = BaseContext.this.entry(this.keys.next()); return (entry); } /** * Remove entry from Context. */ @Override public void remove() { BaseContext.this.remove(entry); } } /** * <p> * Private implementation of <code>Map.Entry</code> for each item in * <code>EntrySetImpl</code>. * </p> */ private class MapEntryImpl implements Map.Entry { /** * Object type identifier. */ private final Object key; /** * Object type identifier. */ private Object value; /** * Constructor for MapEntryImpl. * * @param tempKey * Object * @param tempValue * Object */ MapEntryImpl(final Object tempKey, final Object tempValue) { this.key = tempKey; this.value = tempValue; } /** * Overrides equals method of Object. Check if the passed object is the * same as on which the method is being called. * * @param obj * Object * @return boolean */ @Override public boolean equals(final Object obj) { Map.Entry entry = null; if (obj == null) { return (false); } else if (!(obj instanceof Map.Entry)) { return (false); } entry = (Map.Entry) obj; if (key == null) { return (entry.getKey() == null); } if (this.key.equals(entry.getKey())) { if (value == null) { return (entry.getValue() == null); } return (this.value.equals(entry.getValue())); } return (false); } /** * Return key. * * @return Object */ @Override public Object getKey() { return (this.key); } /** * Return value. * * @return Object */ @Override public Object getValue() { return (this.value); } /** * Get the appropriate hashcode. * * @return int */ @Override public int hashCode() { int keyHashCode = 0; if (key == null) { keyHashCode = 0; } else { keyHashCode = this.key.hashCode(); } int valueHashCode = 0; if (value == null) { valueHashCode = 0; } else { valueHashCode = this.value.hashCode(); } return keyHashCode ^ valueHashCode; } /** * Sets the value. * * @param tempValue * Object * @return object */ @Override public Object setValue(final Object tempValue) { Object previous = this.value; BaseContext.this.put(this.key, tempValue); this.value = tempValue; return (previous); } /** * Overrides toString method of Object. * * @return String */ @Override public String toString() { return getKey() + "=" + getValue(); } } /** * <p> * Private implementation of <code>Collection</code> that implements the * semantics required for the value returned by <code>values()</code>. * </p> */ private class ValuesImpl extends AbstractCollection { /** * Clears the entries in the inner class ValuesImpl object. */ @Override public void clear() { BaseContext.this.clear(); } /** * Check for an entry in the inner class ValuesImpl object. * * @param obj * Object * @return boolean */ @Override public boolean contains(final Object obj) { Map.Entry entry = null; if (!(obj instanceof Map.Entry)) { return (false); } entry = (Map.Entry) obj; return (BaseContext.this.containsValue(entry.getValue())); } /** * Check whether there are any entries in the inner class ValuesImpl * object. * * @return boolean */ @Override public boolean isEmpty() { return (BaseContext.this.isEmpty()); } /** * Get the iterator for ValuesImpl object entries. * * @return Iterator */ @Override public Iterator iterator() { return (BaseContext.this.valuesIterator()); } /** * Remove an entry from the inner class ValuesImpl object. * * @param obj * Object * @return boolean */ @Override public boolean remove(final Object obj) { if (obj instanceof Map.Entry) { return (BaseContext.this.remove((Map.Entry) obj)); } return (false); } /** * Return the no of entries in the ValuesImpl object. * * @return int */ @Override public int size() { return (BaseContext.this.size()); } } /** * <p> * Private implementation of <code>Iterator</code> for the * <code>Collection</code> returned by <code>values()</code>. * </p> */ private class ValuesIterator implements Iterator { /** * Map.Entry type identifier. */ private Map.Entry entry = null; /** * Iterator type identifier. */ private final Iterator keys = BaseContext.this.keySet().iterator(); /** * Returns whether there are more entries to iterate or not. * * @return boolean */ @Override public boolean hasNext() { return (this.keys.hasNext()); } /** * Iterate to the next object. * * @return Object */ @Override public Object next() { this.entry = BaseContext.this.entry(this.keys.next()); return (this.entry.getValue()); } /** * Removes entry from the Context object. */ @Override public void remove() { BaseContext.this.remove(entry); } } /** * <p> * Distinguished SINGLETON value that is stored in the map for each key * that is actually a property. This value is used to ensure that * <code>equals()</code> comparisons will always fail. * </p> */ private static final Map SINGLETON = new HashMap() { /** * Overrides equals method of AbstractMap. Check if the passed object * is the same as on which the method is being called. * * @param object * Object * @return boolean */ @Override public boolean equals(final Object object) { return (false); } }; /** * <p> * Zero-length array of parameter values for calling property getters. * </p> */ private static final Object[] ZERO_PARAMS = new Object[0]; // NOTE - PropertyDescriptor instances are not Serializable, so the // following variables must be declared as transient. When a BaseContext // instance is deserialized, the no-arguments constructor is called, // and the initialize() method called there will repoopulate them. // Therefore, no special restoration activity is required. /** * <p> * The <code>PropertyDescriptor</code> s for all JavaBeans properties of * this {@link Context}implementation class, keyed by property name. This * collection is allocated only if there are any JavaBeans properties. * </p> */ private transient Map descriptors = null; /** * <p> * The same <code>PropertyDescriptor</code> s as an array. * </p> */ private transient PropertyDescriptor[] pd = null; /** * Default Contructor for BaseContext. * */ public BaseContext() { localMap = new HashMap(); // super(); init(); } /** * <p> * Override the default <code>Map</code> behavior to return size of the * underlying <code>Map</code>. * </p> * * @return <code>0</code> if this Context is empty */ @Override public int size() { // Even for properties a key is inserted in the underlying map // (See initialize() method). Hence the below statement will work. return (this.localMap.size()); } /** * <p> * Override the default <code>Map</code> behavior to return * <code>true</code> if the underlying <code>Map</code> only contains * key-value pairs for local properties (if any). * </p> * * @return <code>true</code> if this Context is empty, otherwise * <code>false</code>. */ @Override public boolean isEmpty() { // Case 1 -- no local properties if (descriptors == null) { return (this.localMap.isEmpty()); } // Case 2 -- compare key count to property count return (this.localMap.size() <= this.descriptors.size()); } /** * <p> * Override the default <code>Map</code> behavior to return * <code>true</code> if the specified key is present in the underlying * <code>Map</code>. * </p> * * @param key * the key look for in the context. * @return <code>true</code> if found in this context otherwise * <code>false</code>. */ @Override public boolean containsKey(final Object key) { // Even for properties a key is inserted in the underlying map // (See initialize() method). Hence the below statement will work. return (this.localMap.containsKey(key)); } /** * <p> * Override the default <code>Map</code> behavior to return * <code>true</code> if the specified value is present in either the * underlying <code>Map</code> or one of the local property values. * </p> * * @param value * the value look for in the context. * @return <code>true</code> if found in this context otherwise * <code>false</code>. * */ @Override public boolean containsValue(final Object value) { // Case 1 -- no local properties if (descriptors == null) { return (this.localMap.containsValue(value)); } else if (this.localMap.containsValue(value)) { // Case 2 -- value found in the underlying Map return (true); } // Case 3 -- check the values of our readable properties for (int i = 0; i < this.pd.length; i++) { if (pd[i].getReadMethod() != null) { Object prop = readProperty(pd[i]); if (value == null) { if (prop == null) { return (true); } } else if (value.equals(prop)) { return (true); } } } return (false); } /** * <p> * Override the default <code>Map</code> behavior to return the value of a * local property if the specified key matches a local property name. * </p> * * <p> * <strong>IMPLEMENTATION NOTE </strong>- If the specified <code>key</code> * identifies a write-only property, <code>null</code> will arbitrarily be * returned, in order to avoid difficulties implementing the contracts of * the <code>Map</code> interface. * </p> * * @param key * Key of the value to be returned * @return The value for the specified key. * */ @Override public Object get(final Object key) { checkKey(key); // Case 1 -- no local properties if (descriptors == null) { return (this.localMap.get(key)); } // Case 2 -- this is a local property if (key != null) { PropertyDescriptor descriptor = (PropertyDescriptor) this.descriptors.get(key); if (descriptor != null) { if (descriptor.getReadMethod() != null) { return (readProperty(descriptor)); } return (null); } } // Case 3 -- retrieve value from our underlying Map return (this.localMap.get(key)); } /** * <p> * Override the default <code>Map</code> behavior to set the value of a * local property if the specified key matches a local property name. * </p> * * @param key * Key of the value to be stored or replaced * @param value * New value to be stored * @return The value added to the Context. * */ @Override public Object put(final Object key, final Object value) { checkKey(key); checkValue(value); // Case 1 -- no local properties if (descriptors == null) { return (this.localMap.put(key, value)); } // Case 2 -- this is a local property if (key != null) { PropertyDescriptor descriptor = (PropertyDescriptor) this.descriptors.get(key); if (descriptor != null) { Object previous = null; if (descriptor.getReadMethod() != null) { previous = readProperty(descriptor); } writeProperty(descriptor, value); return (previous); } } // Case 3 -- store or replace value in our underlying map return (this.localMap.put(key, value)); } /** * <p> * Override the default <code>Map</code> behavior to throw * <code>UnsupportedOperationException</code> on any attempt to remove a * key that is the name of a local property. * </p> * * @param key * Key to be removed * @return The value removed from the Context. * */ @Override public Object remove(final Object key) { checkKey(key); // Case 1 -- no local properties if (descriptors == null) { return (this.localMap.remove(key)); } // Case 2 -- this is a local property if (key != null) { PropertyDescriptor descriptor = (PropertyDescriptor) this.descriptors.get(key); if (descriptor != null) { throw new UnsupportedOperationException("Local property '" + key + "' cannot be removed"); } } // Case 3 -- remove from underlying Map return (this.localMap.remove(key)); } /** * <p> * Override the default <code>Map</code> behavior to call the * <code>put()</code> method individually for each key-value pair in the * specified <code>Map</code>. * </p> * * @param map * <code>Map</code> containing key-value pairs to store (or * replace) * */ @Override public void putAll(final Map map) { Iterator pairs = map.entrySet().iterator(); while (pairs.hasNext()) { Map.Entry pair = (Map.Entry) pairs.next(); put(pair.getKey(), pair.getValue()); } } /** * <p> * Override the default <code>Map</code> behavior to clear all keys and * values except those corresponding to JavaBeans properties. * </p> */ @Override public void clear() { if (descriptors == null) { this.localMap.clear(); } else { Iterator keys = keySet().iterator(); while (keys.hasNext()) { Object key = keys.next(); if (!this.descriptors.containsKey(key)) { keys.remove(); } } } } /** * <p> * Override the default <code>Map</code> behavior to return a * <code>Set</code> that meets the specified default behavior except for * attempts to remove the key for a property of the {@link Context} * implementation class, which will throw * <code>UnsupportedOperationException</code>. * </p> * * @return The set of keys for objects in this Context. */ @Override public Set keySet() { return (this.localMap.keySet()); } /** * <p> * Override the default <code>Map</code> behavior to return a * <code>Collection</code> that meets the specified default behavior except * for attempts to remove the key for a property of the {@link Context} * implementation class, which will throw * <code>UnsupportedOperationException</code>. * </p> * * @return The collection of values in this Context. */ @Override public Collection values() { return (new ValuesImpl()); } /** * <p> * Override the default <code>Map</code> behavior to return a * <code>Set</code> that meets the specified default behavior except for * attempts to remove the key for a property of the {@link Context} * implementation class, which will throw * <code>UnsupportedOperationException</code>. * </p> * * @return Set of entries in the Context. */ @Override public Set entrySet() { return (new EntrySetImpl()); } // --------------------------------------------------------- Private // Methods /** * <p> * Eliminate the specified property descriptor from the list of property * descriptors in <code>pd</code>. * </p> * * @param name * Name of the property to eliminate * */ private void removePropertyDescriptor(final String name) { PropertyDescriptor[] results = null; int j = -1; boolean exit = false; for (int i = 0; (i < this.pd.length) && !exit; i++) { if (name.equals(pd[i].getName())) { j = i; exit = true; } } if (j < 0) { throw new IllegalArgumentException("Property '" + name + "' is not present"); } results = new PropertyDescriptor[this.pd.length - 1]; System.arraycopy(pd, 0, results, 0, j); System.arraycopy(pd, j + 1, results, j, this.pd.length - (j + 1)); this.pd = results; } /** * <p> * Return an <code>Iterator</code> over the set of <code>Map.Entry</code> * objects representing our key-value pairs. * </p> * * @return Iterator * */ private Iterator entriesIterator() { return (new EntrySetIterator()); } /** * <p> * Return a <code>Map.Entry</code> for the specified key value, if it is * present; otherwise, return <code>null</code>. * </p> * * @param key * Attribute key or property name * @return Map.Entry * */ private Map.Entry entry(final Object key) { if (containsKey(key)) { return (new MapEntryImpl(key, get(key))); } return (null); } /** * <p> * Customize the contents of our underlying <code>Map</code> so that it * contains keys corresponding to all of the JavaBeans properties of the * {@link Context}implementation class. * </p> * */ private void init() { // Retrieve the set of property descriptors for this Context class try { this.pd = Introspector.getBeanInfo(getClass()).getPropertyDescriptors(); } catch (IntrospectionException e) { this.pd = new PropertyDescriptor[0]; // Should never happen } removePropertyDescriptor("class"); // Because of "getClass()" removePropertyDescriptor("empty"); // Because of "isEmpty()" // Initialize the underlying Map contents if (this.pd.length > 0) { this.descriptors = new HashMap(); for (int i = 0; i < this.pd.length; i++) { this.descriptors.put(pd[i].getName(), pd[i]); this.localMap.put(pd[i].getName(), BaseContext.SINGLETON); } } } /** * <p> * Get and return the value for the specified property. * </p> * * @param descriptor * <code>PropertyDescriptor</code> for the specified property * @return Object * */ private Object readProperty(final PropertyDescriptor descriptor) { try { Method method = descriptor.getReadMethod(); if (method == null) { throw new UnsupportedOperationException("Property '" + descriptor.getName() + "' is not readable"); } return (method.invoke(this, BaseContext.ZERO_PARAMS)); } catch (Exception e) { throw new UnsupportedOperationException( "Exception reading property '" + descriptor.getName() + "': " + e.getMessage()); } } /** * <p> * Remove the specified key-value pair, if it exists, and return * <code>true</code>. If this pair does not exist, return * <code>false</code>. * </p> * * @param entry * Key-value pair to be removed * @return boolean * */ private boolean remove(final Map.Entry entry) { Map.Entry actual = entry(entry.getKey()); if (actual == null) { return (false); } else if (!entry.equals(actual)) { return (false); } else { remove(entry.getKey()); return (true); } } /** * <p> * This method checks for following contrainsts on a key key is not null, * key is a String, key is not a blank string. * </p> * * @param key * Object */ private void checkKey(final Object key) { if (key != null) { if (key instanceof String) { if (StringUtils.isBlank((String) key)) { throw new IllegalArgumentException( "The input key is not valid. " + "Key should be an instance of class " + "java.lang.String and should not be blank " + "The key entered is blank"); } } else { throw new IllegalArgumentException( "The input key is not valid. " + "Key should be an instance of class " + "java.lang.String and should not be blank " + "The key entered is not a String"); } } else { throw new IllegalArgumentException( "Invalid input parameter! The input key is not valid. " + "Key should be an instance of class " + "java.lang.String and should not be blank " + "The key entered is null"); } } /** * <p> * This method checks for following contrainsts on a value is not null * </p> * . * * @param value * Object */ private void checkValue(final Object value) { if (value == null) { throw new IllegalArgumentException("The input value is not valid. Value cannot be null. "); } } /** * <p> * Return an <code>Iterator</code> over the set of values in this * <code>Map</code>. * </p> * * @return Iterator */ private Iterator valuesIterator() { return (new ValuesIterator()); } /** * <p> * Set the value for the specified property. * </p> * * @param descriptor * <code>PropertyDescriptor</code> for the specified property * @param value * The new value for this property (must be of the correct type) * */ private void writeProperty(final PropertyDescriptor descriptor, final Object value) { try { Method method = descriptor.getWriteMethod(); if (method == null) { throw new UnsupportedOperationException("Property '" + descriptor.getName() + "' is not writeable"); } method.invoke(this, new Object[] { value }); } catch (Exception e) { throw new UnsupportedOperationException( "Exception writing property '" + descriptor.getName() + "': " + e.getMessage()); } } /** * Returns the values that are stored in the localMap. * * @return String */ @Override public String toString() { return localMap.toString(); } }