001/**
002 * Copyright 2010-2016 Boxfuse GmbH
003 * <p/>
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 * <p/>
008 * http://www.apache.org/licenses/LICENSE-2.0
009 * <p/>
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.avaje.classpath.scanner.internal.scanner.classpath.android;
017
018import android.content.Context;
019import dalvik.system.DexFile;
020import dalvik.system.PathClassLoader;
021import org.avaje.classpath.scanner.core.ClassPathScanException;
022import org.avaje.classpath.scanner.core.Location;
023import org.avaje.classpath.scanner.ClassFilter;
024import org.avaje.classpath.scanner.ResourceFilter;
025import org.avaje.classpath.scanner.Resource;
026import org.avaje.classpath.scanner.andriod.ContextHolder;
027import org.avaje.classpath.scanner.internal.ResourceAndClassScanner;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031import java.io.IOException;
032import java.util.ArrayList;
033import java.util.Enumeration;
034import java.util.List;
035
036/**
037 * Class & resource scanner for Android.
038 */
039public class AndroidScanner implements ResourceAndClassScanner {
040
041  private static final Logger LOG = LoggerFactory.getLogger(AndroidScanner.class);
042
043  private final Context context;
044
045  private final PathClassLoader classLoader;
046
047  public AndroidScanner(ClassLoader classLoader) {
048    this.classLoader = (PathClassLoader) classLoader;
049    context = ContextHolder.getContext();
050    if (context == null) {
051      throw new ClassPathScanException("Unable to create scanner. " +
052          "Within an activity you can fix this with org.avaje.classpath.scanner.android.ContextHolder.setContext(this);");
053    }
054  }
055
056  public List<Resource> scanForResources(Location location, ResourceFilter predicate) {
057
058    try {
059      List<Resource> resources = new ArrayList<Resource>();
060      String path = location.getPath();
061      for (String asset : context.getAssets().list(path)) {
062        if (predicate.isMatch(asset)) {
063          resources.add(new AndroidResource(context.getAssets(), path, asset));
064        }
065      }
066
067      return resources;
068
069    } catch (IOException e) {
070      throw new ClassPathScanException(e);
071    }
072  }
073
074  public List<Class<?>> scanForClasses(Location location, ClassFilter predicate) {
075
076    try {
077
078      String pkg = location.getPath().replace("/", ".");
079
080      List<Class<?>> classes = new ArrayList<Class<?>>();
081
082      DexFile dex = new DexFile(context.getApplicationInfo().sourceDir);
083      Enumeration<String> entries = dex.entries();
084      while (entries.hasMoreElements()) {
085        String className = entries.nextElement();
086        if (className.startsWith(pkg)) {
087          Class<?> clazz = classLoader.loadClass(className);
088          if (predicate.isMatch(clazz)) {
089            classes.add(clazz);
090            LOG.trace("... found class: {}", className);
091          }
092        }
093      }
094      return classes;
095
096    } catch (IOException e) {
097      throw new ClassPathScanException(e);
098
099    } catch (ClassNotFoundException e) {
100      throw new ClassPathScanException(e);
101    }
102  }
103}