001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.bean;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022
023/**
024 * Represents a single entry in a bean map.
025 *
026 * <p>
027 * This class can be used to get and set property values on a bean, or to get metadata on a property.
028 *
029 * <h5 class='section'>Example:</h5>
030 * <p class='bjava'>
031 *    <jc>// Construct a new bean</jc>
032 *    Person <jv>person</jv> = <jk>new</jk> Person();
033 *
034 *    <jc>// Wrap it in a bean map</jc>
035 *    BeanMap&lt;Person&gt; <jv>beanMap</jv> = BeanMap.of(<jv>person</jv>);
036 *
037 *    <jc>// Get a reference to the birthDate property</jc>
038 *    BeanMapEntry <jv>birthDate</jv> = <jv>beanMap</jv>.getProperty(<js>"birthDate"</js>);
039 *
040 *    <jc>// Set the property value</jc>
041 *    <jv>birthDate</jv>.setValue(<jk>new</jk> Date(1, 2, 3, 4, 5, 6));
042 *
043 *    <jc>// Or if the DateSwap.DEFAULT_ISO8601DT is registered with the bean context, set a transformed value</jc>
044 *    <jv>birthDate</jv>.setValue(<js>"'1901-03-03T04:05:06-5000'"</js>);
045 * </p>
046 *
047 * <h5 class='topic'>Thread safety</h5>
048 *
049 * This is a lightweight immutable view over a {@link BeanMap}.
050 * Its thread-safety characteristics are the same as the backing {@link BeanMap}.
051 *
052 */
053@SuppressWarnings({
054   "java:S1452"  // Wildcard required - BeanMap<?> for generic bean map entries
055})
056public class BeanMapEntry implements Map.Entry<String,Object> {
057   private final BeanMap<?> beanMap;
058   private final BeanPropertyMeta meta;
059   private final String pName;
060
061   /**
062    * Constructor.
063    *
064    * @param beanMap The bean map that this entry belongs to.
065    * @param property The bean property.
066    * @param pName The bean property name.
067    */
068   protected BeanMapEntry(BeanMap<?> beanMap, BeanPropertyMeta property, String pName) {
069      this.beanMap = beanMap;
070      this.meta = property;
071      this.pName = pName;
072   }
073
074   /**
075    * Returns the bean map that contains this property.
076    *
077    * @return The bean map that contains this property.
078    */
079   public BeanMap<?> getBeanMap() { return this.beanMap; }
080
081   @Override /* Overridden from Map.Entry */
082   public String getKey() { return pName != null ? pName : meta.getName(); }
083
084   /**
085    * Returns the metadata about this bean property.
086    *
087    * @return Metadata about this bean property.
088    */
089   public BeanPropertyMeta getMeta() { return this.meta; }
090
091   /**
092    * Returns the value of this property.
093    *
094    * <p>
095    * If there is a {@code ObjectSwap} associated with this bean property or bean property type class, then this method
096    * will return the transformed value.
097    * For example, if the bean property type class is a {@link Date} and the bean property has the
098    * {@code DateFormat.ISO_INSTANT} setting associated with it through the
099    * {@code @Swap(value)} annotation, this method will return a String containing an
100    * ISO8601 date-time string value.
101    */
102   @Override /* Overridden from Map.Entry */
103   public Object getValue() { return meta.get(this.beanMap, pName); }
104
105   /**
106    * Sets the value of this property.
107    *
108    * <p>
109    * If the property is an array of type {@code X}, then the value can be a {@code Collection<X>} or {@code X[]} or
110    * {@code Object[]}.
111    *
112    * <p>
113    * If the property is a bean type {@code X}, then the value can either be an {@code X} or a {@code Map}.
114    *
115    * <p>
116    * If there is a {@code ObjectSwap} associated with this bean property or bean property type class, then you must pass
117    * in a transformed value.
118    * For example, if the bean property type class is a {@link Date} and the bean property has the
119    * {@code DateFormat.ISO_INSTANT} setting associated with it through the
120    * {@code @Swap(value)} annotation, the value being passed in must be a String
121    * containing an ISO8601 date-time string value.
122    *
123    * @param value The new value for this property.  Can be <jk>null</jk> to set the property to <jk>null</jk>.
124    * @return  The set value after it's been converted.
125    */
126   @Override /* Overridden from Map.Entry */
127   public Object setValue(Object value) {
128      return meta.set(this.beanMap, pName, value);
129   }
130
131   /**
132    * Returns <jk>true</jk> if the given object is a {@link java.util.Map.Entry} with equal key and value.
133    *
134    * <p>
135    * This follows the {@link java.util.Map.Entry} equality contract: two entries are equal if both their
136    * keys and values compare equal via {@link Objects#equals}.
137    *
138    * @param obj The object to compare against.
139    * @return <jk>true</jk> if equal.
140    */
141   @Override /* Overridden from Object */
142   public boolean equals(Object obj) {
143      if (this == obj)
144         return true;
145      if (!(obj instanceof Map.Entry<?,?> obj2))
146         return false;
147      return eq(getKey(), obj2.getKey()) && eq(getValue(), obj2.getValue());
148   }
149
150   /**
151    * Returns the hash code for this entry, consistent with the {@link java.util.Map.Entry} contract.
152    *
153    * <p>
154    * The hash code is computed as {@code key.hashCode() ^ value.hashCode()}, matching the
155    * contract that equal entries must have equal hash codes.
156    *
157    * @return The hash code.
158    */
159   @Override /* Overridden from Object */
160   public int hashCode() {
161      return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());
162   }
163
164   @Override /* Overridden from Object */
165   public String toString() {
166      return this.getKey() + "=" + this.getValue();
167   }
168}