001/* 002 * Copyright c 2018 Rusi Popov, MDA Tools.net All rights reserved. 003 * 004 * This program and the accompanying materials are made available under the terms of the 005 * Eclipse Public License v2.0 which accompanies this distribution, and is available at 006 * http://www.eclipse.org/legal/epl-v20.html 007 */ 008package net.mdatools.modelant.core.wrap; 009 010import java.lang.reflect.Constructor; 011import java.util.ArrayList; 012import java.util.Collection; 013import java.util.IdentityHashMap; 014import java.util.List; 015import java.util.Map; 016 017import net.mdatools.modelant.core.api.wrap.Wrapper; 018import net.mdatools.modelant.core.api.wrap.WrapperFactory; 019 020/** 021 * This class is the root class for all factories of wrapper classes. 022 * CONVENTION:<ul> 023 * <li> each Factory subclass must have all its constructors <b>protected</b>, where one of them must be without parameters. 024 * This prevents accidental instantiation of the factory itself this way violating its Singleton property. 025 * <li> each Factory instance caches internally all the wrapper objects it creates, in order to guarantee: <pre> 026 * (for each X, Y)( factory.wrap(X) == factory.wrap(X) <=> X == Y) 027 * </pre> 028 * <li> The subclasses must provide the mapping from wrapped to wrapper classes. 029 * </ul> 030 * @author Rusi Popov (popovr@mdatools.net) 031 */ 032public abstract class BaseWrapperFactory implements WrapperFactory { 033 034 /** 035 * Maps wrapped to wrapper objects, so that whenever a new wrapper is requested 036 * for an object, its already existing wrapper will be re-used. 037 */ 038 private final Map<Object, Wrapper<? extends Object>> wrappedToWrapperMap = new IdentityHashMap<>(1023); 039 040 041 /** 042 * @param toWrap could be null 043 * @return a non-null instance unique for each non-null wrapped object or null, when wrapped is null 044 * @throws IllegalArgumentException when there is no wrapper class corresponding to the class of toWrap 045 */ 046 public final <A> Wrapper<A> wrap(A toWrap) throws IllegalArgumentException { 047 Wrapper<A> result; 048 049 if ( toWrap == null ) { 050 result = null; 051 } else { 052 result = (Wrapper<A>) wrappedToWrapperMap.get( toWrap ); 053 if ( result == null ) { 054 result = constructWrapper( toWrap ); 055 wrappedToWrapperMap.put(toWrap, result ); 056 } 057 } 058 return result; 059 } 060 061 062 /** 063 * @param toWrap is a non-null collection of (any) objects to wrap 064 * @return a non-null ArrayList of wrapped objects. Note that the type is important, because 065 * it is used in any further wrapping 066 * @throws IllegalArgumentException when there is no wrapper class for an object to wrap 067 * @see #wrap(Object) 068 */ 069 public final <A> List<Wrapper<A>> wrap(Collection<A> toWrap) throws IllegalArgumentException { 070 List<Wrapper<A>> result; 071 072 if ( toWrap == null ) { 073 result = null; 074 } else { 075 result = new ArrayList<>( toWrap.size() ); 076 for (A obj : toWrap ) { 077 result.add( wrap( obj ) ); 078 } 079 } 080 return result; 081 } 082 083 /** 084 * Make sure this method is the last method called on a factory instance. 085 */ 086 public final void destroy() { 087 wrappedToWrapperMap.clear(); 088 } 089 090 /** 091 * This method binds an already built wrapper for a wrapped object that <b>has not been used</b> in this factory. 092 * Thus, any further call to wrap( wrapper.getWrapped() ) would return the wrapper object. 093 * @param wrapper is a non-null wrapper object 094 * @throws IllegalArgumentException when there is already a wrapper registered for wrapper.getWrapped() 095 */ 096 public final <A> void bind(Wrapper<A> wrapper) throws IllegalArgumentException { 097 Wrapper<A> existing; 098 099 existing = (Wrapper<A>) wrappedToWrapperMap.put(wrapper.getWrapped(), wrapper ); 100 if ( existing != null ) { 101 throw new IllegalArgumentException( "Registering a wrapper for an object that was already wrapped" ); 102 } 103 } 104 105 /** 106 * @return a non null initialized wrapper instance or an exception is thrown. 107 * It is Wrapper by default 108 */ 109 private <A> Wrapper<A> constructWrapper(A toWrap) throws IllegalArgumentException { 110 Wrapper<A> result; 111 Class resultClass; 112 Constructor constructor; 113 114 // because the wrapper class could be mapped by class or interface, try finding it both ways 115 resultClass = getMappedClass( toWrap ); 116 try { 117 constructor = resultClass.getConstructor(Object.class, WrapperFactory.class); 118 result = (Wrapper<A>) constructor.newInstance( toWrap, this ); 119 } catch (Exception ex) { 120 throw new IllegalArgumentException( ex ); 121 } 122 return result; 123 } 124 125 126 /** 127 * Recursively find the root wrapped object and use its class (or interface) to map it to the actual wrapper class 128 * @param toWrap not null object to wrap, which by itself might be a wrapper 129 * @return the wrapper class 130 * @throws IllegalArgumentException when no wrapper class found 131 */ 132 private <A> Class getMappedClass(A toWrap) throws IllegalArgumentException { 133 Class sourceClass; 134 Class result; 135 136 if ( toWrap instanceof Wrapper ) { 137 result = getMappedClass(((Wrapper) toWrap).getWrapped()); 138 139 } else { 140 sourceClass = toWrap.getClass(); 141 result = getMappedClass( sourceClass ); 142 143 if ( result == null && sourceClass.getInterfaces().length > 0) { 144 result = getMappedClass( sourceClass.getInterfaces()[0] ); 145 146 if (result == null) { 147 throw new IllegalArgumentException( "Could not identify the wrapper class for: "+toWrap ); 148 } 149 } 150 } 151 return result; 152 } 153 154 /** 155 * This method maps a class to wrap to the corresponding wrapper class 156 * @param original is a non-null class to wrap 157 * @return a possibly null wrapper class for that original class. Null is returned 158 * when there is no class to map to. 159 */ 160 protected abstract Class<? extends Wrapper> getMappedClass(Class<?> original); 161}