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}