001/**
002 * Copyright 2011-2015 John Ericksen
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *    http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.parceler;
017
018import android.os.Parcelable;
019
020import java.lang.reflect.Constructor;
021import java.lang.reflect.InvocationTargetException;
022import java.util.concurrent.ConcurrentHashMap;
023import java.util.concurrent.ConcurrentMap;
024
025/**
026 * Static utility class used to wrap an `@Parcel` annotated class with the generated `Parcelable` wrapper.
027 *
028 * @author John Ericksen
029 */
030public final class Parcels {
031
032    public static final String IMPL_EXT = "Parcelable";
033
034    private static final ParcelCodeRepository REPOSITORY = new ParcelCodeRepository();
035
036    static{
037        REPOSITORY.loadRepository(NonParcelRepository.getInstance());
038    }
039
040    private Parcels(){
041        // private utility class constructor
042    }
043
044    /**
045     * Wraps the input `@Parcel` annotated class with a `Parcelable` wrapper.
046     *
047     * @throws ParcelerRuntimeException if there was an error looking up the wrapped Parceler$Parcels class.
048     * @param input Parcel
049     * @return Parcelable wrapper
050     */
051    @SuppressWarnings("unchecked")
052    public static <T> Parcelable wrap(T input) {
053        if(input == null){
054            return null;
055        }
056        return wrap(input.getClass(), input);
057    }
058
059    /**
060     * Wraps the input `@Parcel` annotated class with a `Parcelable` wrapper.
061     *
062     * @throws ParcelerRuntimeException if there was an error looking up the wrapped Parceler$Parcels class.
063     * @param inputType specific type to parcel
064     * @param input Parcel
065     * @return Parcelable wrapper
066     */
067    @SuppressWarnings("unchecked")
068    public static <T> Parcelable wrap(Class<? extends T> inputType, T input) {
069        if(input == null){
070            return null;
071        }
072        ParcelableFactory parcelableFactory = REPOSITORY.get(inputType);
073
074        return parcelableFactory.buildParcelable(input);
075    }
076
077    /**
078     * Unwraps the input wrapped `@Parcel` `Parcelable`
079     *
080     * @throws ClassCastException if the input Parcelable does not implement ParcelWrapper with the correct parameter type.
081     * @param input Parcelable implementing ParcelWrapper
082     * @param <T> type of unwrapped `@Parcel`
083     * @return Unwrapped `@Parcel`
084     */
085    @SuppressWarnings("unchecked")
086    public static <T> T unwrap(Parcelable input) {
087        if(input == null){
088            return null;
089        }
090        ParcelWrapper<T> wrapper = (ParcelWrapper<T>) input;
091        return wrapper.getParcel();
092    }
093
094    /**
095     * Factory class for building a `Parcelable` from the given input.
096     */
097    public interface ParcelableFactory<T> {
098
099        String BUILD_PARCELABLE = "buildParcelable";
100
101        /**
102         * Build the corresponding `Parcelable` class.
103         *
104         * @param input input to wrap with a Parcelable
105         * @return Parcelable instance
106         */
107        Parcelable buildParcelable(T input);
108    }
109
110    private static final class ParcelableFactoryReflectionProxy<T> implements ParcelableFactory<T> {
111
112        private final Constructor<? extends Parcelable> constructor;
113
114        public ParcelableFactoryReflectionProxy(Class<T> parcelClass, Class<? extends Parcelable> parcelWrapperClass) {
115            try {
116                this.constructor = parcelWrapperClass.getConstructor(parcelClass);
117            } catch (NoSuchMethodException e) {
118                throw new ParcelerRuntimeException("Unable to create ParcelFactory Type", e);
119            }
120        }
121
122        @Override
123        public Parcelable buildParcelable(T input) {
124            try {
125                return constructor.newInstance(input);
126            } catch (InstantiationException e) {
127                throw new ParcelerRuntimeException("Unable to create ParcelFactory Type", e);
128            } catch (IllegalAccessException e) {
129                throw new ParcelerRuntimeException("Unable to create ParcelFactory Type", e);
130            } catch (InvocationTargetException e) {
131                throw new ParcelerRuntimeException("Unable to create ParcelFactory Type", e);
132            }
133        }
134    }
135
136    private static final class ParcelCodeRepository {
137
138        private ConcurrentMap<Class, ParcelableFactory> generatedMap = new ConcurrentHashMap<Class, ParcelableFactory>();
139
140        public ParcelableFactory get(Class clazz){
141            ParcelableFactory result = generatedMap.get(clazz);
142            if (result == null) {
143                ParcelableFactory value = findClass(clazz);
144
145                if (Parcelable.class.isAssignableFrom(clazz)) {
146                    value = new NonParcelRepository.ParcelableParcelableFactory();
147                }
148
149                if(value == null){
150                    throw new ParcelerRuntimeException(
151                                    "Unable to find generated Parcelable class for " + clazz.getName() +
152                                    ", verify that your class is configured properly and that the Parcelable class " +
153                                    buildParcelableImplName(clazz) +
154                                    " is generated by Parceler.");
155                }
156                result = generatedMap.putIfAbsent(clazz, value);
157                if (result == null) {
158                    result = value;
159                }
160            }
161
162            return result;
163        }
164
165        private static String buildParcelableImplName(Class clazz){
166            return clazz.getName() + "$$" + IMPL_EXT;
167        }
168
169        @SuppressWarnings("unchecked")
170        public ParcelableFactory findClass(Class clazz){
171            try {
172                Class parcelWrapperClass = Class.forName(buildParcelableImplName(clazz));
173                return new ParcelableFactoryReflectionProxy(clazz, parcelWrapperClass);
174            } catch (ClassNotFoundException e) {
175                return null;
176            }
177        }
178
179        public void loadRepository(Repository<ParcelableFactory> repository){
180            generatedMap.putAll(repository.get());
181        }
182    }
183}