001/**
002 * Copyright 2013 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.androidtransfuse.processor;
017
018import org.androidtransfuse.TransfuseAnalysisException;
019import org.androidtransfuse.config.TransfuseAndroidModule;
020import org.androidtransfuse.model.Mergeable;
021import org.androidtransfuse.model.manifest.*;
022import org.androidtransfuse.util.AndroidLiterals;
023import org.apache.commons.beanutils.PropertyUtils;
024
025import javax.inject.Inject;
026import javax.inject.Named;
027import javax.inject.Singleton;
028import java.beans.BeanInfo;
029import java.beans.IntrospectionException;
030import java.beans.Introspector;
031import java.beans.PropertyDescriptor;
032import java.lang.annotation.Annotation;
033import java.lang.reflect.InvocationTargetException;
034import java.lang.reflect.Method;
035import java.util.ArrayList;
036import java.util.List;
037
038/**
039 * @author John Ericksen
040 */
041@Singleton
042public class ManifestManager {
043
044    private final List<Application> applications = new ArrayList<Application>();
045    private final String manifestPackage;
046    private final List<Activity> activities = new ArrayList<Activity>();
047    private final List<Receiver> broadcastReceivers = new ArrayList<Receiver>();
048    private final List<Service> services = new ArrayList<Service>();
049    private final List<UsesPermission> usesPermissions = new ArrayList<UsesPermission>();
050    private final List<UsesFeature> usesFeatures = new ArrayList<UsesFeature>();
051    private final List<Permission> permissions = new ArrayList<Permission>();
052    private UsesSDK usesSdk;
053
054    @Inject
055    public ManifestManager(@Named(TransfuseAndroidModule.ORIGINAL_MANIFEST) Manifest originalManifest) {
056        this.manifestPackage = originalManifest.getApplicationPackage();
057    }
058
059    public void addApplication(Application application) {
060        this.applications.add(application);
061    }
062
063    public void addPermission(Permission permission){
064        try {
065            updateMergeTags(Permission.class, permission);
066            permissions.add(permission);
067        } catch (MergerException e) {
068            throw new TransfuseAnalysisException("Unable to Merge UsesPermission", e);
069        }
070    }
071    
072    public void addUsesFeature(UsesFeature usesFeature){
073        try {
074            updateMergeTags(UsesFeature.class, usesFeature);
075            usesFeatures.add(usesFeature);
076        } catch (MergerException e) {
077            throw new TransfuseAnalysisException("Unable to Merge UsesFeature", e);
078        }
079    }
080
081    public void addUsesPermission(UsesPermission usesPermission) {
082        try {
083            updateMergeTags(UsesPermission.class, usesPermission);
084            usesPermissions.add(usesPermission);
085        } catch (MergerException e) {
086            throw new TransfuseAnalysisException("Unable to Merge UsesPermission", e);
087        }
088    }
089
090    public void setUsesSdk(UsesSDK usesSdk) {
091        this.usesSdk = usesSdk;
092    }
093
094    public void addActivity(Activity activity) {
095        try {
096            updateMergeTags(Activity.class, activity);
097            updateMergeTags(IntentFilter.class, activity.getIntentFilters());
098            updateMergeTags(MetaData.class, activity.getMetaData());
099            this.activities.add(activity);
100        } catch (MergerException e) {
101            throw new TransfuseAnalysisException("Unable to Merge Activity", e);
102        }
103    }
104
105    public void addBroadcastReceiver(Receiver broadcastReceiver) {
106        try {
107            updateMergeTags(Receiver.class, broadcastReceiver);
108            updateMergeTags(IntentFilter.class, broadcastReceiver.getIntentFilters());
109            updateMergeTags(MetaData.class, broadcastReceiver.getMetaData());
110            this.broadcastReceivers.add(broadcastReceiver);
111        } catch (MergerException e) {
112            throw new TransfuseAnalysisException("Unable to Merge Broadcast Receiver", e);
113        }
114    }
115
116    private <T extends Mergeable> void updateMergeTags(Class<T> clazz, List<T> mergeableCollection) throws MergerException {
117        for (T mergeable : mergeableCollection) {
118            updateMergeTags(clazz, mergeable);
119        }
120    }
121
122    public void addService(Service service) {
123        try {
124            updateMergeTags(Service.class, service);
125            updateMergeTags(IntentFilter.class, service.getIntentFilters());
126            updateMergeTags(MetaData.class, service.getMetaData());
127            this.services.add(service);
128        } catch (MergerException e) {
129            throw new TransfuseAnalysisException("Unable to Merge Service", e);
130        }
131    }
132
133    public Manifest getManifest() throws MergerException {
134        Manifest manifest = new Manifest();
135        manifest.setApplicationPackage(manifestPackage);
136
137        Application localApplication;
138
139        if (applications.isEmpty()){
140            localApplication = new Application();
141            localApplication.setName(AndroidLiterals.APPLICATION.getName());
142        }
143        else{
144            localApplication = applications.get(0);
145        }
146
147        //todo: do multiple applications result in an error?
148
149        updateMergeTags(Application.class, localApplication);
150
151        localApplication.getActivities().addAll(activities);
152        localApplication.getReceivers().addAll(broadcastReceivers);
153        localApplication.getServices().addAll(services);
154
155        manifest.getApplications().add(localApplication);
156
157        manifest.getUsesPermissions().addAll(usesPermissions);
158        
159        manifest.getUsesFeatures().addAll(usesFeatures);
160
161        manifest.getPermissions().addAll(permissions);
162
163        if(usesSdk != null){
164            manifest.getUsesSDKs().add(usesSdk);
165        }
166
167        manifest.updatePackages();
168
169        return manifest;
170    }
171
172    private <T extends Mergeable> void updateMergeTags(Class<T> clazz, T mergeable) throws MergerException {
173        try {
174            mergeable.setGenerated(true);
175
176            BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
177
178            for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
179                Method readMethod = propertyDescriptor.getReadMethod();
180                Method writeMethod = propertyDescriptor.getWriteMethod();
181
182                Merge mergeAnnotation = findAnnotation(Merge.class, writeMethod, readMethod);
183                Object property = PropertyUtils.getProperty(mergeable, propertyDescriptor.getName());
184
185                if (mergeAnnotation != null && property != null) {
186                    mergeable.addMergeTag(mergeAnnotation.value());
187                }
188            }
189        } catch (IntrospectionException e) {
190            throw new MergerException(e);
191        } catch (InvocationTargetException e) {
192            throw new MergerException(e);
193        } catch (NoSuchMethodException e) {
194            throw new MergerException(e);
195        } catch (IllegalAccessException e) {
196            throw new MergerException(e);
197        }
198    }
199
200    private <T extends Annotation> T findAnnotation(Class<T> annotationClass, Method... methods) {
201        T annotation = null;
202        if (methods != null) {
203            for (Method method : methods) {
204                if (annotation == null && method != null && method.isAnnotationPresent(annotationClass)) {
205                    annotation = method.getAnnotation(annotationClass);
206                }
207            }
208        }
209        return annotation;
210    }
211}