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;
024import java.lang.ClassLoader;
025
026/**
027 * Static utility class used to wrap an `@Parcel` annotated class with the generated `Parcelable` wrapper.
028 *
029 * @author John Ericksen
030 */
031public final class Parcels {
032
033    public static final String IMPL_EXT = "Parcelable";
034
035    private static final ParcelCodeRepository REPOSITORY = new ParcelCodeRepository();
036
037    static{
038        REPOSITORY.loadRepository(NonParcelRepository.getInstance());
039    }
040
041    private Parcels(){
042        // private utility class constructor
043    }
044
045    /**
046     * Wraps the input `@Parcel` annotated class with a `Parcelable` wrapper.
047     *
048     * @throws ParcelerRuntimeException if there was an error looking up the wrapped Parceler$Parcels class.
049     * @param input Parcel
050     * @return Parcelable wrapper
051     */
052    @SuppressWarnings("unchecked")
053    public static <T> Parcelable wrap(T input) {
054        if(input == null){
055            return null;
056        }
057        return wrap(input.getClass(), input);
058    }
059
060    /**
061     * Wraps the input `@Parcel` annotated class with a `Parcelable` wrapper.
062     *
063     * @throws ParcelerRuntimeException if there was an error looking up the wrapped Parceler$Parcels class.
064     * @param inputType specific type to parcel
065     * @param input Parcel
066     * @return Parcelable wrapper
067     */
068    @SuppressWarnings("unchecked")
069    public static <T> Parcelable wrap(Class<? extends T> inputType, T input) {
070        if(input == null){
071            return null;
072        }
073        ParcelableFactory parcelableFactory = REPOSITORY.get(inputType);
074
075        return parcelableFactory.buildParcelable(input);
076    }
077
078    /**
079     * Unwraps the input wrapped `@Parcel` `Parcelable`
080     *
081     * @throws ClassCastException if the input Parcelable does not implement ParcelWrapper with the correct parameter type.
082     * @param input Parcelable implementing ParcelWrapper
083     * @param <T> type of unwrapped `@Parcel`
084     * @return Unwrapped `@Parcel`
085     */
086    @SuppressWarnings("unchecked")
087    public static <T> T unwrap(Parcelable input) {
088        if(input == null){
089            return null;
090        }
091        ParcelWrapper<T> wrapper = (ParcelWrapper<T>) input;
092        return wrapper.getParcel();
093    }
094
095    /**
096     * Factory class for building a `Parcelable` from the given input.
097     */
098    public interface ParcelableFactory<T> {
099
100        String BUILD_PARCELABLE = "buildParcelable";
101
102        /**
103         * Build the corresponding `Parcelable` class.
104         *
105         * @param input input to wrap with a Parcelable
106         * @return Parcelable instance
107         */
108        Parcelable buildParcelable(T input);
109    }
110
111    private static final class ParcelableFactoryReflectionProxy<T> implements ParcelableFactory<T> {
112
113        private final Constructor<? extends Parcelable> constructor;
114
115        public ParcelableFactoryReflectionProxy(Class<T> parcelClass, Class<? extends Parcelable> parcelWrapperClass) {
116            try {
117                this.constructor = parcelWrapperClass.getConstructor(parcelClass);
118            } catch (NoSuchMethodException e) {
119                throw new ParcelerRuntimeException("Unable to create ParcelFactory Type", e);
120            }
121        }
122
123        @Override
124        public Parcelable buildParcelable(T input) {
125            try {
126                return constructor.newInstance(input);
127            } catch (InstantiationException e) {
128                throw new ParcelerRuntimeException("Unable to create ParcelFactory Type", e);
129            } catch (IllegalAccessException e) {
130                throw new ParcelerRuntimeException("Unable to create ParcelFactory Type", e);
131            } catch (InvocationTargetException e) {
132                throw new ParcelerRuntimeException("Unable to create ParcelFactory Type", e);
133            }
134        }
135    }
136
137    private static final class ParcelCodeRepository {
138
139        private ConcurrentMap<Class, ParcelableFactory> generatedMap = new ConcurrentHashMap<Class, ParcelableFactory>();
140
141        public ParcelableFactory get(Class clazz){
142            ParcelableFactory result = generatedMap.get(clazz);
143            if (result == null) {
144                ParcelableFactory value = findClass(clazz, clazz.getClassLoader());
145
146                if (Parcelable.class.isAssignableFrom(clazz)) {
147                    value = new NonParcelRepository.ParcelableParcelableFactory();
148                }
149
150                if(value == null){
151                    throw new ParcelerRuntimeException(
152                                    "Unable to find generated Parcelable class for " + clazz.getName() +
153                                    ", verify that your class is configured properly and that the Parcelable class " +
154                                    buildParcelableImplName(clazz) +
155                                    " is generated by Parceler.");
156                }
157                result = generatedMap.putIfAbsent(clazz, value);
158                if (result == null) {
159                    result = value;
160                }
161            }
162
163            return result;
164        }
165
166        private static String buildParcelableImplName(Class clazz){
167            return clazz.getName() + "$$" + IMPL_EXT;
168        }
169
170        @SuppressWarnings("unchecked")
171        public ParcelableFactory findClass(Class clazz, ClassLoader classLoader){
172            try {
173                Class parcelWrapperClass = classLoader.loadClass(buildParcelableImplName(clazz));
174                return new ParcelableFactoryReflectionProxy(clazz, parcelWrapperClass);
175            } catch (ClassNotFoundException e) {
176                return null;
177            }
178        }
179
180        public void loadRepository(Repository<ParcelableFactory> repository){
181            generatedMap.putAll(repository.get());
182        }
183    }
184}