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}