001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2018 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.core;
020
021import java.io.Serializable;
022import java.util.Map;
023import java.util.Optional;
024
025/**
026 * Combines a type and an id value to a key for an {@link Associator}
027 * or {@link Map}. This kind of key can be used if an instance of 
028 * the type as key is too specific (or objects should not be kept alive
029 * only because they are used as keys) and the identifier alone cannot 
030 * be guaranteed to be unique.
031 */
032@SuppressWarnings({ "serial", "PMD.ShortVariable" })
033public final class TypedIdKey<V> implements Serializable {
034
035    private final Class<V> type;
036    private final Serializable id;
037
038    private TypedIdKey(Class<V> type, Serializable id) {
039        this.type = type;
040        this.id = id;
041    }
042
043    /**
044     * Associates the given value's type and the id with the given value
045     * using the given associator. 
046     *
047     * @param <V> the value type
048     * @param associator the associator
049     * @param id the id
050     * @param value the value
051     * @return the value for easy chaining
052     */
053    @SuppressWarnings({ "unchecked" })
054    public static <V> V associate(Associator associator, Serializable id,
055            V value) {
056        associator.setAssociated(
057            new TypedIdKey<V>((Class<V>) value.getClass(), id), value);
058        return value;
059    }
060
061    /**
062     * Associates the given value's type and the id with the given value
063     * using the given map. 
064     *
065     * @param <V> the value type
066     * @param map the map
067     * @param id the id
068     * @param value the value
069     * @return the value for easy chaining
070     */
071    @SuppressWarnings({ "unchecked" })
072    public static <V> V put(Map<? super TypedIdKey<V>, ? super V> map,
073            Serializable id, V value) {
074        map.put(new TypedIdKey<V>((Class<V>) value.getClass(), id), value);
075        return value;
076    }
077
078    /**
079     * Retrieves a value with the given type and id from the given associator.
080     *
081     * @param <V> the value type
082     * @param associator the associator
083     * @param type the type
084     * @param id the id
085     * @return the associated value, if any
086     */
087    public static <V> Optional<V> associated(Associator associator,
088            Class<V> type, Serializable id) {
089        return associator.associated(new TypedIdKey<>(type, id), type);
090    }
091
092    /**
093     * Retrieves a value with the given type and id from the given map.
094     *
095     * @param <V> the value type
096     * @param map the map
097     * @param type the type
098     * @param id the id
099     * @return the associated value, if any
100     */
101    @SuppressWarnings({ "unchecked" })
102    public static <V> Optional<V> get(Map<?, ?> map, Class<V> type,
103            Serializable id) {
104        return Optional
105            .ofNullable((V) map.get(new TypedIdKey<>(type, id)));
106    }
107
108    @Override
109    @SuppressWarnings("PMD.DataflowAnomalyAnalysis")
110    public int hashCode() {
111        @SuppressWarnings("PMD.AvoidFinalLocalVariable")
112        final int prime = 31;
113        int result = 1;
114        result = prime * result + ((id == null) ? 0 : id.hashCode());
115        result = prime * result + ((type == null) ? 0 : type.hashCode());
116        return result;
117    }
118
119    @Override
120    public boolean equals(Object obj) {
121        if (this == obj) {
122            return true;
123        }
124        if (obj == null) {
125            return false;
126        }
127        if (getClass() != obj.getClass()) {
128            return false;
129        }
130        TypedIdKey<?> other = (TypedIdKey<?>) obj;
131        if (id == null) {
132            if (other.id != null) {
133                return false;
134            }
135        } else if (!id.equals(other.id)) {
136            return false;
137        }
138        if (type == null) {
139            if (other.type != null) {
140                return false;
141            }
142        } else if (!type.equals(other.type)) {
143            return false;
144        }
145        return true;
146    }
147
148    @Override
149    public String toString() {
150        return "TypedIdKey [" + type + ":" + id + "]";
151    }
152}