diff --git a/acra-core/build.gradle b/acra-core/build.gradle index 02e6d4224..1b358c86c 100644 --- a/acra-core/build.gradle +++ b/acra-core/build.gradle @@ -25,7 +25,6 @@ android { } dependencies { - //noinspection GradleDependency implementation "com.android.support:support-compat:$supportVersion" api project(':acra-javacore') compileOnly "com.google.auto.service:auto-service:$autoServiceVersion" diff --git a/acra-core/src/main/AndroidManifest.xml b/acra-core/src/main/AndroidManifest.xml index a2a125121..01a935903 100644 --- a/acra-core/src/main/AndroidManifest.xml +++ b/acra-core/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ @@ -200,10 +200,10 @@ public static void init(@NonNull Application app, @NonNull CoreConfiguration con log.d(LOG_TAG, "Not initialising ACRA to listen for uncaught Exceptions as this is the SendWorker process and we only send reports, we don't capture them to avoid infinite loops"); } - final boolean supportedAndroidVersion = Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; + final boolean supportedAndroidVersion = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; if (!supportedAndroidVersion) { // NB We keep initialising so that everything is configured. But ACRA is never enabled below. - log.w(LOG_TAG, "ACRA 5.0.0+ requires Gingerbread or greater. ACRA is disabled and will NOT catch crashes or send messages."); + log.w(LOG_TAG, "ACRA 5.1.0+ requires ICS or greater. ACRA is disabled and will NOT catch crashes or send messages."); } if (isInitialised()) { @@ -222,7 +222,7 @@ public static void init(@NonNull Application app, @NonNull CoreConfiguration con new LegacyFileHandler(app, prefs).updateToCurrentVersionIfNecessary(); if (!senderServiceProcess) { // Initialize ErrorReporter with all required data - final boolean enableAcra = supportedAndroidVersion && !SharedPreferencesFactory.shouldDisableACRA(prefs); + final boolean enableAcra = supportedAndroidVersion && SharedPreferencesFactory.shouldEnableACRA(prefs); // Indicate that ACRA is or is not listening for crashes. log.i(LOG_TAG, "ACRA is " + (enableAcra ? "enabled" : "disabled") + " for " + app.getPackageName() + ", initializing..."); ErrorReporterImpl reporter = new ErrorReporterImpl(app, config, enableAcra, supportedAndroidVersion); @@ -246,7 +246,7 @@ public static void init(@NonNull Application app, @NonNull CoreConfiguration con */ @SuppressWarnings("unused") public static boolean isInitialised() { - return errorReporterSingleton.isRegistered(); + return errorReporterSingleton instanceof ErrorReporterImpl; } /** diff --git a/acra-core/src/main/java/org/acra/ErrorReporter.java b/acra-core/src/main/java/org/acra/ErrorReporter.java index a355050ac..947d8fda1 100644 --- a/acra-core/src/main/java/org/acra/ErrorReporter.java +++ b/acra-core/src/main/java/org/acra/ErrorReporter.java @@ -64,11 +64,6 @@ public interface ErrorReporter { */ void setEnabled(boolean enabled); - /** - * @return if this instance is the current DefaultUncaughtExceptionHandler - */ - boolean isRegistered(); - /** * Send a normal report for the given exception * diff --git a/acra-core/src/main/java/org/acra/annotation/AcraCore.java b/acra-core/src/main/java/org/acra/annotation/AcraCore.java index d7774c465..bfdf1c525 100644 --- a/acra-core/src/main/java/org/acra/annotation/AcraCore.java +++ b/acra-core/src/main/java/org/acra/annotation/AcraCore.java @@ -48,7 +48,7 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited -@Configuration(baseBuilderClass = BaseCoreConfigurationBuilder.class, createBuilderFactory = false) +@Configuration(baseBuilderClass = BaseCoreConfigurationBuilder.class, isPlugin = false) public @interface AcraCore { /** diff --git a/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.java b/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.java index a72fc7502..b5a280c86 100644 --- a/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.java +++ b/acra-core/src/main/java/org/acra/attachment/AcraContentProvider.java @@ -54,7 +54,7 @@ public class AcraContentProvider extends ContentProvider { private static final String[] COLUMNS = { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}; - public static final String MIME_TYPE_OCTET_STREAM = "application/octet-stream"; + private static final String MIME_TYPE_OCTET_STREAM = "application/octet-stream"; private String authority; @Override @@ -219,6 +219,7 @@ public static Uri getUriForFile(@NonNull Context context, @NonNull File file) { * @param relativePath the file path * @return the uri */ + @SuppressWarnings("WeakerAccess") @NonNull public static Uri getUriForFile(@NonNull Context context, @NonNull Directory directory, @NonNull String relativePath) { final Uri.Builder builder = new Uri.Builder() diff --git a/acra-core/src/main/java/org/acra/builder/LastActivityManager.java b/acra-core/src/main/java/org/acra/builder/LastActivityManager.java index 151686caf..52c68df8a 100644 --- a/acra-core/src/main/java/org/acra/builder/LastActivityManager.java +++ b/acra-core/src/main/java/org/acra/builder/LastActivityManager.java @@ -17,11 +17,9 @@ import android.app.Activity; import android.app.Application; -import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; - import org.acra.ACRA; import java.lang.ref.WeakReference; @@ -44,50 +42,46 @@ public final class LastActivityManager { * @param application the application to attach to */ public LastActivityManager(@NonNull Application application) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - - // ActivityLifecycleCallback only available for API14+ - application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { - @Override - public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityCreated " + activity.getClass()); - lastActivityCreated = new WeakReference<>(activity); - } + application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) { + if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityCreated " + activity.getClass()); + lastActivityCreated = new WeakReference<>(activity); + } - @Override - public void onActivityStarted(@NonNull Activity activity) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityStarted " + activity.getClass()); - } + @Override + public void onActivityStarted(@NonNull Activity activity) { + if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityStarted " + activity.getClass()); + } - @Override - public void onActivityResumed(@NonNull Activity activity) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityResumed " + activity.getClass()); - } + @Override + public void onActivityResumed(@NonNull Activity activity) { + if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityResumed " + activity.getClass()); + } - @Override - public void onActivityPaused(@NonNull Activity activity) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityPaused " + activity.getClass()); - } + @Override + public void onActivityPaused(@NonNull Activity activity) { + if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityPaused " + activity.getClass()); + } - @Override - public void onActivityStopped(@NonNull Activity activity) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityStopped " + activity.getClass()); - synchronized (this) { - notify(); - } + @Override + public void onActivityStopped(@NonNull Activity activity) { + if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityStopped " + activity.getClass()); + synchronized (this) { + notify(); } + } - @Override - public void onActivitySaveInstanceState(@NonNull Activity activity, Bundle outState) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivitySaveInstanceState " + activity.getClass()); - } + @Override + public void onActivitySaveInstanceState(@NonNull Activity activity, Bundle outState) { + if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivitySaveInstanceState " + activity.getClass()); + } - @Override - public void onActivityDestroyed(@NonNull Activity activity) { - if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityDestroyed " + activity.getClass()); - } - }); - } + @Override + public void onActivityDestroyed(@NonNull Activity activity) { + if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "onActivityDestroyed " + activity.getClass()); + } + }); } /** @@ -111,12 +105,10 @@ public void clearLastActivity() { * @param timeOutInMillis timeout for wait */ public void waitForActivityStop(int timeOutInMillis) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - synchronized (this) { - try { - wait(timeOutInMillis); - } catch (InterruptedException ignored) { - } + synchronized (this) { + try { + wait(timeOutInMillis); + } catch (InterruptedException ignored) { } } } diff --git a/acra-core/src/main/java/org/acra/builder/ReportExecutor.java b/acra-core/src/main/java/org/acra/builder/ReportExecutor.java index e8e65a60b..fbabb7e60 100644 --- a/acra-core/src/main/java/org/acra/builder/ReportExecutor.java +++ b/acra-core/src/main/java/org/acra/builder/ReportExecutor.java @@ -33,6 +33,7 @@ import org.acra.interaction.ReportInteractionExecutor; import org.acra.sender.SenderServiceStarter; import org.acra.util.ProcessFinisher; +import org.acra.util.ToastSender; import java.io.File; import java.util.ArrayList; @@ -199,7 +200,7 @@ public final void execute(@NonNull final ReportBuilder reportBuilder) { @Override public void run() { Looper.prepare(); - Toast.makeText(context, warning, Toast.LENGTH_LONG).show(); + ToastSender.sendToast(context, warning, Toast.LENGTH_LONG); Looper.loop(); } }).start(); diff --git a/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.java b/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.java index a232af86a..c87899947 100644 --- a/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.java +++ b/acra-core/src/main/java/org/acra/collector/BaseReportFieldCollector.java @@ -89,7 +89,7 @@ public final void collect(@NonNull Context context, @NonNull CoreConfiguration c * @param target put results here * @throws Exception if collection failed */ - abstract void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception; + abstract void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception; /** * {@inheritDoc} diff --git a/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.java b/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.java index b5dcb04a7..ee35ed00b 100644 --- a/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.java +++ b/acra-core/src/main/java/org/acra/collector/ConfigurationCollector.java @@ -70,7 +70,7 @@ public ConfigurationCollector() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { switch (reportField) { case INITIAL_CONFIGURATION: @@ -129,6 +129,7 @@ private JSONObject configToJson(@NonNull Configuration conf) { return result; } + @NonNull private Map> getValueArrays() { final Map> valueArrays = new HashMap<>(); final SparseArray hardKeyboardHiddenValues = new SparseArray<>(); @@ -197,7 +198,7 @@ private Map> getValueArrays() { * constant name. * @throws IllegalAccessException if the supplied field is inaccessible. */ - private Object getFieldValueName(Map> valueArrays, @NonNull Configuration conf, @NonNull Field f) throws IllegalAccessException { + private Object getFieldValueName(@NonNull Map> valueArrays, @NonNull Configuration conf, @NonNull Field f) throws IllegalAccessException { final String fieldName = f.getName(); switch (fieldName) { case FIELD_MCC: diff --git a/acra-core/src/main/java/org/acra/collector/CustomDataCollector.java b/acra-core/src/main/java/org/acra/collector/CustomDataCollector.java index 21f9bf0fe..af5099fa7 100644 --- a/acra-core/src/main/java/org/acra/collector/CustomDataCollector.java +++ b/acra-core/src/main/java/org/acra/collector/CustomDataCollector.java @@ -40,7 +40,7 @@ public CustomDataCollector(){ } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { target.put(ReportField.CUSTOM_DATA, new JSONObject(reportBuilder.getCustomData())); } } diff --git a/acra-core/src/main/java/org/acra/collector/DeviceFeaturesCollector.java b/acra-core/src/main/java/org/acra/collector/DeviceFeaturesCollector.java index 8ae87bdc4..6064f06e3 100644 --- a/acra-core/src/main/java/org/acra/collector/DeviceFeaturesCollector.java +++ b/acra-core/src/main/java/org/acra/collector/DeviceFeaturesCollector.java @@ -42,7 +42,7 @@ public DeviceFeaturesCollector() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws JSONException { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws JSONException { final JSONObject result = new JSONObject(); final PackageManager pm = context.getPackageManager(); final FeatureInfo[] features = pm.getSystemAvailableFeatures(); diff --git a/acra-core/src/main/java/org/acra/collector/DeviceIdCollector.java b/acra-core/src/main/java/org/acra/collector/DeviceIdCollector.java index 411e2cd92..f2bddfe36 100644 --- a/acra-core/src/main/java/org/acra/collector/DeviceIdCollector.java +++ b/acra-core/src/main/java/org/acra/collector/DeviceIdCollector.java @@ -54,7 +54,7 @@ boolean shouldCollect(@NonNull Context context, @NonNull CoreConfiguration confi @SuppressLint("HardwareIds") @RequiresPermission(Manifest.permission.READ_PHONE_STATE) @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { target.put(ReportField.DEVICE_ID, SystemServices.getTelephonyManager(context).getDeviceId()); } } diff --git a/acra-core/src/main/java/org/acra/collector/DisplayManagerCollector.java b/acra-core/src/main/java/org/acra/collector/DisplayManagerCollector.java index c3f134358..0b65bd269 100644 --- a/acra-core/src/main/java/org/acra/collector/DisplayManagerCollector.java +++ b/acra-core/src/main/java/org/acra/collector/DisplayManagerCollector.java @@ -53,7 +53,7 @@ public DisplayManagerCollector() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { final JSONObject result = new JSONObject(); for (Display display : DisplayManagerCompat.getInstance(context).getDisplays()) { try { @@ -90,7 +90,7 @@ private JSONObject collectDisplayData(@NonNull Display display) throws JSONExcep return result; } - private void collectIsValid(@NonNull Display display, JSONObject container) throws JSONException { + private void collectIsValid(@NonNull Display display, @NonNull JSONObject container) throws JSONException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { container.put("isValid", display.isValid()); } @@ -116,23 +116,19 @@ private String rotationToString(int rotation) { } } - private void collectRectSize(@NonNull Display display, JSONObject container) throws JSONException { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { - final Rect size = new Rect(); - display.getRectSize(size); - container.put("rectSize", new JSONArray(Arrays.asList(size.top, size.left, size.width(), size.height()))); - } + private void collectRectSize(@NonNull Display display, @NonNull JSONObject container) throws JSONException { + final Rect size = new Rect(); + display.getRectSize(size); + container.put("rectSize", new JSONArray(Arrays.asList(size.top, size.left, size.width(), size.height()))); } - private void collectSize(@NonNull Display display, JSONObject container) throws JSONException { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) { - final Point size = new Point(); - display.getSize(size); - container.put("size", new JSONArray(Arrays.asList(size.x, size.y))); - } + private void collectSize(@NonNull Display display, @NonNull JSONObject container) throws JSONException { + final Point size = new Point(); + display.getSize(size); + container.put("size", new JSONArray(Arrays.asList(size.x, size.y))); } - private void collectRealSize(@NonNull Display display, JSONObject container) throws JSONException { + private void collectRealSize(@NonNull Display display, @NonNull JSONObject container) throws JSONException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { final Point size = new Point(); display.getRealSize(size); @@ -168,13 +164,13 @@ private void collectFlags(@NonNull Display display, @NonNull JSONObject containe } } - private void collectName(@NonNull Display display, JSONObject container) throws JSONException { + private void collectName(@NonNull Display display, @NonNull JSONObject container) throws JSONException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { container.put("name", display.getName()); } } - private void collectMetrics(@NonNull Display display, JSONObject container) throws JSONException { + private void collectMetrics(@NonNull Display display, @NonNull JSONObject container) throws JSONException { final DisplayMetrics metrics = new DisplayMetrics(); display.getMetrics(metrics); final JSONObject result = new JSONObject(); @@ -182,7 +178,7 @@ private void collectMetrics(@NonNull Display display, JSONObject container) thro container.put("metrics", result); } - private void collectRealMetrics(@NonNull Display display, JSONObject container) throws JSONException { + private void collectRealMetrics(@NonNull Display display, @NonNull JSONObject container) throws JSONException { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { final DisplayMetrics metrics = new DisplayMetrics(); display.getRealMetrics(metrics); @@ -192,7 +188,7 @@ private void collectRealMetrics(@NonNull Display display, JSONObject container) } } - private void collectMetrics(@NonNull DisplayMetrics metrics, JSONObject container) throws JSONException { + private void collectMetrics(@NonNull DisplayMetrics metrics, @NonNull JSONObject container) throws JSONException { container.put("density", metrics.density) .put("densityDpi", metrics.densityDpi) .put("scaledDensity", "x" + metrics.scaledDensity) diff --git a/acra-core/src/main/java/org/acra/collector/DropBoxCollector.java b/acra-core/src/main/java/org/acra/collector/DropBoxCollector.java index 860e3bc2d..a42966bd8 100644 --- a/acra-core/src/main/java/org/acra/collector/DropBoxCollector.java +++ b/acra-core/src/main/java/org/acra/collector/DropBoxCollector.java @@ -71,7 +71,7 @@ public Order getOrder() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception{ + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception{ final DropBoxManager dropbox = SystemServices.getDropBoxManager(context); final Calendar calendar = Calendar.getInstance(); diff --git a/acra-core/src/main/java/org/acra/collector/LogCatCollector.java b/acra-core/src/main/java/org/acra/collector/LogCatCollector.java index e0f8b430a..8354c9d50 100644 --- a/acra-core/src/main/java/org/acra/collector/LogCatCollector.java +++ b/acra-core/src/main/java/org/acra/collector/LogCatCollector.java @@ -97,7 +97,7 @@ private String collectLogCat(@NonNull CoreConfiguration config, @Nullable String try { return streamToString(config, process.getInputStream(), myPidStr == null ? null : new Predicate() { @Override - public boolean apply(String s) { + public boolean apply(@NonNull String s) { return s.contains(myPidStr); } }, tailCount); @@ -114,7 +114,7 @@ boolean shouldCollect(@NonNull Context context, @NonNull CoreConfiguration confi } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws IOException { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws IOException { String bufferName = null; switch (reportField) { case LOGCAT: diff --git a/acra-core/src/main/java/org/acra/collector/LogFileCollector.java b/acra-core/src/main/java/org/acra/collector/LogFileCollector.java index 81f6c988c..a34212012 100644 --- a/acra-core/src/main/java/org/acra/collector/LogFileCollector.java +++ b/acra-core/src/main/java/org/acra/collector/LogFileCollector.java @@ -48,7 +48,7 @@ public Order getOrder() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws IOException { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws IOException { target.put(ReportField.APPLICATION_LOG, new StreamReader(config.applicationLogFileDir().getFile(context, config.applicationLogFile())) .setLimit(config.applicationLogFileLines()).read()); } diff --git a/acra-core/src/main/java/org/acra/collector/MediaCodecListCollector.java b/acra-core/src/main/java/org/acra/collector/MediaCodecListCollector.java index a646cd49f..48c9100b0 100644 --- a/acra-core/src/main/java/org/acra/collector/MediaCodecListCollector.java +++ b/acra-core/src/main/java/org/acra/collector/MediaCodecListCollector.java @@ -77,7 +77,7 @@ public Order getOrder() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws JSONException { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws JSONException { target.put(ReportField.MEDIA_CODEC_LIST, collectMediaCodecList()); } diff --git a/acra-core/src/main/java/org/acra/collector/MemoryInfoCollector.java b/acra-core/src/main/java/org/acra/collector/MemoryInfoCollector.java index 735c63d78..096767135 100644 --- a/acra-core/src/main/java/org/acra/collector/MemoryInfoCollector.java +++ b/acra-core/src/main/java/org/acra/collector/MemoryInfoCollector.java @@ -55,7 +55,7 @@ boolean shouldCollect(@NonNull Context context, @NonNull CoreConfiguration confi } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { switch (reportField) { case DUMPSYS_MEMINFO: target.put(ReportField.DUMPSYS_MEMINFO, collectMemInfo()); diff --git a/acra-core/src/main/java/org/acra/collector/PackageManagerCollector.java b/acra-core/src/main/java/org/acra/collector/PackageManagerCollector.java index 1748f2a67..b5b4e317a 100644 --- a/acra-core/src/main/java/org/acra/collector/PackageManagerCollector.java +++ b/acra-core/src/main/java/org/acra/collector/PackageManagerCollector.java @@ -41,7 +41,7 @@ public PackageManagerCollector() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws CollectorException { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws CollectorException { final PackageInfo info = new PackageManagerWrapper(context).getPackageInfo(); if (info == null) { throw new CollectorException("Failed to get package info"); diff --git a/acra-core/src/main/java/org/acra/collector/ReflectionCollector.java b/acra-core/src/main/java/org/acra/collector/ReflectionCollector.java index b384cfe47..4099ff2e6 100644 --- a/acra-core/src/main/java/org/acra/collector/ReflectionCollector.java +++ b/acra-core/src/main/java/org/acra/collector/ReflectionCollector.java @@ -51,7 +51,7 @@ public ReflectionCollector() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws JSONException, ClassNotFoundException { final JSONObject result = new JSONObject(); switch (reportField) { @@ -104,7 +104,7 @@ private static void collectConstants(@NonNull Class someClass, @NonNull JSONO * * @param someClass the class to be inspected. */ - private void collectStaticGettersResults(@NonNull Class someClass, JSONObject container) throws JSONException { + private void collectStaticGettersResults(@NonNull Class someClass, @NonNull JSONObject container) throws JSONException { final Method[] methods = someClass.getMethods(); for (final Method method : methods) { if (method.getParameterTypes().length == 0 diff --git a/acra-core/src/main/java/org/acra/collector/SettingsCollector.java b/acra-core/src/main/java/org/acra/collector/SettingsCollector.java index d18d21cb3..2ff2099f8 100644 --- a/acra-core/src/main/java/org/acra/collector/SettingsCollector.java +++ b/acra-core/src/main/java/org/acra/collector/SettingsCollector.java @@ -54,7 +54,7 @@ public SettingsCollector() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { switch (reportField) { case SETTINGS_SYSTEM: target.put(ReportField.SETTINGS_SYSTEM, collectSettings(context, config, System.class)); @@ -71,6 +71,7 @@ void collect(ReportField reportField, @NonNull Context context, @NonNull CoreCon } } + @NonNull private JSONObject collectSettings(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull Class settings) throws NoSuchMethodException { final JSONObject result = new JSONObject(); final Field[] keys = settings.getFields(); @@ -78,6 +79,7 @@ private JSONObject collectSettings(@NonNull Context context, @NonNull CoreConfig for (final Field key : keys) { if (!key.isAnnotationPresent(Deprecated.class) && key.getType() == String.class && isAuthorized(config, key)) { try { + //noinspection JavaReflectionInvocation final Object value = getString.invoke(null, context.getContentResolver(), key.get(null)); if (value != null) { result.put(key.getName(), value); diff --git a/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.java b/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.java index 7c1773d81..beb89b6ee 100644 --- a/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.java +++ b/acra-core/src/main/java/org/acra/collector/SharedPreferencesCollector.java @@ -48,7 +48,7 @@ public SharedPreferencesCollector() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { switch (reportField) { case USER_EMAIL: target.put(ReportField.USER_EMAIL, new SharedPreferencesFactory(context, config).create().getString(ACRA.PREF_USER_EMAIL_ADDRESS, null)); diff --git a/acra-core/src/main/java/org/acra/collector/SimpleValuesCollector.java b/acra-core/src/main/java/org/acra/collector/SimpleValuesCollector.java index 6e822fc17..ee1cfa614 100644 --- a/acra-core/src/main/java/org/acra/collector/SimpleValuesCollector.java +++ b/acra-core/src/main/java/org/acra/collector/SimpleValuesCollector.java @@ -49,7 +49,7 @@ public SimpleValuesCollector() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { switch (reportField) { case IS_SILENT: target.put(ReportField.IS_SILENT, reportBuilder.isSendSilently()); diff --git a/acra-core/src/main/java/org/acra/collector/StacktraceCollector.java b/acra-core/src/main/java/org/acra/collector/StacktraceCollector.java index d4993d3aa..daf41ce9b 100644 --- a/acra-core/src/main/java/org/acra/collector/StacktraceCollector.java +++ b/acra-core/src/main/java/org/acra/collector/StacktraceCollector.java @@ -50,7 +50,7 @@ public Order getOrder() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { switch (reportField) { case STACK_TRACE: target.put(ReportField.STACK_TRACE, getStackTrace(reportBuilder.getMessage(), reportBuilder.getException())); diff --git a/acra-core/src/main/java/org/acra/collector/ThreadCollector.java b/acra-core/src/main/java/org/acra/collector/ThreadCollector.java index e5652529f..d8839ce8f 100644 --- a/acra-core/src/main/java/org/acra/collector/ThreadCollector.java +++ b/acra-core/src/main/java/org/acra/collector/ThreadCollector.java @@ -45,7 +45,7 @@ public Order getOrder() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { final Thread t = reportBuilder.getUncaughtExceptionThread(); if (t != null) { final JSONObject result = new JSONObject(); diff --git a/acra-core/src/main/java/org/acra/collector/TimeCollector.java b/acra-core/src/main/java/org/acra/collector/TimeCollector.java index 2ea7fd0f7..905c6414d 100644 --- a/acra-core/src/main/java/org/acra/collector/TimeCollector.java +++ b/acra-core/src/main/java/org/acra/collector/TimeCollector.java @@ -48,7 +48,7 @@ public TimeCollector() { } @Override - void collect(ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) throws Exception { + void collect(@NonNull ReportField reportField, @NonNull Context context, @NonNull CoreConfiguration config, @NonNull ReportBuilder reportBuilder, @NonNull CrashReportData target) { final Calendar time; switch (reportField) { case USER_APP_START_DATE: diff --git a/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.java b/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.java index 830033a59..115e02686 100644 --- a/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.java +++ b/acra-core/src/main/java/org/acra/config/BaseCoreConfigurationBuilder.java @@ -15,23 +15,19 @@ */ package org.acra.config; +import android.content.Context; import android.support.annotation.NonNull; - import org.acra.ACRA; import org.acra.ReportField; +import org.acra.annotation.BuilderMethod; +import org.acra.annotation.ConfigurationValue; import org.acra.annotation.PreBuild; import org.acra.annotation.Transform; +import org.acra.util.StubCreator; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.EnumMap; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.ServiceConfigurationError; -import java.util.ServiceLoader; -import java.util.Set; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.*; import static org.acra.ACRA.DEV_LOGGING; import static org.acra.ACRA.LOG_TAG; @@ -49,7 +45,7 @@ public final class BaseCoreConfigurationBuilder { private final List configurationBuilders; private List configurations; - BaseCoreConfigurationBuilder(@NonNull Class app) { + BaseCoreConfigurationBuilder(@NonNull Context app) { reportContentChanges = new EnumMap<>(ReportField.class); configurationBuilders = new ArrayList<>(); //noinspection ForLoopReplaceableByForEach @@ -72,8 +68,9 @@ void preBuild() throws ACRAConfigurationException { } } + @NonNull @Transform(methodName = "reportContent") - Set transformReportContent(ReportField[] reportFields) { + Set transformReportContent(@NonNull ReportField[] reportFields) { final Set reportContent = new LinkedHashSet<>(); if (reportFields.length != 0) { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "Using custom Report Fields"); @@ -100,22 +97,35 @@ Set transformReportContent(ReportField[] reportFields) { * @param field the field to set * @param enable if this field should be reported */ + @BuilderMethod public void setReportField(@NonNull ReportField field, boolean enable) { this.reportContentChanges.put(field, enable); } + @ConfigurationValue @NonNull List pluginConfigurations() { return configurations; } - public R getPluginConfigurationBuilder(Class c) { + @NonNull + @BuilderMethod + public R getPluginConfigurationBuilder(@NonNull Class c) { for (ConfigurationBuilder builder : configurationBuilders) { if (c.isAssignableFrom(builder.getClass())) { //noinspection unchecked return (R) builder; } } - return null; + if (c.isInterface()) { + ACRA.log.w(ACRA.LOG_TAG, "Couldn't find ConfigurationBuilder " + c.getSimpleName() + ". ALL CALLS TO IT WILL BE IGNORED!"); + return StubCreator.createStub(c, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + return proxy; + } + }); + } + throw new IllegalArgumentException("Class " + c.getName() + " is not a registered ConfigurationBuilder"); } } diff --git a/acra-core/src/main/java/org/acra/config/ConfigUtils.java b/acra-core/src/main/java/org/acra/config/ConfigUtils.java index 652c2f15e..34dd29834 100644 --- a/acra-core/src/main/java/org/acra/config/ConfigUtils.java +++ b/acra-core/src/main/java/org/acra/config/ConfigUtils.java @@ -16,6 +16,8 @@ package org.acra.config; +import android.support.annotation.NonNull; + /** * Allows easy access to Plugin configurations from the main configuration * @@ -24,16 +26,15 @@ */ public final class ConfigUtils { - public static T getPluginConfiguration(CoreConfiguration config, Class c) { - T pluginConfiguration = null; + @NonNull + public static T getPluginConfiguration(@NonNull CoreConfiguration config, @NonNull Class c) { for (Configuration configuration : config.pluginConfigurations()) { if (c.isAssignableFrom(configuration.getClass())) { //noinspection unchecked - pluginConfiguration = (T) configuration; - break; + return (T) configuration; } } - return pluginConfiguration; + throw new IllegalArgumentException(c.getName() + " is no registered configuration"); } } diff --git a/acra-javacore/src/main/java/org/acra/config/ConfigurationBuilderFactory.java b/acra-core/src/main/java/org/acra/config/ConfigurationBuilderFactory.java similarity index 79% rename from acra-javacore/src/main/java/org/acra/config/ConfigurationBuilderFactory.java rename to acra-core/src/main/java/org/acra/config/ConfigurationBuilderFactory.java index 9996fccd9..a4fda4746 100644 --- a/acra-javacore/src/main/java/org/acra/config/ConfigurationBuilderFactory.java +++ b/acra-core/src/main/java/org/acra/config/ConfigurationBuilderFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 + * Copyright (c) 2018 the ACRA team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.acra.config; +import android.content.Context; import android.support.annotation.Keep; import android.support.annotation.NonNull; @@ -30,9 +31,9 @@ public interface ConfigurationBuilderFactory { /** * creates a new builder * - * @param annotatedClass the class holding the annotation from which the builder should pull its values + * @param annotatedContext the context holding the annotation from which the builder should pull its values * @return a new builder with values from the annotation */ @NonNull - ConfigurationBuilder create(@NonNull Class annotatedClass); + ConfigurationBuilder create(@NonNull Context annotatedContext); } diff --git a/acra-core/src/main/java/org/acra/config/DefaultRetryPolicy.java b/acra-core/src/main/java/org/acra/config/DefaultRetryPolicy.java index d03639cfe..77811b2d8 100644 --- a/acra-core/src/main/java/org/acra/config/DefaultRetryPolicy.java +++ b/acra-core/src/main/java/org/acra/config/DefaultRetryPolicy.java @@ -15,6 +15,8 @@ */ package org.acra.config; +import android.support.annotation.NonNull; + import org.acra.sender.ReportSender; import java.util.List; @@ -27,7 +29,7 @@ */ public class DefaultRetryPolicy implements RetryPolicy { @Override - public boolean shouldRetrySend(List senders, List failedSenders) { + public boolean shouldRetrySend(@NonNull List senders, @NonNull List failedSenders) { return (senders.size() == failedSenders.size()) && !senders.isEmpty(); } } diff --git a/acra-core/src/main/java/org/acra/config/RetryPolicy.java b/acra-core/src/main/java/org/acra/config/RetryPolicy.java index 3c0ec4823..9936b02b9 100644 --- a/acra-core/src/main/java/org/acra/config/RetryPolicy.java +++ b/acra-core/src/main/java/org/acra/config/RetryPolicy.java @@ -15,6 +15,8 @@ */ package org.acra.config; +import android.support.annotation.NonNull; + import org.acra.sender.ReportSender; import org.acra.sender.ReportSenderException; @@ -33,22 +35,24 @@ public interface RetryPolicy { * @param failedSenders a list of all failed senders with the thrown exceptions. * @return if the request should be resent later. */ - boolean shouldRetrySend(List senders, List failedSenders); + boolean shouldRetrySend(@NonNull List senders, @NonNull List failedSenders); class FailedSender { private final ReportSender sender; private final ReportSenderException exception; - public FailedSender(ReportSender sender, ReportSenderException exception) { + public FailedSender(@NonNull ReportSender sender, @NonNull ReportSenderException exception) { this.sender = sender; this.exception = exception; } + @NonNull public ReportSender getSender() { return sender; } + @NonNull public ReportSenderException getException() { return exception; } diff --git a/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.java b/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.java index 79aefee63..8fb3b3af8 100644 --- a/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.java +++ b/acra-core/src/main/java/org/acra/data/CrashReportDataFactory.java @@ -68,7 +68,7 @@ public CrashReportDataFactory(@NonNull Context context, @NonNull CoreConfigurati } Collections.sort(collectors, new Comparator() { @Override - public int compare(Collector c1, Collector c2) { + public int compare(@NonNull Collector c1, @NonNull Collector c2) { Collector.Order o1; Collector.Order o2; try { diff --git a/acra-core/src/main/java/org/acra/data/StringFormat.java b/acra-core/src/main/java/org/acra/data/StringFormat.java index 10d9237dc..3c442cf0d 100644 --- a/acra-core/src/main/java/org/acra/data/StringFormat.java +++ b/acra-core/src/main/java/org/acra/data/StringFormat.java @@ -17,6 +17,7 @@ package org.acra.data; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.TextUtils; import org.acra.ACRAConstants; @@ -44,7 +45,7 @@ public enum StringFormat { JSON("application/json") { @NonNull @Override - public String toFormattedString(CrashReportData data, ImmutableSet order, String mainJoiner, String subJoiner, boolean urlEncode) throws JSONException { + public String toFormattedString(@NonNull CrashReportData data, @NonNull ImmutableSet order, @NonNull String mainJoiner, @NonNull String subJoiner, boolean urlEncode) throws JSONException { final Map map = data.toMap(); final JSONStringer stringer = new JSONStringer().object(); for (ReportField field : order) { @@ -59,7 +60,7 @@ public String toFormattedString(CrashReportData data, ImmutableSet KEY_VALUE_LIST("application/x-www-form-urlencoded") { @NonNull @Override - public String toFormattedString(CrashReportData data, ImmutableSet order, String mainJoiner, String subJoiner, boolean urlEncode) throws UnsupportedEncodingException { + public String toFormattedString(@NonNull CrashReportData data, @NonNull ImmutableSet order, @NonNull String mainJoiner, @NonNull String subJoiner, boolean urlEncode) throws UnsupportedEncodingException { final Map map = toStringMap(data.toMap(), subJoiner); final StringBuilder builder = new StringBuilder(); for (ReportField field : order) { @@ -71,7 +72,7 @@ public String toFormattedString(CrashReportData data, ImmutableSet return builder.toString(); } - private void append(StringBuilder builder, String key, String value, String joiner, boolean urlEncode) throws UnsupportedEncodingException { + private void append(@NonNull StringBuilder builder, @Nullable String key, @Nullable String value, @Nullable String joiner, boolean urlEncode) throws UnsupportedEncodingException { if (builder.length() > 0) { builder.append(joiner); } @@ -82,7 +83,8 @@ private void append(StringBuilder builder, String key, String value, String join builder.append(key).append('=').append(value); } - private Map toStringMap(Map map, String joiner) { + @NonNull + private Map toStringMap(@NonNull Map map, @NonNull String joiner) { final Map stringMap = new HashMap<>(map.size()); for (final Map.Entry entry : map.entrySet()) { stringMap.put(entry.getKey(), valueToString(joiner, entry.getValue())); @@ -90,7 +92,7 @@ private Map toStringMap(Map map, String joiner) return stringMap; } - private String valueToString(String joiner, Object value) { + private String valueToString(@NonNull String joiner, @Nullable Object value) { if (value instanceof JSONObject) { return TextUtils.join(joiner, flatten((JSONObject) value)); } else { @@ -98,7 +100,8 @@ private String valueToString(String joiner, Object value) { } } - private List flatten(JSONObject json) { + @NonNull + private List flatten(@NonNull JSONObject json) { final List result = new ArrayList<>(); for (final Iterator iterator = json.keys(); iterator.hasNext(); ) { final String key = iterator.next(); @@ -122,12 +125,12 @@ private List flatten(JSONObject json) { private final String contentType; - StringFormat(String contentType) { + StringFormat(@NonNull String contentType) { this.contentType = contentType; } @NonNull - public abstract String toFormattedString(CrashReportData data, ImmutableSet order, String mainJoiner, String subJoiner, boolean urlEncode) throws Exception; + public abstract String toFormattedString(@NonNull CrashReportData data, @NonNull ImmutableSet order, @NonNull String mainJoiner, @NonNull String subJoiner, boolean urlEncode) throws Exception; @NonNull public String getMatchingHttpContentType() { diff --git a/acra-core/src/main/java/org/acra/file/Directory.java b/acra-core/src/main/java/org/acra/file/Directory.java index e7c99be33..bc4140c9c 100644 --- a/acra-core/src/main/java/org/acra/file/Directory.java +++ b/acra-core/src/main/java/org/acra/file/Directory.java @@ -33,6 +33,7 @@ public enum Directory { * Otherwise it behaves like {@link #FILES}. */ FILES_LEGACY { + @NonNull @Override public File getFile(@NonNull Context context, @NonNull String fileName) { return (fileName.startsWith("/") ? Directory.ROOT : Directory.FILES).getFile(context, fileName); @@ -42,6 +43,7 @@ public File getFile(@NonNull Context context, @NonNull String fileName) { * Directory returned by {@link Context#getFilesDir()} */ FILES { + @NonNull @Override public File getFile(@NonNull Context context, @NonNull String fileName) { return new File(context.getFilesDir(), fileName); @@ -51,6 +53,7 @@ public File getFile(@NonNull Context context, @NonNull String fileName) { * Directory returned by {@link Context#getExternalFilesDir(String)} */ EXTERNAL_FILES { + @NonNull @Override public File getFile(@NonNull Context context, @NonNull String fileName) { return new File(context.getExternalFilesDir(null), fileName); @@ -60,6 +63,7 @@ public File getFile(@NonNull Context context, @NonNull String fileName) { * Directory returned by {@link Context#getCacheDir()} */ CACHE { + @NonNull @Override public File getFile(@NonNull Context context, @NonNull String fileName) { return new File(context.getCacheDir(), fileName); @@ -69,6 +73,7 @@ public File getFile(@NonNull Context context, @NonNull String fileName) { * Directory returned by {@link Context#getExternalCacheDir()} */ EXTERNAL_CACHE { + @NonNull @Override public File getFile(@NonNull Context context, @NonNull String fileName) { return new File(context.getExternalCacheDir(), fileName); @@ -79,6 +84,7 @@ public File getFile(@NonNull Context context, @NonNull String fileName) { * Will fall back to {@link Context#getFilesDir()} on API < 21 */ NO_BACKUP_FILES { + @NonNull @Override public File getFile(@NonNull Context context, @NonNull String fileName) { return new File(ContextCompat.getNoBackupFilesDir(context), fileName); @@ -88,6 +94,7 @@ public File getFile(@NonNull Context context, @NonNull String fileName) { * Directory returned by {@link Environment#getExternalStorageDirectory()} */ EXTERNAL_STORAGE { + @NonNull @Override public File getFile(@NonNull Context context, @NonNull String fileName) { return new File(Environment.getExternalStorageDirectory(), fileName); @@ -97,11 +104,21 @@ public File getFile(@NonNull Context context, @NonNull String fileName) { * Root Directory, paths in this directory are absolute paths */ ROOT { + @NonNull @Override public File getFile(@NonNull Context context, @NonNull String fileName) { - return new File(fileName); + String[] parts = fileName.split(File.separator, 2); + if (parts.length == 1) return new File(fileName); + final File[] roots = File.listRoots(); + for (File root : roots) { + if (parts[0].equals(root.getName())) { + return new File(root, parts[1]); + } + } + return new File(roots[0], fileName); } }; + @NonNull public abstract File getFile(@NonNull Context context, @NonNull String fileName); } diff --git a/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.java b/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.java index 8c7bc4f05..a1e45d7d7 100644 --- a/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.java +++ b/acra-core/src/main/java/org/acra/interaction/ReportInteractionExecutor.java @@ -34,6 +34,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; +import static org.acra.ACRA.LOG_TAG; + /** * Manages and executes all report interactions * @@ -43,9 +45,7 @@ public class ReportInteractionExecutor { private final List reportInteractions; - @NonNull private final Context context; - @NonNull private final CoreConfiguration config; public ReportInteractionExecutor(@NonNull final Context context, @NonNull final CoreConfiguration config) { @@ -58,9 +58,11 @@ public ReportInteractionExecutor(@NonNull final Context context, @NonNull final final ReportInteraction reportInteraction = iterator.next(); if (reportInteraction.enabled(config)) { reportInteractions.add(reportInteraction); + }else if (ACRA.DEV_LOGGING) { + ACRA.log.d(LOG_TAG, "Ignoring disabled ReportInteraction of type " + reportInteraction.getClass().getSimpleName()); } } catch (ServiceConfigurationError e) { - ACRA.log.e(ACRA.LOG_TAG, "Unable to load interaction", e); + ACRA.log.e(ACRA.LOG_TAG, "Unable to load ReportInteraction", e); } } } diff --git a/acra-core/src/main/java/org/acra/legacy/ReportConverter.java b/acra-core/src/main/java/org/acra/legacy/ReportConverter.java index a26f07f86..6d60aa941 100644 --- a/acra-core/src/main/java/org/acra/legacy/ReportConverter.java +++ b/acra-core/src/main/java/org/acra/legacy/ReportConverter.java @@ -54,7 +54,7 @@ class ReportConverter { private static final int NONE = 0, SLASH = 1, UNICODE = 2, CONTINUE = 3, KEY_DONE = 4, IGNORE = 5; private final Context context; - ReportConverter(Context context) { + ReportConverter(@NonNull Context context) { this.context = context; } @@ -297,7 +297,7 @@ private synchronized CrashReportData legacyLoad(@NonNull Reader reader) throws I } } - private void putKeyValue(CrashReportData crashData, String key, String value){ + private void putKeyValue(@NonNull CrashReportData crashData, @NonNull String key, @NonNull String value){ try { crashData.put(key, new JSONObject(value)); } catch (JSONException e1) { diff --git a/acra-core/src/main/java/org/acra/legacy/ReportMigrator.java b/acra-core/src/main/java/org/acra/legacy/ReportMigrator.java index 3274913a5..950e0af62 100644 --- a/acra-core/src/main/java/org/acra/legacy/ReportMigrator.java +++ b/acra-core/src/main/java/org/acra/legacy/ReportMigrator.java @@ -36,7 +36,6 @@ final class ReportMigrator { private final Context context; private final CrashReportFileNameParser fileNameParser = new CrashReportFileNameParser(); - @NonNull private final ReportLocator reportLocator; ReportMigrator(@NonNull Context context) { diff --git a/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.java b/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.java index bf61edc90..ba917b473 100644 --- a/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.java +++ b/acra-core/src/main/java/org/acra/prefs/SharedPreferencesFactory.java @@ -49,17 +49,17 @@ public SharedPreferencesFactory(@NonNull Context context, @NonNull CoreConfigura * * @param prefs SharedPreferences to check to see whether ACRA should be * disabled. - * @return true if prefs indicate that ACRA should be disabled. + * @return true if prefs indicate that ACRA should be enabled. */ - public static boolean shouldDisableACRA(@NonNull SharedPreferences prefs) { - boolean disableAcra = false; + public static boolean shouldEnableACRA(@NonNull SharedPreferences prefs) { + boolean enableAcra = true; try { - final boolean enableAcra = prefs.getBoolean(ACRA.PREF_ENABLE_ACRA, true); - disableAcra = prefs.getBoolean(ACRA.PREF_DISABLE_ACRA, !enableAcra); + final boolean disableAcra = prefs.getBoolean(ACRA.PREF_DISABLE_ACRA, false); + enableAcra = prefs.getBoolean(ACRA.PREF_ENABLE_ACRA, !disableAcra); } catch (Exception e) { // In case of a ClassCastException } - return disableAcra; + return enableAcra; } /** diff --git a/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.java b/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.java index f75531e9f..539de78f3 100644 --- a/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.java +++ b/acra-core/src/main/java/org/acra/reporter/ErrorReporterImpl.java @@ -25,8 +25,8 @@ import org.acra.builder.LastActivityManager; import org.acra.builder.ReportBuilder; import org.acra.builder.ReportExecutor; -import org.acra.data.CrashReportDataFactory; import org.acra.config.CoreConfiguration; +import org.acra.data.CrashReportDataFactory; import org.acra.prefs.SharedPreferencesFactory; import org.acra.util.InstanceCreator; import org.acra.util.ProcessFinisher; @@ -53,12 +53,8 @@ public class ErrorReporterImpl implements Thread.UncaughtExceptionHandler, SharedPreferences.OnSharedPreferenceChangeListener, ErrorReporter { private final boolean supportedAndroidVersion; - private final Application context; - - @NonNull private final ReportExecutor reportExecutor; - private final Map customData = new HashMap<>(); @@ -93,7 +89,7 @@ public ErrorReporterImpl(@NonNull Application context, @NonNull CoreConfiguratio * {@inheritDoc} */ @Override - public String putCustomData(@NonNull String key, String value) { + public String putCustomData(@NonNull String key, @Nullable String value) { return customData.put(key, value); } @@ -101,6 +97,7 @@ public String putCustomData(@NonNull String key, String value) { * {@inheritDoc} */ @Override + @Nullable public String removeCustomData(@NonNull String key) { return customData.remove(key); } @@ -117,6 +114,7 @@ public void clearCustomData() { * {@inheritDoc} */ @Override + @Nullable public String getCustomData(@NonNull String key) { return customData.get(key); } @@ -181,14 +179,6 @@ public void setEnabled(boolean enabled) { } } - /** - * {@inheritDoc} - */ - @Override - public boolean isRegistered() { - return Thread.getDefaultUncaughtExceptionHandler() == this; - } - /** * {@inheritDoc} */ @@ -212,9 +202,9 @@ public void handleException(@Nullable Throwable e) { } @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + public void onSharedPreferenceChanged(@NonNull SharedPreferences sharedPreferences, @Nullable String key) { if (ACRA.PREF_DISABLE_ACRA.equals(key) || ACRA.PREF_ENABLE_ACRA.equals(key)) { - setEnabled(!SharedPreferencesFactory.shouldDisableACRA(sharedPreferences)); + setEnabled(SharedPreferencesFactory.shouldEnableACRA(sharedPreferences)); } } } \ No newline at end of file diff --git a/acra-core/src/main/java/org/acra/reporter/ErrorReporterStub.java b/acra-core/src/main/java/org/acra/reporter/ErrorReporterStub.java deleted file mode 100644 index 11ee35c3f..000000000 --- a/acra-core/src/main/java/org/acra/reporter/ErrorReporterStub.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.acra.reporter; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import org.acra.ACRA; -import org.acra.ErrorReporter; - -/** - * @author F43nd1r - * @since 29.12.2017 - */ - -public class ErrorReporterStub implements ErrorReporter { - @Override - public String putCustomData(@NonNull String key, String value) { - warnStubCalled(); - return null; - } - - @Override - public String removeCustomData(@NonNull String key) { - warnStubCalled(); - return null; - } - - @Override - public void clearCustomData() { - warnStubCalled(); - - } - - @Override - public String getCustomData(@NonNull String key) { - warnStubCalled(); - return null; - } - - @Override - public void handleSilentException(@Nullable Throwable e) { - warnStubCalled(); - } - - @Override - public void setEnabled(boolean enabled) { - warnStubCalled(); - } - - @Override - public boolean isRegistered() { - return false; - } - - @Override - public void handleException(@Nullable Throwable e, boolean endApplication) { - warnStubCalled(); - } - - @Override - public void handleException(@Nullable Throwable e) { - warnStubCalled(); - } - - private void warnStubCalled() { - StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); - String methodName = stackTraceElements.length > 3 ? stackTraceElements[3].getMethodName() : null; - String message = ACRA.isACRASenderServiceProcess() ? "in SenderService process" : "before ACRA#init (if you did call #init, check if your configuration is valid)"; - ACRA.log.w(ACRA.LOG_TAG, String.format("ErrorReporter#%s called %s. THIS CALL WILL BE IGNORED!", methodName, message)); - } -} diff --git a/acra-core/src/main/java/org/acra/sender/DefaultReportSenderFactory.java b/acra-core/src/main/java/org/acra/sender/DefaultReportSenderFactory.java index c89f90d7f..74d411b85 100644 --- a/acra-core/src/main/java/org/acra/sender/DefaultReportSenderFactory.java +++ b/acra-core/src/main/java/org/acra/sender/DefaultReportSenderFactory.java @@ -48,6 +48,8 @@ public ReportSender create(@NonNull Context context, @NonNull CoreConfiguration final ReportSenderFactory reportSenderFactory = iterator.next(); if (reportSenderFactory.enabled(config)) { factoryList.add(reportSenderFactory); + } else if (ACRA.DEV_LOGGING) { + ACRA.log.d(LOG_TAG, "Ignoring disabled ReportSenderFactory of type " + reportSenderFactory.getClass().getSimpleName()); } } catch (ServiceConfigurationError e) { ACRA.log.e(ACRA.LOG_TAG, "Unable to load ReportSenderFactory", e); diff --git a/acra-core/src/main/java/org/acra/sender/NullSender.java b/acra-core/src/main/java/org/acra/sender/NullSender.java index e6970e2a2..5351f2430 100644 --- a/acra-core/src/main/java/org/acra/sender/NullSender.java +++ b/acra-core/src/main/java/org/acra/sender/NullSender.java @@ -29,7 +29,7 @@ */ final class NullSender implements ReportSender { @Override - public void send(@NonNull Context context, @NonNull CrashReportData errorContent) throws ReportSenderException { + public void send(@NonNull Context context, @NonNull CrashReportData errorContent) { ACRA.log.w(LOG_TAG, context.getPackageName() + " reports will NOT be sent - no valid ReportSender is configured. Try setting 'formUri' or 'mailTo'"); } } diff --git a/acra-core/src/main/java/org/acra/sender/SenderService.java b/acra-core/src/main/java/org/acra/sender/SenderService.java index ee393433d..ddf536192 100644 --- a/acra-core/src/main/java/org/acra/sender/SenderService.java +++ b/acra-core/src/main/java/org/acra/sender/SenderService.java @@ -15,12 +15,11 @@ */ package org.acra.sender; -import android.app.IntentService; import android.content.Intent; import android.os.Handler; import android.os.Looper; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; +import android.support.v4.app.JobIntentService; import android.widget.Toast; import org.acra.ACRA; @@ -38,22 +37,21 @@ import static org.acra.ACRA.LOG_TAG; -public class SenderService extends IntentService { +public class SenderService extends JobIntentService { public static final String EXTRA_ONLY_SEND_SILENT_REPORTS = "onlySendSilentReports"; public static final String EXTRA_APPROVE_REPORTS_FIRST = "approveReportsFirst"; public static final String EXTRA_ACRA_CONFIG = "acraConfig"; - private final ReportLocator locator = new ReportLocator(this); + private final ReportLocator locator; public SenderService() { - super("ACRA SenderService"); - setIntentRedelivery(true); + locator = new ReportLocator(this); } @Override - protected void onHandleIntent(@Nullable final Intent intent) { - if (intent == null || !intent.hasExtra(EXTRA_ACRA_CONFIG)) { + protected void onHandleWork(@NonNull Intent intent) { + if (!intent.hasExtra(EXTRA_ACRA_CONFIG)) { if(ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "SenderService was started but no valid intent was delivered, will now quit"); return; } @@ -94,12 +92,12 @@ protected void onHandleIntent(@Nullable final Intent intent) { reportDistributor.distribute(report); reportsSentCount++; } - final int toastRes = reportsSentCount > 0 ? config.resReportSendSuccessToast() : config.resReportSendFailureToast(); - if (toastRes != ACRAConstants.DEFAULT_RES_VALUE) { + final String toast = reportsSentCount > 0 ? config.reportSendSuccessToast() : config.reportSendFailureToast(); + if (toast != null) { new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { - ToastSender.sendToast(SenderService.this, toastRes, Toast.LENGTH_LONG); + ToastSender.sendToast(SenderService.this, toast, Toast.LENGTH_LONG); } }); } diff --git a/acra-core/src/main/java/org/acra/sender/SenderServiceStarter.java b/acra-core/src/main/java/org/acra/sender/SenderServiceStarter.java index 5c5450900..1ab3bb59f 100644 --- a/acra-core/src/main/java/org/acra/sender/SenderServiceStarter.java +++ b/acra-core/src/main/java/org/acra/sender/SenderServiceStarter.java @@ -19,6 +19,7 @@ import android.content.Context; import android.content.Intent; import android.support.annotation.NonNull; +import android.support.v4.app.JobIntentService; import org.acra.ACRA; import org.acra.config.CoreConfiguration; @@ -46,10 +47,10 @@ public SenderServiceStarter(@NonNull Context context, @NonNull CoreConfiguration */ public void startService(boolean onlySendSilentReports, boolean approveReportsFirst) { if (ACRA.DEV_LOGGING) ACRA.log.d(LOG_TAG, "About to start SenderService"); - final Intent intent = new Intent(context, SenderService.class); + final Intent intent = new Intent(); intent.putExtra(SenderService.EXTRA_ONLY_SEND_SILENT_REPORTS, onlySendSilentReports); intent.putExtra(SenderService.EXTRA_APPROVE_REPORTS_FIRST, approveReportsFirst); intent.putExtra(SenderService.EXTRA_ACRA_CONFIG, config); - context.startService(intent); + JobIntentService.enqueueWork(context, SenderService.class, 0, intent); } } diff --git a/acra-core/src/main/java/org/acra/util/InstanceCreator.java b/acra-core/src/main/java/org/acra/util/InstanceCreator.java index b3b30fc23..0c11369ac 100644 --- a/acra-core/src/main/java/org/acra/util/InstanceCreator.java +++ b/acra-core/src/main/java/org/acra/util/InstanceCreator.java @@ -17,6 +17,7 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import org.acra.ACRA; @@ -34,12 +35,21 @@ public final class InstanceCreator { /** * Create an instance of clazz - * @param clazz the clazz to create an instance of + * + * @param clazz the clazz to create an instance of * @param fallback the value to return in case of a failure - * @param the return type + * @param the return type * @return a new instance of clazz or fallback */ - public T create(@NonNull Class clazz, @Nullable T fallback) { + @NonNull + public T create(@NonNull Class clazz, @NonNull T fallback) { + T t = create(clazz); + return t != null ? t : fallback; + } + + @VisibleForTesting + @Nullable + T create(@NonNull Class clazz) { try { return clazz.newInstance(); } catch (InstantiationException e) { @@ -47,20 +57,21 @@ public T create(@NonNull Class clazz, @Nullable T fallback) { } catch (IllegalAccessException e) { ACRA.log.e(LOG_TAG, "Failed to create instance of class " + clazz.getName(), e); } - return fallback; + return null; } /** * Create instances of the given classes + * * @param classes the classes to create insatnces of - * @param the return type + * @param the return type * @return a list of successfully created instances, does not contain null */ @NonNull public List create(@NonNull Collection> classes) { final List result = new ArrayList<>(); for (Class clazz : classes) { - final T instance = create(clazz, null); + final T instance = create(clazz); if (instance != null) { result.add(instance); } diff --git a/acra-core/src/main/java/org/acra/util/NonBlockingBufferedReader.java b/acra-core/src/main/java/org/acra/util/NonBlockingBufferedReader.java deleted file mode 100644 index 9448a1c89..000000000 --- a/acra-core/src/main/java/org/acra/util/NonBlockingBufferedReader.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2017 the ACRA team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.acra.util; - -import java.io.BufferedReader; -import java.io.IOException; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; - -/** - * Asynchronously reads a buffer into a List of String. - * - * @author C-Romeo - * @since 4.9.0 - */ -final class NonBlockingBufferedReader { - - private final BlockingQueue lines = new LinkedBlockingQueue<>(); - private Thread backgroundReaderThread = null; - private volatile IOException exception = null; - - NonBlockingBufferedReader(final BufferedReader bufferedReader) { - backgroundReaderThread = new Thread(new Runnable() { - @Override - public void run() { - try { - while (!Thread.interrupted()) { - final String line = bufferedReader.readLine(); - if (line == null) { - break; - } - lines.add(line); - } - } catch (IOException e) { - exception = e; - } finally { - IOUtils.safeClose(bufferedReader); - } - } - }); - backgroundReaderThread.setDaemon(true); - backgroundReaderThread.start(); - } - - String readLine() throws InterruptedException, IOException { - if(exception != null){ - throw exception; - } - return lines.isEmpty() ? null : lines.poll(500L, TimeUnit.MILLISECONDS); - } - - void close() { - if (backgroundReaderThread != null) { - backgroundReaderThread.interrupt(); - backgroundReaderThread = null; - } - } -} diff --git a/acra-core/src/main/java/org/acra/util/PackageManagerWrapper.java b/acra-core/src/main/java/org/acra/util/PackageManagerWrapper.java index baae94441..33c9487cd 100644 --- a/acra-core/src/main/java/org/acra/util/PackageManagerWrapper.java +++ b/acra-core/src/main/java/org/acra/util/PackageManagerWrapper.java @@ -43,6 +43,7 @@ */ public final class PackageManagerWrapper { + @NonNull private final Context context; public PackageManagerWrapper(@NonNull Context context) { diff --git a/acra-core/src/main/java/org/acra/util/StreamReader.java b/acra-core/src/main/java/org/acra/util/StreamReader.java index f37f6ea72..10a7667f2 100644 --- a/acra-core/src/main/java/org/acra/util/StreamReader.java +++ b/acra-core/src/main/java/org/acra/util/StreamReader.java @@ -62,21 +62,25 @@ public StreamReader(@NonNull InputStream inputStream) { this.inputStream = inputStream; } + @NonNull public StreamReader setLimit(int limit) { this.limit = limit; return this; } + @NonNull public StreamReader setTimeout(int timeout) { this.timeout = timeout; return this; } + @NonNull public StreamReader setFilter(Predicate filter) { this.filter = filter; return this; } + @NonNull public String read() throws IOException { final String text = timeout == INDEFINITE ? readFully() : readWithTimeout(); if (filter == null) { @@ -99,6 +103,7 @@ public String read() throws IOException { return TextUtils.join("\n", buffer); } + @NonNull private String readFully() throws IOException { final Reader input = new InputStreamReader(inputStream); try { @@ -114,6 +119,7 @@ private String readFully() throws IOException { } } + @NonNull private String readWithTimeout() throws IOException { final long until = System.currentTimeMillis() + timeout; try { @@ -129,7 +135,7 @@ private String readWithTimeout() throws IOException { } } - private int fillBufferUntil(byte[] buffer, long until) throws IOException { + private int fillBufferUntil(@NonNull byte[] buffer, long until) throws IOException { int bufferOffset = 0; while (System.currentTimeMillis() < until && bufferOffset < buffer.length) { final int readResult = inputStream.read(buffer, bufferOffset, Math.min(inputStream.available(), buffer.length - bufferOffset)); diff --git a/acra-core/src/main/java/org/acra/util/StubCreator.java b/acra-core/src/main/java/org/acra/util/StubCreator.java new file mode 100644 index 000000000..75906186a --- /dev/null +++ b/acra-core/src/main/java/org/acra/util/StubCreator.java @@ -0,0 +1,35 @@ +package org.acra.util; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import org.acra.ACRA; +import org.acra.ErrorReporter; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +public final class StubCreator { + private StubCreator() { + } + + @NonNull + public static ErrorReporter createErrorReporterStub() { + return createStub(ErrorReporter.class, new InvocationHandler() { + @Nullable + @Override + public Object invoke(Object proxy, @NonNull Method method, Object[] args) { + String message = ACRA.isACRASenderServiceProcess() ? "in SenderService process" : "before ACRA#init (if you did call #init, check if your configuration is valid)"; + ACRA.log.w(ACRA.LOG_TAG, String.format("ErrorReporter#%s called %s. THIS CALL WILL BE IGNORED!", method.getName(), message)); + return null; + } + }); + } + + @NonNull + public static T createStub(Class interfaceClass, InvocationHandler handler) { + //noinspection unchecked + return (T) Proxy.newProxyInstance(StubCreator.class.getClassLoader(), new Class[]{interfaceClass}, handler); + } +} diff --git a/acra-core/src/main/java/org/acra/util/SystemServices.java b/acra-core/src/main/java/org/acra/util/SystemServices.java index d3eb2c1f0..c21bc71e7 100644 --- a/acra-core/src/main/java/org/acra/util/SystemServices.java +++ b/acra-core/src/main/java/org/acra/util/SystemServices.java @@ -29,7 +29,7 @@ */ public final class SystemServices { - private SystemServices(){ + private SystemServices() { } @NonNull @@ -55,14 +55,14 @@ public static ActivityManager getActivityManager(@NonNull Context context) throw @NonNull private static Object getService(@NonNull Context context, @NonNull String id) throws ServiceNotReachedException { final Object service = context.getSystemService(id); - if(service == null){ + if (service == null) { throw new ServiceNotReachedException("Unable to load SystemService " + id); } return service; } - public static class ServiceNotReachedException extends Exception{ - public ServiceNotReachedException(String message) { + static class ServiceNotReachedException extends Exception { + ServiceNotReachedException(String message) { super(message); } } diff --git a/acra-core/src/main/java/org/acra/util/ToastSender.java b/acra-core/src/main/java/org/acra/util/ToastSender.java index 3785a0f6c..4cf339d9c 100644 --- a/acra-core/src/main/java/org/acra/util/ToastSender.java +++ b/acra-core/src/main/java/org/acra/util/ToastSender.java @@ -19,7 +19,6 @@ import android.content.Context; import android.support.annotation.IntRange; import android.support.annotation.NonNull; -import android.support.annotation.StringRes; import android.widget.Toast; import org.acra.ACRA; @@ -33,18 +32,19 @@ * @since 4.3.0 */ public final class ToastSender { - private ToastSender(){} + private ToastSender() { + } /** * Sends a Toast and ensures that any Exception thrown during sending is handled. * - * @param context Application context. - * @param toastResourceId Id of the resource to send as the Toast message. - * @param toastLength Length of the Toast. + * @param context Application context. + * @param toast toast message. + * @param toastLength Length of the Toast. */ - public static void sendToast(@NonNull Context context, @StringRes int toastResourceId, @IntRange(from = 0, to = 1) int toastLength) { + public static void sendToast(@NonNull Context context, String toast, @IntRange(from = 0, to = 1) int toastLength) { try { - Toast.makeText(context, toastResourceId, toastLength).show(); + Toast.makeText(context, toast, toastLength).show(); } catch (RuntimeException e) { ACRA.log.w(LOG_TAG, "Could not send crash Toast", e); } diff --git a/acra-core/src/test/java/org/acra/attachment/AcraContentProviderTest.java b/acra-core/src/test/java/org/acra/attachment/AcraContentProviderTest.java index e2d42396d..61e88bfc3 100644 --- a/acra-core/src/test/java/org/acra/attachment/AcraContentProviderTest.java +++ b/acra-core/src/test/java/org/acra/attachment/AcraContentProviderTest.java @@ -24,6 +24,8 @@ import com.google.common.net.MediaType; +import org.acra.ACRA; +import org.acra.log.RobolectricLog; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -53,6 +55,8 @@ public class AcraContentProviderTest { @Before public void setUp() throws Exception { + ACRA.log = new RobolectricLog(); + ACRA.DEV_LOGGING = true; Robolectric.setupContentProvider(AcraContentProvider.class, RuntimeEnvironment.application.getPackageName() + ".acra"); resolver = RuntimeEnvironment.application.getContentResolver(); file = File.createTempFile("test", "." + JSON_EXTENSION); @@ -60,7 +64,7 @@ public void setUp() throws Exception { } @Test - public void query() throws Exception { + public void query() { final Cursor cursor = resolver.query(AcraContentProvider.getUriForFile(RuntimeEnvironment.application, file), new String[]{OpenableColumns.SIZE, OpenableColumns.DISPLAY_NAME}, null, null, null); assertNotNull(cursor); assertTrue(cursor.moveToFirst()); @@ -78,7 +82,7 @@ public void openFile() throws Exception { } @Test - public void guessMimeType() throws Exception { + public void guessMimeType() { assertEquals(JSON_MIMETYPE, AcraContentProvider.guessMimeType(AcraContentProvider.getUriForFile(RuntimeEnvironment.application, file))); } diff --git a/acra-core/src/test/java/org/acra/attachment/DefaultAttachmentProviderTest.java b/acra-core/src/test/java/org/acra/attachment/DefaultAttachmentProviderTest.java index f98757789..71ad3867d 100644 --- a/acra-core/src/test/java/org/acra/attachment/DefaultAttachmentProviderTest.java +++ b/acra-core/src/test/java/org/acra/attachment/DefaultAttachmentProviderTest.java @@ -16,6 +16,7 @@ package org.acra.attachment; +import android.app.Application; import android.net.Uri; import org.acra.config.CoreConfigurationBuilder; @@ -39,7 +40,7 @@ public class DefaultAttachmentProviderTest { @Test public void getAttachments() throws Exception { Uri uri = Uri.parse("content://not-a-valid-content-uri"); - List result = new DefaultAttachmentProvider().getAttachments(RuntimeEnvironment.application, new CoreConfigurationBuilder(this).setAttachmentUris(uri.toString()).build()); + List result = new DefaultAttachmentProvider().getAttachments(RuntimeEnvironment.application, new CoreConfigurationBuilder(new Application()).setAttachmentUris(uri.toString()).build()); assertThat(result, hasSize(1)); assertEquals(uri, result.get(0)); } diff --git a/acra-core/src/test/java/org/acra/config/CoreConfigurationBuilderTest.java b/acra-core/src/test/java/org/acra/config/CoreConfigurationBuilderTest.java new file mode 100644 index 000000000..2877ce4d5 --- /dev/null +++ b/acra-core/src/test/java/org/acra/config/CoreConfigurationBuilderTest.java @@ -0,0 +1,34 @@ +package org.acra.config; + +import android.app.Application; + +import org.acra.annotation.AcraCore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author F43nd1r + * @since 01.02.18 + */ +@RunWith(RobolectricTestRunner.class) +public class CoreConfigurationBuilderTest { + + @Test + public void enabled() { + assertTrue(new CoreConfigurationBuilder(new AnnotatedClass()).enabled()); + assertFalse(new CoreConfigurationBuilder(new NonAnnotatedClass()).enabled()); + } + + @AcraCore + private static class AnnotatedClass extends Application { + + } + + private static class NonAnnotatedClass extends Application { + + } +} \ No newline at end of file diff --git a/acra-core/src/test/java/org/acra/data/CrashReportDataTest.java b/acra-core/src/test/java/org/acra/data/CrashReportDataTest.java index d1e4e5e95..347ebce75 100644 --- a/acra-core/src/test/java/org/acra/data/CrashReportDataTest.java +++ b/acra-core/src/test/java/org/acra/data/CrashReportDataTest.java @@ -33,7 +33,7 @@ @RunWith(RobolectricTestRunner.class) public class CrashReportDataTest { @Test - public void put() throws Exception { + public void put() { final CrashReportData data = new CrashReportData(); data.put(ReportField.DEVICE_ID, "FAKE_ID"); assertEquals("FAKE_ID", data.getString(ReportField.DEVICE_ID)); @@ -45,7 +45,7 @@ public void put() throws Exception { } @Test - public void containsKey() throws Exception { + public void containsKey() { final CrashReportData data = new CrashReportData(); data.put(ReportField.DEVICE_ID, "FAKE_ID"); data.put("CUSTOM","\n"); @@ -55,7 +55,7 @@ public void containsKey() throws Exception { } @Test - public void toMap() throws Exception { + public void toMap() { final CrashReportData data = new CrashReportData(); data.put(ReportField.DEVICE_ID, "FAKE_ID"); data.put("CUSTOM",-1); diff --git a/acra-core/src/test/java/org/acra/data/StringFormatTest.java b/acra-core/src/test/java/org/acra/data/StringFormatTest.java index b535c2bff..357c805f9 100644 --- a/acra-core/src/test/java/org/acra/data/StringFormatTest.java +++ b/acra-core/src/test/java/org/acra/data/StringFormatTest.java @@ -69,8 +69,14 @@ public void testKeyValue() throws Exception { } @Test - public void getMatchingHttpContentType() throws Exception { + public void getMatchingHttpContentType() { assertEquals(MediaType.JSON_UTF_8.type() + "/" + MediaType.JSON_UTF_8.subtype(), StringFormat.JSON.getMatchingHttpContentType()); assertEquals(MediaType.FORM_DATA.type() + "/" + MediaType.FORM_DATA.subtype(), StringFormat.KEY_VALUE_LIST.getMatchingHttpContentType()); } + + @Test + public void issue626() throws Exception { + CrashReportData reportData = new CrashReportData(); + assertEquals("DEVICE_ID=null", StringFormat.KEY_VALUE_LIST.toFormattedString(reportData, new ImmutableSet<>(ReportField.DEVICE_ID), "\n", " ", true)); + } } \ No newline at end of file diff --git a/acra-core/src/test/java/org/acra/log/RobolectricLog.java b/acra-core/src/test/java/org/acra/log/RobolectricLog.java new file mode 100644 index 000000000..51c92841f --- /dev/null +++ b/acra-core/src/test/java/org/acra/log/RobolectricLog.java @@ -0,0 +1,87 @@ +package org.acra.log; + +import android.support.annotation.Nullable; + +import org.robolectric.util.Logger; + +/** + * @author F43nd1r + * @since 01.02.18 + */ +public class RobolectricLog implements ACRALog { + + public RobolectricLog() { + } + + @Override + public int v(String tag, String msg) { + Logger.debug(msg); + return 0; + } + + @Override + public int v(String tag, String msg, Throwable tr) { + Logger.debug(msg, tr); + return 0; + } + + @Override + public int d(String tag, String msg) { + Logger.debug(msg); + return 0; + } + + @Override + public int d(String tag, String msg, Throwable tr) { + Logger.debug(msg, tr); + return 0; + } + + @Override + public int i(String tag, String msg) { + Logger.info(msg); + return 0; + } + + @Override + public int i(String tag, String msg, Throwable tr) { + Logger.info(msg, tr); + return 0; + } + + @Override + public int w(String tag, String msg) { + Logger.warn(msg); + return 0; + } + + @Override + public int w(String tag, String msg, Throwable tr) { + Logger.warn(msg, tr); + return 0; + } + + @Override + public int w(String tag, Throwable tr) { + Logger.warn("", tr); + return 0; + } + + @Override + public int e(String tag, String msg) { + Logger.error(msg); + return 0; + } + + @Override + public int e(String tag, String msg, Throwable tr) { + Logger.error(msg, tr); + return 0; + } + + @Nullable + @Override + public String getStackTraceString(Throwable tr) { + return String.valueOf(tr); + } +} diff --git a/acra-core/src/test/java/org/acra/util/InstallationTest.java b/acra-core/src/test/java/org/acra/util/InstallationTest.java index 21f30965d..170c2bf4c 100644 --- a/acra-core/src/test/java/org/acra/util/InstallationTest.java +++ b/acra-core/src/test/java/org/acra/util/InstallationTest.java @@ -34,7 +34,7 @@ @RunWith(RobolectricTestRunner.class) public class InstallationTest { @Test - public void id() throws Exception { + public void id() { final String id = Installation.id(RuntimeEnvironment.application); assertEquals(id, Installation.id(RuntimeEnvironment.application)); for(File child : RuntimeEnvironment.application.getFilesDir().listFiles()){ diff --git a/acra-core/src/test/java/org/acra/util/InstanceCreatorTest.java b/acra-core/src/test/java/org/acra/util/InstanceCreatorTest.java new file mode 100644 index 000000000..8972acb46 --- /dev/null +++ b/acra-core/src/test/java/org/acra/util/InstanceCreatorTest.java @@ -0,0 +1,59 @@ +package org.acra.util; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +import java.util.Arrays; + +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.*; + +@RunWith(RobolectricTestRunner.class) +public class InstanceCreatorTest { + private InstanceCreator instanceCreator; + + @Before + public void setUp() { + instanceCreator = new InstanceCreator(); + } + + @Test + public void create() { + assertNotNull(instanceCreator.create(ClassWithDefaultConstructor.class)); + assertNotNull(instanceCreator.create(ClassWithExplicitNoArgsConstructor.class)); + assertNull(instanceCreator.create(ClassWithPrivateConstructor.class)); + assertNull(instanceCreator.create(ClassWithImplicitConstructorArg.class)); + assertNull(instanceCreator.create(ClassWithExplicitConstructorArg.class)); + } + + @Test + public void create1() { + assertThat(instanceCreator.create(Arrays.asList(ClassWithDefaultConstructor.class, ClassWithExplicitConstructorArg.class)), hasSize(1)); + } + + public static class ClassWithDefaultConstructor { + } + + public static class ClassWithExplicitNoArgsConstructor { + public ClassWithExplicitNoArgsConstructor() { + //nothing + } + } + + public static class ClassWithPrivateConstructor { + private ClassWithPrivateConstructor(){ + //nothing + } + } + + public class ClassWithImplicitConstructorArg { + } + + public static class ClassWithExplicitConstructorArg { + public ClassWithExplicitConstructorArg(@SuppressWarnings("unused") String arg){ + //nothing + } + } +} \ No newline at end of file diff --git a/acra-dialog/src/main/java/org/acra/dialog/BaseCrashReportDialog.java b/acra-dialog/src/main/java/org/acra/dialog/BaseCrashReportDialog.java index e0619218d..3a31cd2b4 100644 --- a/acra-dialog/src/main/java/org/acra/dialog/BaseCrashReportDialog.java +++ b/acra-dialog/src/main/java/org/acra/dialog/BaseCrashReportDialog.java @@ -21,8 +21,8 @@ import android.support.v4.app.FragmentActivity; import org.acra.ACRA; -import org.acra.data.CrashReportData; import org.acra.config.CoreConfiguration; +import org.acra.data.CrashReportData; import org.acra.file.BulkReportDeleter; import org.acra.file.CrashReportPersister; import org.acra.interaction.DialogInteraction; diff --git a/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.java b/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.java index 60f1e19c7..0f6c63c69 100644 --- a/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.java +++ b/acra-dialog/src/main/java/org/acra/dialog/CrashReportDialog.java @@ -79,17 +79,17 @@ protected void init(@Nullable Bundle savedInstanceState) { */ protected void buildAndShowDialog(@Nullable Bundle savedInstanceState) { final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - final int titleResourceId = dialogConfiguration.resTitle(); - if (titleResourceId != ACRAConstants.DEFAULT_RES_VALUE) { - dialogBuilder.setTitle(titleResourceId); + final String title = dialogConfiguration.title(); + if (title != null) { + dialogBuilder.setTitle(title); } final int iconResourceId = dialogConfiguration.resIcon(); if (iconResourceId != ACRAConstants.DEFAULT_RES_VALUE) { dialogBuilder.setIcon(iconResourceId); } dialogBuilder.setView(buildCustomView(savedInstanceState)) - .setPositiveButton(getText(dialogConfiguration.resPositiveButtonText()), this) - .setNegativeButton(getText(dialogConfiguration.resNegativeButtonText()), this); + .setPositiveButton(dialogConfiguration.positiveButtonText(), this) + .setNegativeButton(dialogConfiguration.negativeButtonText(), this); mDialog = dialogBuilder.create(); mDialog.setCanceledOnTouchOutside(false); @@ -152,9 +152,9 @@ protected final void addViewToDialog(@NonNull View v) { @NonNull protected View getMainView() { final TextView text = new TextView(this); - final int dialogTextId = dialogConfiguration.resText(); - if (dialogTextId != ACRAConstants.DEFAULT_RES_VALUE) { - text.setText(getText(dialogTextId)); + final String dialogText = dialogConfiguration.text(); + if (dialogText != null) { + text.setText(dialogText); } return text; } @@ -166,10 +166,10 @@ protected View getMainView() { */ @Nullable protected View getCommentLabel() { - final int commentPromptId = dialogConfiguration.resCommentPrompt(); - if (commentPromptId != ACRAConstants.DEFAULT_RES_VALUE) { + final String commentPrompt = dialogConfiguration.commentPrompt(); + if (commentPrompt != null) { final TextView labelView = new TextView(this); - labelView.setText(getText(commentPromptId)); + labelView.setText(commentPrompt); return labelView; } return null; @@ -198,10 +198,10 @@ protected EditText getCommentPrompt(@Nullable CharSequence savedComment) { */ @Nullable protected View getEmailLabel() { - final int emailPromptId = dialogConfiguration.resEmailPrompt(); - if (emailPromptId != ACRAConstants.DEFAULT_RES_VALUE) { + final String emailPrompt = dialogConfiguration.emailPrompt(); + if (emailPrompt != null) { final TextView labelView = new TextView(this); - labelView.setText(getText(emailPromptId)); + labelView.setText(emailPrompt); return labelView; } return null; diff --git a/acra-http/src/main/java/org/acra/annotation/AcraHttpSender.java b/acra-http/src/main/java/org/acra/annotation/AcraHttpSender.java index 27b10325e..f830098f3 100644 --- a/acra-http/src/main/java/org/acra/annotation/AcraHttpSender.java +++ b/acra-http/src/main/java/org/acra/annotation/AcraHttpSender.java @@ -50,7 +50,7 @@ * @return URI of a server to which to send reports. * @since 5.0.0 */ - String uri(); + @NonNull String uri(); /** * you can set here and in {@link #basicAuthPassword()} the credentials for a BASIC HTTP authentication. diff --git a/acra-http/src/main/java/org/acra/config/BaseHttpConfigurationBuilder.java b/acra-http/src/main/java/org/acra/config/BaseHttpConfigurationBuilder.java index 5210137f6..40d7f83bc 100644 --- a/acra-http/src/main/java/org/acra/config/BaseHttpConfigurationBuilder.java +++ b/acra-http/src/main/java/org/acra/config/BaseHttpConfigurationBuilder.java @@ -18,6 +18,9 @@ import android.support.annotation.NonNull; +import org.acra.annotation.BuilderMethod; +import org.acra.annotation.ConfigurationValue; + import java.util.HashMap; import java.util.Map; @@ -39,11 +42,13 @@ public class BaseHttpConfigurationBuilder { * * @param headers A map associating HTTP header names to their values. */ + @BuilderMethod public void setHttpHeaders(@NonNull Map headers) { this.httpHeaders.clear(); this.httpHeaders.putAll(headers); } + @ConfigurationValue @NonNull Map httpHeaders() { return httpHeaders; diff --git a/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java b/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java index 024798496..e6ab8581e 100644 --- a/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java +++ b/acra-http/src/main/java/org/acra/http/BaseHttpRequest.java @@ -20,7 +20,6 @@ import android.support.annotation.Nullable; import android.util.Base64; import android.util.Log; - import org.acra.ACRA; import org.acra.ACRAConstants; import org.acra.BuildConfig; @@ -31,6 +30,9 @@ import org.acra.sender.HttpSender.Method; import org.acra.util.IOUtils; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -41,10 +43,6 @@ import java.security.KeyStore; import java.util.Map; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; - import static org.acra.ACRA.LOG_TAG; /** @@ -54,11 +52,8 @@ @SuppressWarnings("WeakerAccess") public abstract class BaseHttpRequest implements HttpRequest { - @NonNull private final CoreConfiguration config; - @NonNull private final Context context; - @NonNull private final Method method; private final String login; private final String password; @@ -167,6 +162,7 @@ protected void configureHeaders(@NonNull HttpURLConnection connection, @Nullable } } + @NonNull protected abstract String getContentType(@NonNull Context context, @NonNull T t); @SuppressWarnings("WeakerAccess") @@ -191,7 +187,8 @@ protected void writeContent(@NonNull HttpURLConnection connection, @NonNull Meth } } - protected abstract byte[] asBytes(T content) throws IOException; + @NonNull + protected abstract byte[] asBytes(@NonNull T content) throws IOException; @SuppressWarnings("WeakerAccess") protected void handleResponse(int responseCode, String responseMessage) throws IOException { @@ -204,7 +201,7 @@ protected void handleResponse(int responseCode, String responseMessage) throws I //timeout or server error. Repeat the request later. ACRA.log.w(LOG_TAG, "Could not send ACRA Post responseCode=" + responseCode + " message=" + responseMessage); throw new IOException("Host returned error code " + responseCode); - } else if (responseCode >= HttpURLConnection.HTTP_BAD_REQUEST && responseCode < HttpURLConnection.HTTP_INTERNAL_ERROR) { + } else if (responseCode >= HttpURLConnection.HTTP_BAD_REQUEST) { // Client error. The request must not be repeated. Discard it. ACRA.log.w(LOG_TAG, responseCode + ": Client error - request will be discarded"); } else { diff --git a/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.java b/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.java index 6f9396256..22700f9fe 100644 --- a/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.java +++ b/acra-http/src/main/java/org/acra/http/BinaryHttpRequest.java @@ -34,7 +34,6 @@ */ public class BinaryHttpRequest extends BaseHttpRequest { - @NonNull private final Context context; public BinaryHttpRequest(@NonNull CoreConfiguration config, @NonNull Context context, @@ -43,13 +42,15 @@ public BinaryHttpRequest(@NonNull CoreConfiguration config, @NonNull Context con this.context = context; } + @NonNull @Override protected String getContentType(@NonNull Context context, @NonNull Uri uri) { return UriUtils.getMimeType(context, uri); } + @NonNull @Override - protected byte[] asBytes(Uri content) throws IOException { + protected byte[] asBytes(@NonNull Uri content) throws IOException { return UriUtils.uriToByteArray(context, content); } } diff --git a/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.java b/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.java index ddb4c8d94..8225983d8 100644 --- a/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.java +++ b/acra-http/src/main/java/org/acra/http/DefaultHttpRequest.java @@ -32,7 +32,6 @@ */ public class DefaultHttpRequest extends BaseHttpRequest { - @NonNull private final String contentType; public DefaultHttpRequest(@NonNull CoreConfiguration config, @NonNull Context context, @NonNull HttpSender.Method method, @NonNull String contentType, @@ -41,13 +40,15 @@ public DefaultHttpRequest(@NonNull CoreConfiguration config, @NonNull Context co this.contentType = contentType; } + @NonNull @Override protected String getContentType(@NonNull Context context, @NonNull String s) { return contentType; } + @NonNull @Override - protected byte[] asBytes(String content) throws IOException { + protected byte[] asBytes(@NonNull String content) throws IOException { return content.getBytes(ACRAConstants.UTF8); } } diff --git a/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.java b/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.java index e443d46f2..d4b7b327d 100644 --- a/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.java +++ b/acra-http/src/main/java/org/acra/http/MultipartHttpRequest.java @@ -47,9 +47,7 @@ public class MultipartHttpRequest extends BaseHttpRequest private static final String BOUNDARY_FIX = "--"; private static final String NEW_LINE = "\r\n"; private static final String CONTENT_TYPE = "Content-Type: "; - @NonNull private final Context context; - @NonNull private final String contentType; public MultipartHttpRequest(@NonNull CoreConfiguration config, @NonNull Context context, @NonNull String contentType, @Nullable String login, @Nullable String password, @@ -59,13 +57,15 @@ public MultipartHttpRequest(@NonNull CoreConfiguration config, @NonNull Context this.contentType = contentType; } + @NonNull @Override protected String getContentType(@NonNull Context context, @NonNull Pair> stringListPair) { return "multipart/mixed; boundary=" + BOUNDARY; } + @NonNull @Override - protected byte[] asBytes(Pair> content) throws IOException { + protected byte[] asBytes(@NonNull Pair> content) throws IOException { final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); final Writer writer = new OutputStreamWriter(outputStream, ACRAConstants.UTF8); //noinspection TryFinallyCanBeTryWithResources we do not target api 19 diff --git a/acra-http/src/main/java/org/acra/security/BaseKeyStoreFactory.java b/acra-http/src/main/java/org/acra/security/BaseKeyStoreFactory.java index b51c98217..fa717f664 100644 --- a/acra-http/src/main/java/org/acra/security/BaseKeyStoreFactory.java +++ b/acra-http/src/main/java/org/acra/security/BaseKeyStoreFactory.java @@ -67,16 +67,19 @@ public BaseKeyStoreFactory(String certificateType) { this.certificateType = certificateType; } + @Nullable protected abstract InputStream getInputStream(@NonNull Context context); protected String getKeyStoreType() { return KeyStore.getDefaultType(); } + @NonNull protected Type getStreamType() { return Type.CERTIFICATE; } + @Nullable protected char[] getPassword() { return null; } diff --git a/acra-http/src/main/java/org/acra/sender/HttpSender.java b/acra-http/src/main/java/org/acra/sender/HttpSender.java index 5f25141a7..9cf21df5f 100644 --- a/acra-http/src/main/java/org/acra/sender/HttpSender.java +++ b/acra-http/src/main/java/org/acra/sender/HttpSender.java @@ -25,7 +25,7 @@ import org.acra.ACRAConstants; import org.acra.ReportField; import org.acra.attachment.DefaultAttachmentProvider; -import org.acra.config.Configuration; +import org.acra.config.ConfigUtils; import org.acra.config.CoreConfiguration; import org.acra.config.HttpSenderConfiguration; import org.acra.data.CrashReportData; @@ -57,30 +57,30 @@ public class HttpSender implements ReportSender { */ public enum Method { POST { + @NonNull @Override - URL createURL(String baseUrl, CrashReportData report) throws MalformedURLException { + URL createURL(@NonNull String baseUrl, @NonNull CrashReportData report) throws MalformedURLException { return new URL(baseUrl); } }, PUT { + @NonNull @Override - URL createURL(String baseUrl, CrashReportData report) throws MalformedURLException { + URL createURL(@NonNull String baseUrl, @NonNull CrashReportData report) throws MalformedURLException { return new URL(baseUrl + '/' + report.getString(ReportField.REPORT_ID)); } }; - abstract URL createURL(String baseUrl, CrashReportData report) throws MalformedURLException; + @NonNull + abstract URL createURL(@NonNull String baseUrl, @NonNull CrashReportData report) throws MalformedURLException; } private final CoreConfiguration config; private final HttpSenderConfiguration httpConfig; - @NonNull private final Uri mFormUri; private final Method mMethod; private final StringFormat mType; - @Nullable private String mUsername; - @Nullable private String mPassword; /** @@ -113,7 +113,7 @@ public HttpSender(@NonNull CoreConfiguration config, @Nullable Method method, @N */ public HttpSender(@NonNull CoreConfiguration config, @Nullable Method method, @Nullable StringFormat type, @Nullable String formUri) { this.config = config; - this.httpConfig = getHttpSenderConfiguration(config); + this.httpConfig = ConfigUtils.getPluginConfiguration(config, HttpSenderConfiguration.class); mMethod = (method == null) ? httpConfig.httpMethod() : method; mFormUri = Uri.parse((formUri == null) ? httpConfig.uri() : formUri); mType = (type == null) ? config.reportFormat() : type; @@ -121,17 +121,6 @@ public HttpSender(@NonNull CoreConfiguration config, @Nullable Method method, @N mPassword = null; } - private static HttpSenderConfiguration getHttpSenderConfiguration(CoreConfiguration config) { - HttpSenderConfiguration httpSenderConfiguration = null; - for (Configuration configuration : config.pluginConfigurations()) { - if (configuration instanceof HttpSenderConfiguration) { - httpSenderConfiguration = (HttpSenderConfiguration) configuration; - break; - } - } - return httpSenderConfiguration; - } - /** *

* Set credentials for this HttpSender that override (if present) the ones set globally. @@ -224,8 +213,9 @@ protected void putAttachment(@NonNull CoreConfiguration configuration, @NonNull * @return a string representation of the report * @throws Exception if conversion failed */ + @NonNull @SuppressWarnings("WeakerAccess") - protected String convertToString(CrashReportData report, StringFormat format) throws Exception { + protected String convertToString(CrashReportData report, @NonNull StringFormat format) throws Exception { return format.toFormattedString(report, config.reportContent(), "&", "\n", true); } diff --git a/acra-http/src/main/java/org/acra/util/UriUtils.java b/acra-http/src/main/java/org/acra/util/UriUtils.java index b4653bb2d..9d2b20b81 100644 --- a/acra-http/src/main/java/org/acra/util/UriUtils.java +++ b/acra-http/src/main/java/org/acra/util/UriUtils.java @@ -41,7 +41,7 @@ private UriUtils() { } @NonNull - public static byte[] uriToByteArray(@NonNull Context context, Uri uri) throws IOException { + public static byte[] uriToByteArray(@NonNull Context context, @NonNull Uri uri) throws IOException { final InputStream inputStream = context.getContentResolver().openInputStream(uri); if (inputStream == null) { throw new FileNotFoundException("Could not open " + uri.toString()); @@ -55,7 +55,8 @@ public static byte[] uriToByteArray(@NonNull Context context, Uri uri) throws IO return outputStream.toByteArray(); } - public static String getFileNameFromUri(Context context, Uri uri) { + @NonNull + public static String getFileNameFromUri(@NonNull Context context, @NonNull Uri uri) { String result = null; if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); @@ -79,10 +80,12 @@ public static String getFileNameFromUri(Context context, Uri uri) { return result; } - public static String getMimeType(Context context, Uri uri) { + @NonNull + public static String getMimeType(@NonNull Context context, @NonNull Uri uri) { if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { final ContentResolver contentResolver = context.getContentResolver(); - return contentResolver.getType(uri); + String type = contentResolver.getType(uri); + if (type != null) return type; } return AcraContentProvider.guessMimeType(uri); } diff --git a/acra-javacore/src/main/java/org/acra/collections/ImmutableList.java b/acra-javacore/src/main/java/org/acra/collections/ImmutableList.java index 63b9ecf72..96e1311af 100644 --- a/acra-javacore/src/main/java/org/acra/collections/ImmutableList.java +++ b/acra-javacore/src/main/java/org/acra/collections/ImmutableList.java @@ -40,7 +40,7 @@ public ImmutableList(E... elements) { this(Arrays.asList(elements)); } - public ImmutableList(Collection collection) { + public ImmutableList(@NonNull Collection collection) { this.mList = new ArrayList<>(collection); } diff --git a/acra-javacore/src/main/java/org/acra/collections/ImmutableMap.java b/acra-javacore/src/main/java/org/acra/collections/ImmutableMap.java index 8fe87777b..a256bf991 100644 --- a/acra-javacore/src/main/java/org/acra/collections/ImmutableMap.java +++ b/acra-javacore/src/main/java/org/acra/collections/ImmutableMap.java @@ -33,7 +33,7 @@ public final class ImmutableMap implements Map, Serializable { private final Map mMap; - public ImmutableMap(Map map) { + public ImmutableMap(@NonNull Map map) { this.mMap = new HashMap<>(map); } @@ -105,7 +105,7 @@ public Collection values() { return new ImmutableList<>(mMap.values()); } - public static class ImmutableEntryWrapper implements Map.Entry { + private static class ImmutableEntryWrapper implements Map.Entry { private final Map.Entry mEntry; ImmutableEntryWrapper(Entry mEntry) { @@ -122,6 +122,7 @@ public V getValue() { return mEntry.getValue(); } + @NonNull @Override public V setValue(Object object) { throw new UnsupportedOperationException(); diff --git a/acra-javacore/src/main/java/org/acra/collections/ImmutableSet.java b/acra-javacore/src/main/java/org/acra/collections/ImmutableSet.java index 85736daec..6597bbe4e 100644 --- a/acra-javacore/src/main/java/org/acra/collections/ImmutableSet.java +++ b/acra-javacore/src/main/java/org/acra/collections/ImmutableSet.java @@ -34,6 +34,7 @@ public final class ImmutableSet implements Set, Serializable { private static final ImmutableSet EMPTY = new ImmutableSet<>(); + @NonNull public static ImmutableSet empty() { //noinspection unchecked return (ImmutableSet) EMPTY; @@ -50,7 +51,7 @@ public ImmutableSet(E... elements) { this(Arrays.asList(elements)); } - public ImmutableSet(Collection collection) { + public ImmutableSet(@NonNull Collection collection) { this.mSet = new LinkedHashSet<>(collection); } diff --git a/acra-limiter/src/main/java/org/acra/annotation/AcraLimiter.java b/acra-limiter/src/main/java/org/acra/annotation/AcraLimiter.java index f84e4e3f7..456bb174c 100644 --- a/acra-limiter/src/main/java/org/acra/annotation/AcraLimiter.java +++ b/acra-limiter/src/main/java/org/acra/annotation/AcraLimiter.java @@ -16,6 +16,7 @@ package org.acra.annotation; +import android.support.annotation.NonNull; import android.support.annotation.StringRes; import org.acra.ACRAConstants; @@ -46,7 +47,7 @@ * @return a time unit * @since 5.0.0 */ - TimeUnit periodUnit() default TimeUnit.DAYS; + @NonNull TimeUnit periodUnit() default TimeUnit.DAYS; /** * Reports which have been collected before this will not be considered for any limits except {@link #failedReportLimit()} diff --git a/acra-limiter/src/main/java/org/acra/config/LimiterData.java b/acra-limiter/src/main/java/org/acra/config/LimiterData.java index 6c449001a..ce8763579 100644 --- a/acra-limiter/src/main/java/org/acra/config/LimiterData.java +++ b/acra-limiter/src/main/java/org/acra/config/LimiterData.java @@ -17,6 +17,7 @@ package org.acra.config; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import org.acra.ACRAConstants; import org.acra.ReportField; @@ -38,10 +39,10 @@ * @since 26.10.2017 */ -public class LimiterData { +class LimiterData { private final List list; - public LimiterData(String json) throws JSONException { + LimiterData(@Nullable String json) throws JSONException { list = new ArrayList<>(); if (json != null && !json.isEmpty()) { final JSONArray array = new JSONArray(json); @@ -52,13 +53,14 @@ public LimiterData(String json) throws JSONException { } } - public List getReportMetadata() throws JSONException { + @NonNull + public List getReportMetadata() { return list; } public void purgeOldData(Calendar keepAfter) { for (final Iterator iterator = list.iterator(); iterator.hasNext(); ) { - if (iterator.next().getTimestamp().before(keepAfter)) { + if (keepAfter.after(iterator.next().getTimestamp())) { iterator.remove(); } } @@ -73,7 +75,7 @@ public static class ReportMetadata extends JSONObject { private static final String KEY_EXCEPTION_CLASS = "class"; private static final String KEY_TIMESTAMP = "timestamp"; - public ReportMetadata(CrashReportData crashReportData) throws JSONException { + ReportMetadata(@NonNull CrashReportData crashReportData) throws JSONException { final String stacktrace = crashReportData.getString(ReportField.STACK_TRACE); put(KEY_STACK_TRACE, stacktrace); final int index = stacktrace.indexOf('\n'); @@ -90,14 +92,10 @@ public ReportMetadata(CrashReportData crashReportData) throws JSONException { } - public ReportMetadata(JSONObject copyFrom) throws JSONException { + ReportMetadata(@NonNull JSONObject copyFrom) throws JSONException { super(copyFrom, jsonArrayToList(copyFrom.names())); } - public ReportMetadata(String json) throws JSONException { - super(json); - } - public String getStacktrace() { return optString(KEY_STACK_TRACE); } @@ -106,7 +104,8 @@ public String getExceptionClass() { return optString(KEY_EXCEPTION_CLASS); } - public Calendar getTimestamp() { + @Nullable + Calendar getTimestamp() { final String timestamp = optString(KEY_TIMESTAMP); if (timestamp != null) { try { @@ -121,7 +120,7 @@ public Calendar getTimestamp() { } @NonNull - private static String[] jsonArrayToList(JSONArray array) { + private static String[] jsonArrayToList(@Nullable JSONArray array) { final List list = new ArrayList<>(); if (array != null) { final int length = array.length(); diff --git a/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.java b/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.java index 76abde1ca..d80c9ed65 100644 --- a/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.java +++ b/acra-limiter/src/main/java/org/acra/config/LimitingReportAdministrator.java @@ -26,7 +26,6 @@ import com.google.auto.service.AutoService; import org.acra.ACRA; -import org.acra.ACRAConstants; import org.acra.builder.ReportBuilder; import org.acra.data.CrashReportData; import org.acra.file.ReportLocator; @@ -108,12 +107,12 @@ public boolean shouldSendReport(@NonNull Context context, @NonNull CoreConfigura @Override public void notifyReportDropped(@NonNull final Context context, @NonNull final CoreConfiguration config) { final LimiterConfiguration limiterConfiguration = ConfigUtils.getPluginConfiguration(config, LimiterConfiguration.class); - if (limiterConfiguration.resIgnoredCrashToast() != ACRAConstants.DEFAULT_RES_VALUE) { + if (limiterConfiguration.ignoredCrashToast() != null) { final Future future = Executors.newSingleThreadExecutor().submit(new Runnable() { @Override public void run() { Looper.prepare(); - ToastSender.sendToast(context, limiterConfiguration.resIgnoredCrashToast(), Toast.LENGTH_LONG); + ToastSender.sendToast(context, limiterConfiguration.ignoredCrashToast(), Toast.LENGTH_LONG); final Looper looper = Looper.myLooper(); if (looper != null) { new Handler(looper).postDelayed(new Runnable() { @@ -147,7 +146,8 @@ public boolean enabled(@NonNull CoreConfiguration config) { return ConfigUtils.getPluginConfiguration(config, LimiterConfiguration.class).enabled(); } - private LimiterData loadLimiterData(@NonNull Context context, LimiterConfiguration limiterConfiguration) throws IOException, JSONException { + @NonNull + private LimiterData loadLimiterData(@NonNull Context context, @NonNull LimiterConfiguration limiterConfiguration) throws IOException, JSONException { String data = null; try { data = new StreamReader(context.openFileInput(FILE_LIMITER_DATA)).read(); diff --git a/acra-mail/src/main/java/org/acra/annotation/AcraMailSender.java b/acra-mail/src/main/java/org/acra/annotation/AcraMailSender.java index 72f5f9af6..7632e9d18 100644 --- a/acra-mail/src/main/java/org/acra/annotation/AcraMailSender.java +++ b/acra-mail/src/main/java/org/acra/annotation/AcraMailSender.java @@ -71,5 +71,5 @@ * @return resource id of the custom email subject * @since 5.0.1 */ - @StringRes int subject() default ACRAConstants.DEFAULT_RES_VALUE; + @StringRes int resSubject() default ACRAConstants.DEFAULT_RES_VALUE; } diff --git a/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.java b/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.java index 64ca13f7c..34ccaa366 100644 --- a/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.java +++ b/acra-mail/src/main/java/org/acra/sender/EmailIntentSender.java @@ -190,7 +190,7 @@ private void showChooser(@NonNull Context context, @NonNull List initial context.startActivity(chooser); } - private void grantPermission(@NonNull Context context, Intent intent, String packageName, List attachments) { + private void grantPermission(@NonNull Context context, @NonNull Intent intent, String packageName, @NonNull List attachments) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); } else { @@ -209,8 +209,9 @@ private void grantPermission(@NonNull Context context, Intent intent, String pac */ @NonNull protected String buildSubject(@NonNull Context context) { - if (mailConfig.subject() != ACRAConstants.DEFAULT_RES_VALUE) { - return context.getString(mailConfig.subject()); + final String subject = mailConfig.subject(); + if (subject != null) { + return subject; } return context.getPackageName() + " Crash Report"; } diff --git a/acra-notification/build.gradle b/acra-notification/build.gradle index 7305c70ce..6defb3ec4 100644 --- a/acra-notification/build.gradle +++ b/acra-notification/build.gradle @@ -18,15 +18,9 @@ apply plugin: 'com.android.library' apply plugin: 'maven-publish' apply plugin: 'com.jfrog.bintray' -android { - defaultConfig { - minSdkVersion 14 - } -} - dependencies { api project(':acra-core') - implementation 'com.android.support:support-compat:26.1.0' + implementation "com.android.support:support-compat:$supportVersion" compileOnly "com.google.auto.service:auto-service:$autoServiceVersion" annotationProcessor project(':annotationprocessor') compileOnly project(':annotations') diff --git a/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.java b/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.java index 9e02ea758..9ebf52b85 100644 --- a/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.java +++ b/acra-notification/src/main/java/org/acra/interaction/NotificationInteraction.java @@ -31,7 +31,6 @@ import com.google.auto.service.AutoService; import org.acra.ACRA; -import org.acra.ACRAConstants; import org.acra.config.ConfigUtils; import org.acra.config.CoreConfiguration; import org.acra.config.NotificationConfiguration; @@ -76,39 +75,38 @@ public boolean performInteraction(@NonNull Context context, @NonNull CoreConfigu final NotificationConfiguration notificationConfig = ConfigUtils.getPluginConfiguration(config, NotificationConfiguration.class); //We have to create a channel on Oreo+, because notifications without one aren't allowed if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - final NotificationChannel channel = new NotificationChannel(CHANNEL, context.getString(notificationConfig.resChannelName()), notificationConfig.resChannelImportance()); + final NotificationChannel channel = new NotificationChannel(CHANNEL, notificationConfig.channelName(), notificationConfig.resChannelImportance()); channel.setSound(null, null); - if (notificationConfig.resChannelDescription() != ACRAConstants.DEFAULT_RES_VALUE) { - channel.setDescription(context.getString(notificationConfig.resChannelDescription())); + if (notificationConfig.channelDescription() != null) { + channel.setDescription(notificationConfig.channelDescription()); } notificationManager.createNotificationChannel(channel); } //configure base notification final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, CHANNEL) .setWhen(System.currentTimeMillis()) - .setContentTitle(context.getString(notificationConfig.resTitle())) - .setContentText(context.getString(notificationConfig.resText())) + .setContentTitle(notificationConfig.title()) + .setContentText(notificationConfig.text()) .setSmallIcon(notificationConfig.resIcon()) .setPriority(NotificationCompat.PRIORITY_HIGH); //add ticker if set - if (notificationConfig.resTickerText() != ACRAConstants.DEFAULT_RES_VALUE) { - notification.setTicker(context.getString(notificationConfig.resTickerText())); + if (notificationConfig.tickerText() != null) { + notification.setTicker(notificationConfig.tickerText()); } final PendingIntent sendIntent = getSendIntent(context, config, reportFile); final PendingIntent discardIntent = getDiscardIntent(context); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && notificationConfig.resSendWithCommentButtonText() != ACRAConstants.DEFAULT_RES_VALUE) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && notificationConfig.sendWithCommentButtonText() != null) { final RemoteInput.Builder remoteInput = new RemoteInput.Builder(KEY_COMMENT); - if (notificationConfig.resCommentPrompt() != ACRAConstants.DEFAULT_RES_VALUE) { - remoteInput.setLabel(context.getString(notificationConfig.resCommentPrompt())); + if (notificationConfig.commentPrompt() != null) { + remoteInput.setLabel(notificationConfig.commentPrompt()); } - notification.addAction(new NotificationCompat.Action.Builder(notificationConfig.resSendWithCommentButtonIcon(), - context.getString(notificationConfig.resSendWithCommentButtonText()), sendIntent) + notification.addAction(new NotificationCompat.Action.Builder(notificationConfig.resSendWithCommentButtonIcon(), notificationConfig.sendWithCommentButtonText(), sendIntent) .addRemoteInput(remoteInput.build()).build()); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { final RemoteViews bigView = getBigView(context, notificationConfig); - notification.addAction(notificationConfig.resSendButtonIcon(), context.getString(notificationConfig.resSendButtonText()), sendIntent) - .addAction(notificationConfig.resDiscardButtonIcon(), context.getString(notificationConfig.resDiscardButtonText()), discardIntent) + notification.addAction(notificationConfig.resSendButtonIcon(), notificationConfig.sendButtonText(), sendIntent) + .addAction(notificationConfig.resDiscardButtonIcon(), notificationConfig.discardButtonText(), discardIntent) .setCustomContentView(getSmallView(context, notificationConfig, sendIntent, discardIntent)) .setCustomBigContentView(bigView) .setCustomHeadsUpContentView(bigView) @@ -137,10 +135,11 @@ private PendingIntent getDiscardIntent(@NonNull Context context) { return PendingIntent.getBroadcast(context, ACTION_DISCARD, intent, PendingIntent.FLAG_UPDATE_CURRENT); } + @NonNull private RemoteViews getSmallView(@NonNull Context context, @NonNull NotificationConfiguration notificationConfig, @NonNull PendingIntent sendIntent, @NonNull PendingIntent discardIntent) { final RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.notification_small); - view.setTextViewText(R.id.text, context.getString(notificationConfig.resText())); - view.setTextViewText(R.id.title, context.getString(notificationConfig.resTitle())); + view.setTextViewText(R.id.text, notificationConfig.text()); + view.setTextViewText(R.id.title, notificationConfig.title()); view.setImageViewResource(R.id.button_send, notificationConfig.resSendButtonIcon()); view.setImageViewResource(R.id.button_discard, notificationConfig.resDiscardButtonIcon()); view.setOnClickPendingIntent(R.id.button_send, sendIntent); @@ -148,10 +147,11 @@ private RemoteViews getSmallView(@NonNull Context context, @NonNull Notification return view; } + @NonNull private RemoteViews getBigView(@NonNull Context context, @NonNull NotificationConfiguration notificationConfig) { final RemoteViews view = new RemoteViews(context.getPackageName(), R.layout.notification_big); - view.setTextViewText(R.id.text, context.getString(notificationConfig.resText())); - view.setTextViewText(R.id.title, context.getString(notificationConfig.resTitle())); + view.setTextViewText(R.id.text, notificationConfig.text()); + view.setTextViewText(R.id.title, notificationConfig.title()); return view; } } diff --git a/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.java b/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.java index 15d190a30..a3f335300 100644 --- a/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.java +++ b/acra-notification/src/main/java/org/acra/receiver/NotificationBroadcastReceiver.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.app.RemoteInput; import org.acra.ACRA; @@ -47,7 +48,7 @@ public class NotificationBroadcastReceiver extends BroadcastReceiver { @Override - public void onReceive(Context context, Intent intent) { + public void onReceive(@NonNull Context context, @NonNull Intent intent) { try { final NotificationManager notificationManager = SystemServices.getNotificationManager(context); notificationManager.cancel(NotificationInteraction.NOTIFICATION_ID); @@ -70,7 +71,7 @@ public void onReceive(Context context, Intent intent) { final CrashReportData crashData = persister.load(reportFile); crashData.put(USER_COMMENT, comment.toString()); persister.store(crashData, reportFile); - } catch (IOException | JSONException e) { + } catch (@NonNull IOException | JSONException e) { ACRA.log.w(LOG_TAG, "User comment not added: ", e); } } diff --git a/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.java b/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.java index dca2648a3..91afcbbd0 100644 --- a/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.java +++ b/acra-toast/src/main/java/org/acra/interaction/ToastInteraction.java @@ -50,7 +50,7 @@ public ToastInteraction() { @Override public boolean performInteraction(@NonNull Context context, @NonNull CoreConfiguration config, @NonNull File reportFile) { Looper.prepare(); - ToastSender.sendToast(context, ConfigUtils.getPluginConfiguration(config, ToastConfiguration.class).resText(), Toast.LENGTH_LONG); + ToastSender.sendToast(context, ConfigUtils.getPluginConfiguration(config, ToastConfiguration.class).text(), Toast.LENGTH_LONG); final Looper looper = Looper.myLooper(); if(looper != null) { new Handler(looper).postDelayed(new Runnable() { diff --git a/annotationprocessor/build.gradle b/annotationprocessor/build.gradle index 4d0d9f333..caa1da5c3 100644 --- a/annotationprocessor/build.gradle +++ b/annotationprocessor/build.gradle @@ -20,10 +20,11 @@ dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile "com.google.auto.service:auto-service:$autoServiceVersion" compile 'com.squareup:javapoet:1.9.0' - compile 'org.apache.commons:commons-lang3:3.6' + compile 'org.apache.commons:commons-lang3:3.7' + compile 'org.apache.commons:commons-text:1.1' compile project(':annotations') compile project(':acra-javacore') } -sourceCompatibility = "1.8" -targetCompatibility = "1.8" +sourceCompatibility = "1.9" +targetCompatibility = "1.9" diff --git a/annotationprocessor/src/main/java/org/acra/ModelUtils.java b/annotationprocessor/src/main/java/org/acra/ModelUtils.java deleted file mode 100644 index c45021b21..000000000 --- a/annotationprocessor/src/main/java/org/acra/ModelUtils.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (c) 2017 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.acra; - -import com.google.auto.common.MoreTypes; -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.ArrayTypeName; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.JavaFile; -import com.squareup.javapoet.ParameterSpec; -import com.squareup.javapoet.ParameterizedTypeName; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; -import com.squareup.javapoet.TypeVariableName; - -import org.acra.annotation.AnyNonDefault; -import org.acra.annotation.Configuration; -import org.acra.annotation.Instantiatable; -import org.acra.annotation.NonEmpty; -import org.acra.collections.ImmutableList; -import org.acra.collections.ImmutableMap; -import org.acra.collections.ImmutableSet; -import org.acra.definition.Type; - -import java.io.IOException; -import java.text.DateFormat; -import java.util.Arrays; -import java.util.Calendar; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.MirroredTypeException; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import javax.tools.Diagnostic; - -/** - * Collection of constants and helper methods to generate ACRA classes - * - * @author F43nd1r - * @since 18.03.2017 - */ - -public class ModelUtils { - public static final String PARAM_0 = "arg0"; - public static final String VAR_0 = "var0"; - public static final String FIELD_0 = "field0"; - public static final String PACKAGE = "org.acra.config"; - private static final ClassName IMMUTABLE_MAP = ClassName.get(ImmutableMap.class); - private static final ClassName IMMUTABLE_SET = ClassName.get(ImmutableSet.class); - private static final ClassName IMMUTABLE_LIST = ClassName.get(ImmutableList.class); - private static final ClassName MAP = ClassName.get(Map.class); - private static final ClassName SET = ClassName.get(Set.class); - private static final ClassName LIST = ClassName.get(List.class); - private static final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance(); - - private final Types typeUtils; - private final Elements elementUtils; - private final ProcessingEnvironment processingEnv; - - ModelUtils(ProcessingEnvironment processingEnv) { - this.processingEnv = processingEnv; - typeUtils = processingEnv.getTypeUtils(); - elementUtils = processingEnv.getElementUtils(); - } - - public Type getType(Supplier supplier) { - TypeMirror mirror; - try { - mirror = elementUtils.getTypeElement(supplier.get().getName()).asType(); - } catch (MirroredTypeException e) { - mirror = e.getTypeMirror(); - } - return getType(mirror); - } - - public Type getType(TypeMirror mirror) { - return new Type(mirror.getKind() == TypeKind.TYPEVAR || mirror.getKind().isPrimitive() || mirror.getKind() == TypeKind.ARRAY || mirror.getKind() == TypeKind.VOID - ? null : MoreTypes.asTypeElement(typeUtils, mirror), mirror, TypeName.get(mirror)); - } - - public Type getType(Class c) { - return new Type(elementUtils.getTypeElement(c.getName())); - } - - public Type getBooleanType(){ - return new Type(null, typeUtils.getPrimitiveType(TypeKind.BOOLEAN), TypeName.BOOLEAN); - } - - /** - * Returns an immutable type extending this type, or if the type is an array a immutable list type - * - * @param type the type - * @return the immutable counterpart (might be type, if type is already immutable or no immutable type was found) - */ - public TypeName getImmutableType(TypeName type) { - if (type instanceof ParameterizedTypeName) { - final TypeName genericType = ((ParameterizedTypeName) type).rawType; - if (MAP.equals(genericType)) { - return getWithParams(IMMUTABLE_MAP, (ParameterizedTypeName) type); - } else if (SET.equals(genericType)) { - return getWithParams(IMMUTABLE_SET, (ParameterizedTypeName) type); - } else if (LIST.equals(genericType)) { - return getWithParams(IMMUTABLE_LIST, (ParameterizedTypeName) type); - } - } else if (type instanceof ArrayTypeName) { - return ParameterizedTypeName.get(IMMUTABLE_LIST, ((ArrayTypeName) type).componentType); - } - return type; - } - - /** - * Creates a type based on base, but with the type parameters from parameterType - * - * @param baseType base - * @param parameterType parameterType - * @return the parametrized type - */ - private TypeName getWithParams(ClassName baseType, ParameterizedTypeName parameterType) { - return ParameterizedTypeName.get(baseType, parameterType.typeArguments.toArray(new TypeName[parameterType.typeArguments.size()])); - } - - /** - * Writes the given class to a respective file in the configuration package - * - * @param typeSpec the class - * @throws IOException if writing fails - */ - public void write(TypeSpec typeSpec) throws IOException { - JavaFile.builder(PACKAGE, typeSpec) - .skipJavaLangImports(true) - .indent(" ") - .addFileComment("Copyright (c) " + Calendar.getInstance().get(Calendar.YEAR) + "\n\n" + - "Licensed under the Apache License, Version 2.0 (the \"License\");\n" + - "you may not use this file except in compliance with the License.\n\n" + - "http://www.apache.org/licenses/LICENSE-2.0\n\n" + - "Unless required by applicable law or agreed to in writing, software\n" + - "distributed under the License is distributed on an \"AS IS\" BASIS,\n" + - "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + - "See the License for the specific language governing permissions and\n" + - "limitations under the License.") - .build() - .writeTo(processingEnv.getFiler()); - } - - private static final List excludeAnnotationsFromBuilder = Arrays.asList(ClassName.get(NonEmpty.class), - ClassName.get(AnyNonDefault.class), ClassName.get(Instantiatable.class)); - - /** - * @param method a method - * @return annotationSpecs for all relevant annotations on the method - */ - public List getAnnotations(ExecutableElement method) { - return method.getAnnotationMirrors().stream().map(AnnotationSpec::get) - .filter(annotationSpec -> !excludeAnnotationsFromBuilder.contains(annotationSpec.type)).collect(Collectors.toList()); - } - - public void addClassJavadoc(TypeSpec.Builder builder, TypeElement base) { - builder.addJavadoc("Class generated based on {@link $L} ($L)\n", base.getQualifiedName(), DATE_FORMAT.format(Calendar.getInstance().getTime())); - } - - public ExecutableElement getOnlyMethod(TypeElement typeElement) { - final List elements = getMethods(typeElement); - if (elements.size() == 1) { - return elements.get(0); - } else { - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Needs exactly one method", typeElement); - throw new IllegalArgumentException(); - } - } - - public void error(Element element, String annotationField, String message) { - final AnnotationMirror mirror = element.getAnnotationMirrors().stream() - .filter(m -> MoreTypes.isTypeOf(Configuration.class, m.getAnnotationType())) - .findAny().orElseThrow(IllegalArgumentException::new); - processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, message, element, mirror, mirror.getElementValues().entrySet().stream() - .filter(entry -> entry.getKey().getSimpleName().toString().equals(annotationField)).findAny().map(Map.Entry::getValue).orElse(null)); - throw new IllegalArgumentException(); - } - - public List getConstructors(Element element) { - return ElementFilter.constructorsIn(element.getEnclosedElements()); - } - - public List getMethods(Element element) { - return ElementFilter.methodsIn(element.getEnclosedElements()); - } - - public boolean hasClassParameter(ExecutableElement method) { - return method.getParameters().stream().map(VariableElement::asType).map(typeUtils::asElement).map(Element::toString).anyMatch(Class.class.getName()::equals); - } - - public TypeMirror erasure(TypeMirror t) { - return typeUtils.erasure(t); - } - - public String getJavadoc(Element element) { - return elementUtils.getDocComment(element); - } - - public List getParameters(ExecutableElement method){ - return method.getParameters().stream().map(p -> ParameterSpec.builder(TypeName.get(p.asType()), p.getSimpleName().toString()).build()).collect(Collectors.toList()); - } - - public List getTypeParameters(ExecutableElement method){ - return method.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList()); - } -} diff --git a/annotationprocessor/src/main/java/org/acra/creator/BuildMethodCreator.java b/annotationprocessor/src/main/java/org/acra/creator/BuildMethodCreator.java deleted file mode 100644 index 11073e90d..000000000 --- a/annotationprocessor/src/main/java/org/acra/creator/BuildMethodCreator.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2017 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.acra.creator; - -import android.support.annotation.NonNull; - -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.CodeBlock; -import com.squareup.javapoet.MethodSpec; - -import org.acra.annotation.AnyNonDefault; -import org.acra.annotation.Instantiatable; -import org.acra.annotation.NonEmpty; -import org.acra.config.ACRAConfigurationException; -import org.acra.config.ClassValidator; -import org.acra.definition.Field; -import org.acra.definition.Type; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import javax.lang.model.element.ExecutableElement; - -/** - * @author F43nd1r - * @since 12.06.2017 - */ - -class BuildMethodCreator { - private final MethodSpec.Builder methodBuilder; - private final List anyNonDefault; - private final ClassName config; - private final List statements; - - BuildMethodCreator(ExecutableElement override, ClassName config) { - this.config = config; - methodBuilder = MethodSpec.overriding(override) - .addAnnotation(NonNull.class) - .returns(config) - .beginControlFlow("if ($L)", BuilderCreator.ENABLED); - anyNonDefault = new ArrayList<>(); - statements = new ArrayList<>(); - } - - void addValidation(Field field, ExecutableElement method) { - if (!field.hasDefault()) { - methodBuilder.beginControlFlow("if ($L == $L)", field.getName(), getDefault(field.getType())) - .addStatement("throw new $T(\"$L has to be set\")", ACRAConfigurationException.class, field.getName()) - .endControlFlow(); - } - if (method.getAnnotation(NonEmpty.class) != null) { - methodBuilder.beginControlFlow("if ($L().length == 0)", field.getName()) - .addStatement("throw new $T(\"$L cannot be empty\")", ACRAConfigurationException.class, field.getName()) - .endControlFlow(); - } - if (method.getAnnotation(Instantiatable.class) != null) { - methodBuilder.addStatement("$T.check($L())", ClassValidator.class, field.getName()); - } - if (method.getAnnotation(AnyNonDefault.class) != null) { - anyNonDefault.add(method); - } - } - - private String getDefault(Type type) { - switch (type.getMirror().getKind()) { - case BOOLEAN: - return "false"; - case BYTE: - return "0"; - case SHORT: - return "0"; - case INT: - return "0"; - case LONG: - return "0L"; - case CHAR: - return "\u0000"; - case FLOAT: - return "0.0f"; - case DOUBLE: - return "0.0d"; - default: - return "null"; - } - } - - void addMethodCall(String delegate, String methodName) { - statements.add(CodeBlock.builder().addStatement("$L.$L()", delegate, methodName).build()); - } - - MethodSpec build() { - if (anyNonDefault.size() > 0) { - methodBuilder.beginControlFlow("if ($L)", anyNonDefault.stream().map(m -> m.getSimpleName().toString() + "() == " + m.getDefaultValue()).collect(Collectors.joining(" && "))) - .addStatement("throw new $T(\"One of $L must not be default\")", ACRAConfigurationException.class, - anyNonDefault.stream().map(m -> m.getSimpleName().toString()).collect(Collectors.joining(", "))) - .endControlFlow(); - } - methodBuilder.endControlFlow(); - for (CodeBlock s : statements) { - methodBuilder.addCode(s); - } - methodBuilder.addStatement("return new $T(this)", config); - return methodBuilder.build(); - } - - -} diff --git a/annotationprocessor/src/main/java/org/acra/creator/BuilderCreator.java b/annotationprocessor/src/main/java/org/acra/creator/BuilderCreator.java deleted file mode 100644 index 38811243f..000000000 --- a/annotationprocessor/src/main/java/org/acra/creator/BuilderCreator.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2017 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.acra.creator; - -import android.support.annotation.NonNull; - -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.FieldSpec; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterSpec; -import com.squareup.javapoet.ParameterizedTypeName; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; -import com.squareup.javapoet.WildcardTypeName; - -import org.acra.ModelUtils; -import org.acra.annotation.PreBuild; -import org.acra.annotation.Transform; -import org.acra.config.ConfigurationBuilder; -import org.acra.definition.DelegateMethod; -import org.acra.definition.Field; -import org.acra.definition.FieldGetter; -import org.acra.definition.FieldSetter; -import org.acra.definition.Method; -import org.acra.definition.Transformer; -import org.acra.definition.Type; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; - -import static org.acra.ModelUtils.FIELD_0; -import static org.acra.ModelUtils.PARAM_0; -import static org.acra.ModelUtils.VAR_0; - -/** - * @author F43nd1r - * @since 11.06.2017 - */ - -class BuilderCreator { - static final String ENABLED = "enabled"; - - private final TypeSpec.Builder classBuilder; - private final MethodSpec.Builder constructor; - private final BuildMethodCreator build; - private final Type baseAnnotation; - private final ModelUtils utils; - private final Type baseBuilder; - private final ClassName builder; - private final List methods; - - BuilderCreator(Type baseAnnotation, Type baseBuilder, ClassName config, ClassName builder, ModelUtils utils) { - this.baseAnnotation = baseAnnotation; - this.utils = utils; - this.baseBuilder = baseBuilder; - this.builder = builder; - methods = new ArrayList<>(); - final Type configurationBuilder = utils.getType(ConfigurationBuilder.class); - classBuilder = TypeSpec.classBuilder(builder.simpleName()) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addSuperinterface(configurationBuilder.getName()); - utils.addClassJavadoc(classBuilder, baseAnnotation.getElement()); - constructor = MethodSpec.constructorBuilder() - .addModifiers(Modifier.PUBLIC) - .addParameter(ParameterSpec.builder(ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(Object.class)), PARAM_0).addAnnotation(NonNull.class).build()) - .addJavadoc("@param $L class annotated with {@link $T}\n", PARAM_0, baseAnnotation.getName()); - build = new BuildMethodCreator(utils.getOnlyMethod(configurationBuilder.getElement()), config); - final Field enabled = new Field(ENABLED, utils.getBooleanType(), Collections.emptyList(), null, null); - enabled.addTo(classBuilder, utils); - methods.add(new FieldGetter(enabled)); - methods.add(new FieldSetter(enabled, builder)); - } - - private void addConvenienceConstructor() { - classBuilder.addMethod(MethodSpec.constructorBuilder() - .addModifiers(Modifier.PUBLIC) - .addParameter(ParameterSpec.builder(TypeName.OBJECT, PARAM_0).addAnnotation(NonNull.class).build()) - .addStatement("this($L.getClass())", PARAM_0) - .addJavadoc("@param $L object annotated with {@link $T}\n", PARAM_0, baseAnnotation.getName()) - .build()); - } - - private void handleAnnotationMethods() { - constructor.addStatement("final $1T $2L = $3L.getAnnotation($1T.class)", baseAnnotation.getName(), VAR_0, PARAM_0) - .addStatement("$L = $L != null", ENABLED, VAR_0) - .beginControlFlow("if ($L)", ENABLED); - for (ExecutableElement method : utils.getMethods(baseAnnotation.getElement())) { - final Field field = Field.from(method, utils); - field.addTo(classBuilder, utils); - methods.add(new FieldGetter(field)); - methods.add(new FieldSetter(field, builder)); - build.addValidation(field, method); - constructor.addStatement("$L = $L.$L()", field.getName(), VAR_0, method.getSimpleName().toString()); - } - constructor.endControlFlow(); - } - - private boolean handleBaseBuilder() { - if (!baseBuilder.getName().equals(TypeName.OBJECT)) { - classBuilder.addField(FieldSpec.builder(baseBuilder.getName(), ModelUtils.FIELD_0, Modifier.PRIVATE, Modifier.FINAL).build()); - final List constructors = utils.getConstructors(baseBuilder.getElement()); - if (constructors.stream().anyMatch(c -> c.getParameters().size() == 0)) { - constructor.addStatement("$L = new $T()", FIELD_0, baseBuilder.getName()); - } else if (constructors.stream().anyMatch(c -> c.getParameters().size() == 1 && utils.hasClassParameter(c))) { - constructor.addStatement("$L = new $T($L)", FIELD_0, baseBuilder.getName(), PARAM_0); - } else { - utils.error(baseAnnotation.getElement(), "builderSuperClass", "Classes used as base builder must have a constructor which takes no arguments, " + - "or exactly one argument of type Class"); - } - return true; - } - return false; - } - - private void handleBaseBuilderMethods() { - for (ExecutableElement method : utils.getMethods(baseBuilder.getElement())) { - if (method.getAnnotation(PreBuild.class) != null) { - build.addMethodCall(FIELD_0, method.getSimpleName().toString()); - } else if (method.getAnnotation(Transform.class) != null) { - final String transform = method.getAnnotation(Transform.class).methodName(); - methods.stream().filter(m -> m instanceof FieldGetter).map(FieldGetter.class::cast).filter(m -> m.getName().equals(transform)).findAny() - .ifPresent(m -> methods.set(methods.indexOf(m), Transformer.from(method, FIELD_0, m, utils))); - } else { - methods.add(DelegateMethod.from(method, FIELD_0, builder, utils)); - } - } - } - - private void createMethods() { - methods.forEach(m -> m.writeTo(classBuilder, utils)); - } - - private void finishBuildMethod() { - classBuilder.addMethod(build.build()); - } - - private void finishConstructor() { - classBuilder.addMethod(constructor.build()); - } - - List create() throws IOException { - addConvenienceConstructor(); - handleAnnotationMethods(); - if (handleBaseBuilder()) { - handleBaseBuilderMethods(); - } - createMethods(); - finishBuildMethod(); - finishConstructor(); - utils.write(classBuilder.build()); - return methods; - } -} diff --git a/annotationprocessor/src/main/java/org/acra/creator/ClassCreator.java b/annotationprocessor/src/main/java/org/acra/creator/ClassCreator.java deleted file mode 100644 index ca8daa231..000000000 --- a/annotationprocessor/src/main/java/org/acra/creator/ClassCreator.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2017 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.acra.creator; - -import android.support.annotation.NonNull; - -import com.google.auto.service.AutoService; -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.FieldSpec; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterSpec; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; - -import org.acra.ModelUtils; -import org.acra.annotation.Configuration; -import org.acra.config.ConfigurationBuilderFactory; -import org.acra.definition.Method; -import org.acra.definition.Type; - -import java.io.IOException; -import java.io.Serializable; -import java.util.List; -import java.util.stream.Collectors; - -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; - -import static org.acra.ModelUtils.PACKAGE; -import static org.acra.ModelUtils.PARAM_0; - -/** - * @author F43nd1r - * @since 04.06.2017 - */ - -public class ClassCreator { - private final Type baseAnnotation; - private final Configuration configuration; - private final ModelUtils utils; - private final String factoryName; - private final ClassName config; - private final ClassName builder; - - public ClassCreator(TypeElement baseAnnotation, Configuration configuration, ModelUtils utils) { - this.baseAnnotation = new Type(baseAnnotation); - this.configuration = configuration; - this.utils = utils; - final String configName = baseAnnotation.getSimpleName().toString().replace("Acra", "") + "Configuration"; - final String builderName = configName + "Builder"; - factoryName = builderName + "Factory"; - config = ClassName.get(PACKAGE, configName); - builder = ClassName.get(PACKAGE, builderName); - - } - - public void createClasses() throws IOException { - createConfigClass(createBuilderClass()); - if (configuration.createBuilderFactory()) { - createFactoryClass(); - } - } - - private List createBuilderClass() throws IOException { - return new BuilderCreator(baseAnnotation, utils.getType(configuration::baseBuilderClass), config, builder, utils).create().stream() - .filter(Method::shouldPropagate).collect(Collectors.toList()); - } - - - private void createConfigClass(List methods) throws IOException { - final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(config.simpleName()) - .addSuperinterface(Serializable.class) - .addSuperinterface(org.acra.config.Configuration.class) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL); - utils.addClassJavadoc(classBuilder, baseAnnotation.getElement()); - final MethodSpec.Builder constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) - .addParameter(ParameterSpec.builder(builder, PARAM_0).addAnnotation(NonNull.class).build()); - for (Method method : methods) { - final String name = method.getName(); - final TypeName type = utils.getImmutableType(method.getReturnType()); - if (!type.equals(method.getReturnType())) { - constructor.addStatement("$1L = new $2T($3L.$1L())", name, type, PARAM_0); - } else { - constructor.addStatement("$1L = $2L.$1L()", name, PARAM_0); - } - classBuilder.addField(FieldSpec.builder(type, name, Modifier.PRIVATE, Modifier.FINAL).addAnnotations(method.getAnnotations()).build()); - classBuilder.addMethod(MethodSpec.methodBuilder(name) - .returns(type) - .addModifiers(Modifier.PUBLIC) - .addAnnotations(method.getAnnotations()) - .addStatement("return $L", name) - .build()); - } - classBuilder.addMethod(constructor.build()); - utils.write(classBuilder.build()); - } - - private void createFactoryClass() throws IOException { - final Type configurationBuilderFactory = utils.getType(ConfigurationBuilderFactory.class); - utils.write(TypeSpec.classBuilder(factoryName) - .addModifiers(Modifier.PUBLIC) - .addSuperinterface(configurationBuilderFactory.getName()) - .addAnnotation(AnnotationSpec.builder(AutoService.class).addMember("value", "$T.class", configurationBuilderFactory.getName()).build()) - .addMethod(MethodSpec.overriding(utils.getOnlyMethod(configurationBuilderFactory.getElement())) - .addAnnotation(NonNull.class) - .addStatement("return new $T($L)", builder, PARAM_0) - .build()) - .build()); - } -} diff --git a/annotationprocessor/src/main/java/org/acra/definition/DelegateMethod.java b/annotationprocessor/src/main/java/org/acra/definition/DelegateMethod.java deleted file mode 100644 index c89ce7fe9..000000000 --- a/annotationprocessor/src/main/java/org/acra/definition/DelegateMethod.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2017 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.acra.definition; - -import android.support.annotation.NonNull; - -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterSpec; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; -import com.squareup.javapoet.TypeVariableName; - -import org.acra.ModelUtils; - -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; - -/** - * @author F43nd1r - * @since 12.06.2017 - */ - -public class DelegateMethod implements Method { - private final String name; - private final String delegate; - private final List annotations; - private final Type returnType; - private final List parameters; - private final Collection modifiers; - private final List typeVariables; - private final String javadoc; - private final ClassName builderName; - - public static DelegateMethod from(ExecutableElement method, String field, ClassName builderName, ModelUtils utils) { - return new DelegateMethod(method.getSimpleName().toString(), field, utils.getType(method.getReturnType()), utils.getAnnotations(method), - utils.getParameters(method), utils.getTypeParameters(method), method.getModifiers(), utils.getJavadoc(method), builderName); - } - - private DelegateMethod(String name, String delegate, Type returnType, List annotations, List parameters, - List typeVariables, Collection modifiers, String javadoc, ClassName builderName) { - this.name = name; - this.delegate = delegate; - this.annotations = annotations; - this.returnType = returnType; - this.parameters = parameters; - this.modifiers = modifiers; - this.typeVariables = typeVariables; - this.javadoc = javadoc; - this.builderName = builderName; - } - - @Override - public boolean shouldPropagate() { - return parameters.size() == 0; - } - - @Override - public void writeTo(TypeSpec.Builder builder, ModelUtils utils) { - final MethodSpec.Builder method = MethodSpec.methodBuilder(name) - .addModifiers(modifiers) - .addParameters(parameters) - .addTypeVariables(typeVariables) - .addAnnotations(annotations); - if (javadoc != null) { - method.addJavadoc(javadoc.replaceAll("(\n|^) ", "$1")); - } - if (returnType.getName().equals(TypeName.VOID)) { - method.addStatement("$L.$L($L)", delegate, name, parameters.stream().map(p -> p.name).collect(Collectors.joining(", "))) - .addStatement("return this") - .returns(builderName) - .addAnnotation(NonNull.class) - .addJavadoc("@return this instance\n"); - } else { - method.addStatement("return $L.$L($L)", delegate, name, parameters.stream().map(p -> p.name).collect(Collectors.joining(", "))) - .returns(returnType.getName()); - } - builder.addMethod(method.build()); - } - - @Override - public String getName() { - return name; - } - - @Override - public List getAnnotations() { - return annotations; - } - - @Override - public TypeName getReturnType() { - return returnType.getName(); - } -} diff --git a/annotationprocessor/src/main/java/org/acra/definition/Field.java b/annotationprocessor/src/main/java/org/acra/definition/Field.java deleted file mode 100644 index b23df4667..000000000 --- a/annotationprocessor/src/main/java/org/acra/definition/Field.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2017 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.acra.definition; - -import android.support.annotation.NonNull; - -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.FieldSpec; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; - -import org.acra.ModelUtils; - -import java.util.List; - -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.type.TypeKind; - -/** - * The minimal Definition needed to create a getter - * - * @author F43nd1r - * @since 18.03.2017 - */ -public class Field { - private final String name; - private final Type type; - private final List annotations; - private final AnnotationValue defaultValue; - private final String javadoc; - - public static Field from(ExecutableElement method, ModelUtils utils) { - return new Field(method.getSimpleName().toString(), utils.getType(method.getReturnType()), utils.getAnnotations(method), method.getDefaultValue(), utils.getJavadoc(method)); - } - - public Field(String name, Type type, List annotations, AnnotationValue defaultValue, String javadoc) { - this.name = name; - this.type = type; - this.annotations = annotations; - this.defaultValue = defaultValue; - this.javadoc = javadoc; - } - - public String getName() { - return name; - } - - public Type getType() { - return type; - } - - List getAnnotations() { - return annotations; - } - - public boolean hasDefault() { - return defaultValue != null; - } - - public void addTo(TypeSpec.Builder builder, ModelUtils utils) { - annotations.removeIf(a -> a.type.equals(TypeName.get(NonNull.class))); - final FieldSpec.Builder field = FieldSpec.builder(type.getName(), name, Modifier.PRIVATE) - .addAnnotations(annotations); - if(defaultValue != null){ - if (type.getMirror().getKind() == TypeKind.ARRAY) { - field.initializer("new $T$L", utils.erasure(type.getMirror()), defaultValue); - }else { - field.initializer("$L", defaultValue); - } - } - builder.addField(field.build()); - } - - public String getJavadoc() { - return javadoc; - } -} diff --git a/annotationprocessor/src/main/java/org/acra/definition/FieldGetter.java b/annotationprocessor/src/main/java/org/acra/definition/FieldGetter.java deleted file mode 100644 index 8e486eb26..000000000 --- a/annotationprocessor/src/main/java/org/acra/definition/FieldGetter.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2017 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.acra.definition; - -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; - -import org.acra.ModelUtils; - -import java.util.List; - -/** - * @author F43nd1r - * @since 12.06.2017 - */ - -public class FieldGetter extends FieldMethod { - public FieldGetter(Field field) { - super(field); - } - - @Override - public boolean shouldPropagate() { - return true; - } - - @Override - public void writeTo(TypeSpec.Builder builder, ModelUtils utils) { - builder.addMethod(MethodSpec.methodBuilder(getField().getName()) - .addAnnotations(getField().getAnnotations()) - .returns(getField().getType().getName()) - .addStatement("return $L", getField().getName()) - .build()); - } - - @Override - public String getName() { - return getField().getName(); - } - - @Override - public List getAnnotations() { - return getField().getAnnotations(); - } - - @Override - public TypeName getReturnType() { - return getField().getType().getName(); - } - -} diff --git a/annotationprocessor/src/main/java/org/acra/definition/FieldSetter.java b/annotationprocessor/src/main/java/org/acra/definition/FieldSetter.java deleted file mode 100644 index aa9a61f11..000000000 --- a/annotationprocessor/src/main/java/org/acra/definition/FieldSetter.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2017 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.acra.definition; - -import android.support.annotation.NonNull; - -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterSpec; -import com.squareup.javapoet.TypeSpec; - -import org.acra.ModelUtils; - -import java.util.List; - -import javax.lang.model.element.Modifier; -import javax.lang.model.type.TypeKind; - -/** - * @author F43nd1r - * @since 12.06.2017 - */ - -public class FieldSetter extends FieldMethod { - private static final String PREFIX_SETTER = "set"; - private final ClassName builderName; - - public FieldSetter(Field field, ClassName builderName) { - super(field); - this.builderName = builderName; - } - - @Override - public boolean shouldPropagate() { - return false; - } - - @Override - public void writeTo(TypeSpec.Builder builder, ModelUtils utils) { - final MethodSpec.Builder method = MethodSpec.methodBuilder(getName()) - .returns(builderName) - .addParameter(ParameterSpec.builder(getField().getType().getName(), getField().getName()).addAnnotations(getAnnotations()).build()) - .varargs(getField().getType().getMirror().getKind() == TypeKind.ARRAY) - .addModifiers(Modifier.PUBLIC) - .addAnnotation(NonNull.class) - .addStatement("this.$1L = $1L", getField().getName()) - .addStatement("return this"); - if (getField().getJavadoc() != null) { - method.addJavadoc(getField().getJavadoc().replaceAll("(\n|^) ", "$1").replaceAll("@return ((.|\n)*)$", "@param " + getField().getName() + " $1@return this instance\n")); - } - builder.addMethod(method.build()); - } - - @Override - public String getName() { - return PREFIX_SETTER + Character.toUpperCase(getField().getName().charAt(0)) + getField().getName().substring(1); - } - - @Override - public List getAnnotations() { - return getField().getAnnotations(); - } - - @Override - public ClassName getReturnType() { - return builderName; - } -} diff --git a/annotationprocessor/src/main/java/org/acra/definition/Transformer.java b/annotationprocessor/src/main/java/org/acra/definition/Transformer.java deleted file mode 100644 index 34963b0a6..000000000 --- a/annotationprocessor/src/main/java/org/acra/definition/Transformer.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2017 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.acra.definition; - -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; - -import org.acra.ModelUtils; - -import java.util.List; - -import javax.lang.model.element.ExecutableElement; - -/** - * @author F43nd1r - * @since 04.06.2017 - */ - -public class Transformer implements Method { - private final String name; - private final String delegate; - private final Type returnType; - private final FieldGetter transformable; - - public static Transformer from(ExecutableElement method, String field, FieldGetter transformable, ModelUtils utils) { - return new Transformer(method.getSimpleName().toString(), field, utils.getType(method.getReturnType()), transformable); - } - - private Transformer(String name, String delegate, Type returnType, FieldGetter transformable) { - this.name = name; - this.delegate = delegate; - this.returnType = returnType; - this.transformable = transformable; - } - - - @Override - public boolean shouldPropagate() { - return transformable.shouldPropagate(); - } - - @Override - public void writeTo(TypeSpec.Builder builder, ModelUtils utils) { - builder.addMethod(MethodSpec.methodBuilder(transformable.getName()) - .addAnnotations(transformable.getAnnotations()) - .returns(returnType.getName()) - .addStatement("return $L.$L($L)", delegate, name, transformable.getField().getName()) - .build()); - } - - @Override - public String getName() { - return transformable.getName(); - } - - @Override - public List getAnnotations() { - return transformable.getAnnotations(); - } - - @Override - public TypeName getReturnType() { - return returnType.getName(); - } -} diff --git a/annotationprocessor/src/main/java/org/acra/definition/Type.java b/annotationprocessor/src/main/java/org/acra/definition/Type.java deleted file mode 100644 index 189c751dc..000000000 --- a/annotationprocessor/src/main/java/org/acra/definition/Type.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2017 - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.acra.definition; - -import com.squareup.javapoet.TypeName; - -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; - -/** - * @author F43nd1r - * @since 04.06.2017 - */ - -public class Type { - private final TypeElement element; - private final TypeMirror mirror; - private final TypeName name; - - public Type(TypeElement element) { - this.element = element; - mirror = element.asType(); - name = TypeName.get(mirror); - } - - public Type(TypeElement element, TypeMirror mirror, TypeName name) { - this.element = element; - this.mirror = mirror; - this.name = name; - } - - public TypeElement getElement() { - return element; - } - - public TypeMirror getMirror() { - return mirror; - } - - public TypeName getName() { - return name; - } -} diff --git a/annotationprocessor/src/main/java/org/acra/AcraAnnotationProcessor.java b/annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.java similarity index 73% rename from annotationprocessor/src/main/java/org/acra/AcraAnnotationProcessor.java rename to annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.java index 0fd3ae6ba..0f3ec2a07 100644 --- a/annotationprocessor/src/main/java/org/acra/AcraAnnotationProcessor.java +++ b/annotationprocessor/src/main/java/org/acra/processor/AcraAnnotationProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 + * Copyright (c) 2018 the ACRA team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,16 @@ * limitations under the License. */ -package org.acra; +package org.acra.processor; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.google.auto.common.MoreElements; import com.google.auto.service.AutoService; - -import org.acra.annotation.AnyNonDefault; +import com.squareup.javapoet.ClassName; import org.acra.annotation.Configuration; -import org.acra.annotation.Instantiatable; -import org.acra.annotation.NonEmpty; -import org.acra.annotation.PreBuild; -import org.acra.annotation.Transform; -import org.acra.creator.ClassCreator; - -import java.util.ArrayList; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import org.acra.processor.creator.ClassCreator; +import org.acra.processor.util.Types; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Processor; @@ -40,29 +34,31 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; +import java.util.ArrayList; +import java.util.Set; +import java.util.stream.Collectors; /** * @author F43nd1r * @since 18.03.2017 */ @AutoService(Processor.class) -@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedSourceVersion(SourceVersion.RELEASE_9) public class AcraAnnotationProcessor extends AbstractProcessor { @Override public Set getSupportedAnnotationTypes() { - return Stream.of(AnyNonDefault.class, Configuration.class, Instantiatable.class, NonEmpty.class, PreBuild.class, Transform.class) - .map(Class::getName).collect(Collectors.toSet()); + return Types.MARKER_ANNOTATIONS.stream().map(ClassName::reflectionName).collect(Collectors.toSet()); } @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { + public boolean process(@Nullable Set annotations, @NonNull RoundEnvironment roundEnv) { try { final ArrayList annotatedElements = new ArrayList<>(roundEnv.getElementsAnnotatedWith(Configuration.class)); if (!annotatedElements.isEmpty()) { for (final Element e : annotatedElements) { if (e.getKind() == ElementKind.ANNOTATION_TYPE) { - new ClassCreator((TypeElement) e, e.getAnnotation(Configuration.class), new ModelUtils(processingEnv)).createClasses(); + new ClassCreator(MoreElements.asType(e), e.getAnnotation(Configuration.class), processingEnv).createClasses(); } else { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, String.format("%s is only supported on %s", Configuration.class.getName(), ElementKind.ANNOTATION_TYPE.name()), e); diff --git a/annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.java b/annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.java new file mode 100644 index 000000000..24700bbaa --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/creator/BuildMethodCreator.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.creator; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; + +import org.acra.config.ACRAConfigurationException; +import org.acra.config.ClassValidator; +import org.acra.processor.util.Strings; +import org.acra.processor.util.Types; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; + +/** + * @author F43nd1r + * @since 12.06.2017 + */ + +public class BuildMethodCreator { + private final MethodSpec.Builder methodBuilder; + private final Map anyNonDefault; + private final ClassName config; + private final List statements; + + BuildMethodCreator(@NonNull ExecutableElement override, @NonNull ClassName config) { + this.config = config; + methodBuilder = Types.overriding(override) + .addAnnotation(Types.NON_NULL) + .returns(config) + .beginControlFlow("if ($L)", Strings.FIELD_ENABLED); + anyNonDefault = new LinkedHashMap<>(); + statements = new ArrayList<>(); + } + + public void addNotUnset(@NonNull String name, @NonNull TypeName type) { + methodBuilder.beginControlFlow("if ($L == $L)", name, getDefault(type)) + .addStatement("throw new $T(\"$L has to be set\")", ACRAConfigurationException.class, name) + .endControlFlow(); + } + + public void addNotEmpty(@NonNull String name) { + methodBuilder.beginControlFlow("if ($L.length == 0)", name) + .addStatement("throw new $T(\"$L cannot be empty\")", ACRAConfigurationException.class, name) + .endControlFlow(); + } + + public void addInstantiatable(@NonNull String name) { + methodBuilder.addStatement("$T.check($L)", ClassValidator.class, name); + } + + public void addAnyNonDefault(@NonNull String name, @Nullable AnnotationValue defaultValue) { + anyNonDefault.put(name, defaultValue); + } + + @NonNull + private String getDefault(@NonNull TypeName type) { + if (type.isPrimitive()) { + if (type.equals(TypeName.BOOLEAN)) { + return "false"; + } else if (type.equals(TypeName.BYTE)) { + return "0"; + } else if (type.equals(TypeName.SHORT)) { + return "0"; + } else if (type.equals(TypeName.INT)) { + return "0"; + } else if (type.equals(TypeName.LONG)) { + return "0L"; + } else if (type.equals(TypeName.CHAR)) { + return "\u0000"; + } else if (type.equals(TypeName.FLOAT)) { + return "0.0f"; + } else if (type.equals(TypeName.DOUBLE)) { + return "0.0d"; + } + } + return "null"; + } + + public void addDelegateCall(@NonNull String methodName) { + statements.add(CodeBlock.builder().addStatement("$L.$L()", Strings.FIELD_DELEGATE, methodName).build()); + } + + @NonNull + MethodSpec build() { + if (anyNonDefault.size() > 0) { + methodBuilder.beginControlFlow("if ($L)", anyNonDefault.entrySet().stream().map(field -> field.getKey() + " == " + field.getValue()).collect(Collectors.joining(" && "))) + .addStatement("throw new $T(\"One of $L must not be default\")", ACRAConfigurationException.class, + anyNonDefault.keySet().stream().collect(Collectors.joining(", "))) + .endControlFlow(); + } + methodBuilder.endControlFlow(); + for (CodeBlock s : statements) { + methodBuilder.addCode(s); + } + methodBuilder.addStatement("return new $T(this)", config); + return methodBuilder.build(); + } + + +} diff --git a/annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.java b/annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.java new file mode 100644 index 000000000..158b4d4c7 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/creator/ClassCreator.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.creator; + +import android.support.annotation.NonNull; +import com.google.auto.common.MoreTypes; +import com.google.auto.service.AutoService; +import com.squareup.javapoet.*; +import org.acra.annotation.Configuration; +import org.acra.config.ConfigurationBuilder; +import org.acra.processor.element.*; +import org.acra.processor.util.Strings; +import org.acra.processor.util.Types; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.MirroredTypeException; +import java.io.IOException; +import java.io.Serializable; +import java.util.List; + +import static org.acra.processor.util.Strings.*; + +/** + * @author F43nd1r + * @since 04.06.2017 + */ + +public class ClassCreator { + private final TypeElement baseAnnotation; + private final Configuration configuration; + private final ProcessingEnvironment processingEnv; + private final String factoryName; + private final String configName; + private final String builderName; + private final String builderVisibleName; + + public ClassCreator(@NonNull TypeElement baseAnnotation, Configuration configuration, @NonNull ProcessingEnvironment processingEnv) { + this.baseAnnotation = baseAnnotation; + this.configuration = configuration; + this.processingEnv = processingEnv; + configName = baseAnnotation.getSimpleName().toString().replace("Acra", "") + "Configuration"; + builderVisibleName = configName + "Builder"; + builderName = configuration.isPlugin() ? builderVisibleName + "Impl" : builderVisibleName; + factoryName = builderVisibleName + "Factory"; + + } + + public void createClasses() throws IOException { + TypeElement baseBuilder; + try { + baseBuilder = processingEnv.getElementUtils().getTypeElement(configuration.baseBuilderClass().getName()); + } catch (MirroredTypeException e) { + baseBuilder = MoreTypes.asTypeElement(e.getTypeMirror()); + } + final List elements = new ModelBuilder(baseAnnotation, new ElementFactory(processingEnv.getElementUtils()), baseBuilder, processingEnv.getMessager()).build(); + createBuilderClass(elements); + createConfigClass(elements); + if (configuration.isPlugin()) { + createBuilderInterface(elements); + createFactoryClass(); + } + } + + private void createBuilderInterface(@NonNull List elements) throws IOException { + final TypeSpec.Builder interfaceBuilder = TypeSpec.interfaceBuilder(builderVisibleName) + .addModifiers(Modifier.PUBLIC) + .addSuperinterface(ConfigurationBuilder.class); + final TypeName baseAnnotation = TypeName.get(this.baseAnnotation.asType()); + Strings.addClassJavadoc(interfaceBuilder, baseAnnotation); + ClassName builder = ClassName.get(PACKAGE, builderVisibleName); + elements.stream().filter(BuilderElement.Interface.class::isInstance).map(BuilderElement.Interface.class::cast) + .forEach(element -> element.addToBuilderInterface(interfaceBuilder, builder)); + Strings.writeClass(processingEnv.getFiler(), interfaceBuilder.build()); + } + + private void createBuilderClass(@NonNull List elements) throws IOException { + final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(builderName) + .addModifiers(Modifier.FINAL); + final TypeName baseAnnotation = TypeName.get(this.baseAnnotation.asType()); + Strings.addClassJavadoc(classBuilder, baseAnnotation); + final MethodSpec.Builder constructor = MethodSpec.constructorBuilder() + .addParameter(ParameterSpec.builder(Types.CONTEXT, PARAM_0).addAnnotation(Types.NON_NULL).build()) + .addJavadoc("@param $L object annotated with {@link $T}\n", PARAM_0, baseAnnotation) + .addStatement("final $1T $2L = $3L.getClass().getAnnotation($1T.class)", baseAnnotation, VAR_ANNOTATION, PARAM_0); + if (!configuration.isPlugin()) { + classBuilder.addModifiers(Modifier.PUBLIC) + .addSuperinterface(ConfigurationBuilder.class); + constructor.addModifiers(Modifier.PUBLIC); + } else { + classBuilder.addSuperinterface(ClassName.get(PACKAGE, builderVisibleName)); + } + final CodeBlock.Builder always = CodeBlock.builder(); + final CodeBlock.Builder whenAnnotationPresent = CodeBlock.builder(); + ClassName builder = ClassName.get(PACKAGE, builderName); + elements.stream().filter(BuilderElement.class::isInstance).map(BuilderElement.class::cast).forEach(m -> m.addToBuilder(classBuilder, builder, always, whenAnnotationPresent)); + constructor.addCode(always.build()) + .beginControlFlow("if ($L)", Strings.FIELD_ENABLED) + .addCode(whenAnnotationPresent.build()) + .endControlFlow(); + classBuilder.addMethod(constructor.build()); + final BuildMethodCreator build = new BuildMethodCreator(Types.getOnlyMethod(processingEnv, ConfigurationBuilder.class.getName()), ClassName.get(PACKAGE, configName)); + elements.stream().filter(ValidatedElement.class::isInstance).map(ValidatedElement.class::cast).forEach(element -> element.addToBuildMethod(build)); + classBuilder.addMethod(build.build()); + Strings.writeClass(processingEnv.getFiler(), classBuilder.build()); + } + + + private void createConfigClass(@NonNull List elements) throws IOException { + final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(configName) + .addSuperinterface(Serializable.class) + .addSuperinterface(org.acra.config.Configuration.class) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL); + Strings.addClassJavadoc(classBuilder, ClassName.get(baseAnnotation.asType())); + final MethodSpec.Builder constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) + .addParameter(ParameterSpec.builder(ClassName.get(PACKAGE, builderName), PARAM_0).addAnnotation(Types.NON_NULL).build()); + elements.stream().filter(ConfigElement.class::isInstance).map(ConfigElement.class::cast).forEach(element -> element.addToConfig(classBuilder, constructor)); + classBuilder.addMethod(constructor.build()); + Strings.writeClass(processingEnv.getFiler(), classBuilder.build()); + } + + private void createFactoryClass() throws IOException { + final TypeName configurationBuilderFactory = Types.CONFIGURATION_BUILDER_FACTORY; + Strings.writeClass(processingEnv.getFiler(), TypeSpec.classBuilder(factoryName) + .addModifiers(Modifier.PUBLIC) + .addSuperinterface(configurationBuilderFactory) + .addAnnotation(AnnotationSpec.builder(AutoService.class).addMember("value", "$T.class", configurationBuilderFactory).build()) + .addMethod(Types.overriding(Types.getOnlyMethod(processingEnv, Strings.CONFIGURATION_BUILDER_FACTORY)) + .addAnnotation(Types.NON_NULL) + .addStatement("return new $T($L)", ClassName.get(PACKAGE, builderName), PARAM_0) + .build()) + .build()); + } +} diff --git a/annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.java b/annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.java new file mode 100644 index 000000000..d85a6bb0a --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/creator/ModelBuilder.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.creator; + +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; + +import com.google.auto.common.MoreElements; +import com.google.auto.common.MoreTypes; +import com.squareup.javapoet.TypeName; + +import org.acra.annotation.BuilderMethod; +import org.acra.annotation.Configuration; +import org.acra.annotation.ConfigurationValue; +import org.acra.annotation.PreBuild; +import org.acra.annotation.Transform; +import org.acra.processor.element.BuilderElement; +import org.acra.processor.element.Element; +import org.acra.processor.element.ElementFactory; +import org.acra.processor.util.Types; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import javax.annotation.processing.Messager; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementFilter; +import javax.tools.Diagnostic; + +/** + * @author F43nd1r + * @since 10.01.2018 + */ + +class ModelBuilder { + private final TypeElement baseAnnotation; + private final ElementFactory modelFactory; + private final Messager messager; + private final List elements; + private final TypeElement baseBuilder; + + ModelBuilder(@NonNull TypeElement baseAnnotation, @NonNull ElementFactory modelFactory, @NonNull TypeElement baseBuilder, @NonNull Messager messager) { + this.baseAnnotation = baseAnnotation; + this.modelFactory = modelFactory; + this.messager = messager; + this.elements = new ArrayList<>(); + this.baseBuilder = baseBuilder; + } + + private void handleParameter() { + elements.add(new BuilderElement.Context()); + elements.add(new BuilderElement.Enabled()); + } + + private void handleAnnotationMethods() { + for (ExecutableElement method : ElementFilter.methodsIn(baseAnnotation.getEnclosedElements())) { + elements.add(MoreElements.isAnnotationPresent(method, StringRes.class) ? modelFactory.fromStringResourceAnnotationMethod(method) : modelFactory.fromAnnotationMethod(method)); + } + } + + private void handleBaseBuilder() { + if (!MoreTypes.isTypeOf(Object.class, baseBuilder.asType())) { + final List constructors = ElementFilter.constructorsIn(baseBuilder.getEnclosedElements()); + Optional constructor = constructors.stream().filter(c -> c.getParameters().size() == 0).findAny(); + if (constructor.isPresent()) { + elements.add(modelFactory.fromDelegateConstructor(constructor.get(), false)); + } else { + constructor = constructors.stream().filter(c -> c.getParameters().size() == 1 && Types.CONTEXT.equals(TypeName.get(c.getParameters().get(0).asType()))).findAny(); + if (constructor.isPresent()) { + elements.add(modelFactory.fromDelegateConstructor(constructor.get(), true)); + } else { + final AnnotationMirror mirror = baseAnnotation.getAnnotationMirrors().stream() + .filter(m -> MoreTypes.isTypeOf(Configuration.class, m.getAnnotationType())) + .findAny().orElseThrow(IllegalArgumentException::new); + messager.printMessage(Diagnostic.Kind.ERROR, "Classes used as base builder must have a constructor which takes no arguments, " + + "or exactly one argument of type Class", baseAnnotation, mirror, mirror.getElementValues().entrySet().stream() + .filter(entry -> entry.getKey().getSimpleName().toString().equals("builderSuperClass")).findAny().map(Map.Entry::getValue).orElse(null)); + throw new IllegalArgumentException(); + } + } + handleBaseBuilderMethods(); + } + } + + private void handleBaseBuilderMethods() { + for (ExecutableElement method : ElementFilter.methodsIn(baseBuilder.getEnclosedElements())) { + if (method.getAnnotation(PreBuild.class) != null) { + elements.add(modelFactory.fromPreBuildDelegateMethod(method)); + } else if (method.getAnnotation(Transform.class) != null) { + final String transform = method.getAnnotation(Transform.class).methodName(); + elements.stream().filter(field -> field.getName().equals(transform)).findAny() + .ifPresent(element -> elements.set(elements.indexOf(element), modelFactory.fromTransformDelegateMethod(method, element))); + } else if (method.getAnnotation(ConfigurationValue.class) != null) { + elements.add(modelFactory.fromConfigDelegateMethod(method)); + } else if (method.getAnnotation(BuilderMethod.class) != null) { + elements.add(modelFactory.fromBuilderDelegateMethod(method)); + } + } + } + + @NonNull + List build() { + handleParameter(); + handleAnnotationMethods(); + handleBaseBuilder(); + return elements; + } +} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.java b/annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.java new file mode 100644 index 000000000..e249436b1 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/AbstractElement.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.element; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.TypeName; + +import java.util.Collection; + +/** + * @author F43nd1r + * @since 12.01.2018 + */ + +class AbstractElement implements Element { + private final String name; + private final TypeName type; + private final Collection annotations; + + AbstractElement(@NonNull String name, @Nullable TypeName type, @NonNull Collection annotations) { + this.type = type; + this.name = name; + this.annotations = annotations; + } + + @Override + public String getName() { + return name; + } + + @Override + public TypeName getType() { + return type; + } + + @Override + public Collection getAnnotations() { + return annotations; + } +} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.java b/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.java new file mode 100644 index 000000000..59f3d34be --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/AnnotationField.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.element; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.squareup.javapoet.*; +import org.acra.processor.creator.BuildMethodCreator; +import org.acra.processor.util.InitializerVisitor; +import org.acra.processor.util.Strings; +import org.acra.processor.util.Types; +import org.apache.commons.text.WordUtils; + +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * @author F43nd1r + * @since 12.01.2018 + */ + +abstract class AnnotationField extends AbstractElement implements TransformedField.Transformable { + private final Collection markers; + private final String javadoc; + + AnnotationField(@NonNull String name, @NonNull TypeName type, @NonNull Collection annotations, @Nullable String javadoc, @NonNull Collection markers) { + super(name, type, annotations); + this.javadoc = javadoc; + this.markers = markers; + } + + boolean hasMarker(@NonNull ClassName marker) { + return markers.contains(marker); + } + + @Override + public final void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, @NonNull CodeBlock.Builder constructorWhenAnnotationPresent) { + addWithoutGetter(builder, builderName, constructorAlways, constructorWhenAnnotationPresent); + addGetter(builder); + } + + @Override + public final void addWithoutGetter(@NonNull TypeSpec.Builder builder, ClassName builderName, CodeBlock.Builder constructorAlways, CodeBlock.Builder constructorWhenAnnotationPresent) { + TransformedField.Transformable.super.addToBuilder(builder, builderName, constructorAlways, constructorWhenAnnotationPresent); + addSetter(builder, builderName); + addInitializer(constructorWhenAnnotationPresent); + } + + protected abstract void addInitializer(CodeBlock.Builder constructorWhenAnnotationPresent); + + @Override + public void configureSetter(@NonNull MethodSpec.Builder builder) { + if (javadoc != null) { + builder.addJavadoc(javadoc.replaceAll("(\n|^) ", "$1").replaceAll("@return ((.|\n)*)$", "@param " + getName() + " $1@return this instance\n")); + } + } + + static class Normal extends AnnotationField { + private final AnnotationValue defaultValue; + + Normal(String name, TypeName type, Collection annotations, Collection markers, AnnotationValue defaultValue, String javadoc) { + super(name, type, annotations, javadoc, markers); + this.defaultValue = defaultValue; + } + + @Override + public void addInitializer(@NonNull CodeBlock.Builder constructorWhenAnnotationPresent) { + constructorWhenAnnotationPresent.addStatement("$1L = $2L.$1L()", getName(), Strings.VAR_ANNOTATION); + } + + @Override + public void configureField(@NonNull FieldSpec.Builder builder) { + if (defaultValue != null) { + final List parameters = new ArrayList<>(); + final String statement = defaultValue.accept(new InitializerVisitor(getType()), parameters); + builder.initializer(statement, parameters.toArray(new Object[parameters.size()])); + } + } + + @Override + public void addToBuildMethod(@NonNull BuildMethodCreator method) { + if (defaultValue == null) { + method.addNotUnset(getName(), getType()); + } + if (hasMarker(Types.NON_EMPTY)) { + method.addNotEmpty(getName()); + } + if (hasMarker(Types.INSTANTIATABLE)) { + method.addInstantiatable(getName()); + } + if (hasMarker(Types.ANY_NON_DEFAULT)) { + method.addAnyNonDefault(getName(), defaultValue); + } + } + + } + + static class StringResource extends AnnotationField { + @NonNull + private final String originalName; + private final boolean allowNull; + + StringResource(String name, Collection annotations, Collection markers, + boolean allowNull, String javadoc) { + super((name.startsWith(Strings.PREFIX_RES)) ? WordUtils.uncapitalize(name.substring(Strings.PREFIX_RES.length())) : name, Types.STRING, annotations, javadoc, markers); + this.originalName = name; + this.allowNull = allowNull; + getAnnotations().remove(Types.STRING_RES); + if (allowNull) { + getAnnotations().add(Types.NULLABLE); + } + } + + @Override + public void addInitializer(@NonNull CodeBlock.Builder constructorWhenAnnotationPresent) { + constructorWhenAnnotationPresent.beginControlFlow("if ($L.$L() != 0)", Strings.VAR_ANNOTATION, originalName) + .addStatement("$L = $L.getString($L.$L())", getName(), Strings.FIELD_CONTEXT, Strings.VAR_ANNOTATION, originalName) + .endControlFlow(); + } + + @Override + public void addSetter(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName) { + super.addSetter(builder, builderName); + final MethodSpec.Builder setter = baseResSetter(builderName) + .addStatement("this.$L = $L.getString($L)", getName(), Strings.FIELD_CONTEXT, Strings.PREFIX_RES + WordUtils.capitalize(getName())) + .addStatement("return this"); + configureSetter(setter); + builder.addMethod(setter.build()); + } + + private MethodSpec.Builder baseResSetter(ClassName builderName){ + final String parameterName = Strings.PREFIX_RES + WordUtils.capitalize(getName()); + final List annotations = new ArrayList<>(getAnnotations()); + annotations.removeIf(Types.NULLABLE::equals); + annotations.add(Types.STRING_RES); + return MethodSpec.methodBuilder(Strings.PREFIX_SETTER + WordUtils.capitalize(parameterName)) + .addParameter(ParameterSpec.builder(TypeName.INT, parameterName).addAnnotations(annotations).build()) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Types.NON_NULL) + .returns(builderName); + } + + @Override + public void addToBuilderInterface(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName) { + super.addToBuilderInterface(builder, builderName); + final MethodSpec.Builder setter = baseResSetter(builderName).addModifiers(Modifier.ABSTRACT); + configureSetter(setter); + builder.addMethod(setter.build()); + } + + @Override + public void addToBuildMethod(@NonNull BuildMethodCreator method) { + if (!allowNull) { + method.addNotUnset(getName(), getType()); + } + if (hasMarker(Types.ANY_NON_DEFAULT)) { + method.addAnyNonDefault(getName(), null); + } + } + } + +} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/BuilderElement.java b/annotationprocessor/src/main/java/org/acra/processor/element/BuilderElement.java new file mode 100644 index 000000000..f728d86fb --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/BuilderElement.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.element; + +import android.support.annotation.NonNull; +import com.squareup.javapoet.*; +import org.acra.processor.util.Strings; +import org.acra.processor.util.Types; +import org.apache.commons.text.WordUtils; + +import javax.lang.model.element.Modifier; +import java.util.Collections; + +/** + * @author F43nd1r + * @since 11.01.2018 + */ + +public interface BuilderElement extends Element { + + default void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, + @NonNull CodeBlock.Builder constructorWhenAnnotationPresent) { + final FieldSpec.Builder field = FieldSpec.builder(getType(), getName(), Modifier.PRIVATE).addAnnotations(getAnnotations()); + configureField(field); + builder.addField(field.build()); + } + + default void configureField(@NonNull FieldSpec.Builder builder) { + } + + default void addGetter(@NonNull TypeSpec.Builder builder) { + final MethodSpec.Builder method = MethodSpec.methodBuilder(getName()) + .addAnnotations(getAnnotations()) + .returns(getType()); + configureGetter(method); + builder.addMethod(method.build()); + } + + default void configureGetter(@NonNull MethodSpec.Builder builder) { + builder.addStatement("return $L", getName()); + } + + default void addSetter(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName) { + final MethodSpec.Builder method = baseSetter(builderName) + .addStatement("this.$1L = $1L", getName()) + .addStatement("return this"); + configureSetter(method); + builder.addMethod(method.build()); + } + + default MethodSpec.Builder baseSetter(ClassName builderName) { + return MethodSpec.methodBuilder(Strings.PREFIX_SETTER + WordUtils.capitalize(getName())) + .addParameter(ParameterSpec.builder(getType(), getName()).addAnnotations(getAnnotations()).build()) + .varargs(getType() instanceof ArrayTypeName) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Types.NON_NULL) + .returns(builderName); + } + + default void configureSetter(@NonNull MethodSpec.Builder builder) { + } + + interface Final extends BuilderElement { + default void configureField(@NonNull FieldSpec.Builder builder) { + builder.addModifiers(Modifier.FINAL); + } + } + + interface Interface extends BuilderElement { + + default void addToBuilderInterface(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName) { + MethodSpec.Builder setter = MethodSpec.methodBuilder(Strings.PREFIX_SETTER + WordUtils.capitalize(getName())) + .addParameter(ParameterSpec.builder(getType(), getName()).addAnnotations(getAnnotations()).build()) + .varargs(getType() instanceof ArrayTypeName) + .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) + .addAnnotation(Types.NON_NULL) + .returns(builderName); + configureSetter(setter); + builder.addMethod(setter.build()); + } + } + + class Context extends AbstractElement implements Final { + public Context() { + super(Strings.FIELD_CONTEXT, Types.CONTEXT, Collections.singleton(Types.NON_NULL)); + } + + @Override + public void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, + @NonNull CodeBlock.Builder constructorWhenAnnotationPresent) { + Final.super.addToBuilder(builder, builderName, constructorAlways, constructorWhenAnnotationPresent); + constructorAlways.addStatement("$L = $L", getName(), Strings.PARAM_0); + } + } + + class Delegate extends AbstractElement implements Final { + private final boolean hasContextParameter; + + Delegate(@NonNull TypeName type, boolean hasContextParameter) { + super(Strings.FIELD_DELEGATE, type, Collections.singleton(Types.NON_NULL)); + this.hasContextParameter = hasContextParameter; + } + + @Override + public void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, + @NonNull CodeBlock.Builder constructorWhenAnnotationPresent) { + Final.super.addToBuilder(builder, builderName, constructorAlways, constructorWhenAnnotationPresent); + if (hasContextParameter) { + constructorAlways.addStatement("$L = new $T($L)", getName(), getType(), Strings.PARAM_0); + } else { + constructorAlways.addStatement("$L = new $T()", getName(), getType()); + } + } + } + + class Enabled extends AbstractElement implements Interface, ConfigElement { + public Enabled() { + super(Strings.FIELD_ENABLED, TypeName.BOOLEAN, Collections.emptyList()); + } + + @Override + public void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, + @NonNull CodeBlock.Builder constructorWhenAnnotationPresent) { + Interface.super.addToBuilder(builder, builderName, constructorAlways, constructorWhenAnnotationPresent); + addSetter(builder, builderName); + addGetter(builder); + constructorAlways.addStatement("$L = $L != null", getName(), Strings.VAR_ANNOTATION); + } + } +} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/ConfigElement.java b/annotationprocessor/src/main/java/org/acra/processor/element/ConfigElement.java new file mode 100644 index 000000000..b3a267215 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/ConfigElement.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.element; + +import android.support.annotation.NonNull; + +import com.squareup.javapoet.ArrayTypeName; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import org.acra.processor.util.Types; + +import javax.lang.model.element.Modifier; + +import static org.acra.processor.util.Strings.PARAM_0; + +/** + * @author F43nd1r + * @since 10.01.2018 + */ + +public interface ConfigElement extends Element { + + default void addToConfig(@NonNull TypeSpec.Builder builder, @NonNull MethodSpec.Builder constructor) { + //add field + final TypeName type = getImmutableType(); + builder.addField(FieldSpec.builder(type, getName(), Modifier.PRIVATE, Modifier.FINAL).addAnnotations(getAnnotations()).build()); + if (!type.equals(getType())) { + constructor.addStatement("$1L = new $2T($3L.$1L())", getName(), type, PARAM_0); + } else { + constructor.addStatement("$1L = $2L.$1L()", getName(), PARAM_0); + } + //add getter + builder.addMethod(MethodSpec.methodBuilder(getName()) + .addAnnotations(getAnnotations()) + .returns(type) + .addStatement("return $L", getName()) + .addModifiers(Modifier.PUBLIC) + .build()); + } + + @NonNull + private TypeName getImmutableType() { + TypeName type = getType(); + if (type instanceof ParameterizedTypeName) { + final TypeName genericType = ((ParameterizedTypeName) type).rawType; + if (Types.MAP.equals(genericType)) { + type = getWithParams(Types.IMMUTABLE_MAP, (ParameterizedTypeName) type); + } else if (Types.SET.equals(genericType)) { + return getWithParams(Types.IMMUTABLE_SET, (ParameterizedTypeName) type); + } else if (Types.LIST.equals(genericType)) { + type = getWithParams(Types.IMMUTABLE_LIST, (ParameterizedTypeName) type); + } + } else if (type instanceof ArrayTypeName) { + type = ParameterizedTypeName.get(Types.IMMUTABLE_LIST, ((ArrayTypeName) type).componentType); + } + return type; + } + + private static TypeName getWithParams(@NonNull ClassName baseType, @NonNull ParameterizedTypeName parameterType) { + return ParameterizedTypeName.get(baseType, parameterType.typeArguments.toArray(new TypeName[parameterType.typeArguments.size()])); + } +} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/DelegateMethod.java b/annotationprocessor/src/main/java/org/acra/processor/element/DelegateMethod.java new file mode 100644 index 000000000..fe3c5e633 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/DelegateMethod.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.element; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.squareup.javapoet.*; +import org.acra.processor.util.Strings; +import org.acra.processor.util.Types; + +import javax.lang.model.element.Modifier; +import java.util.Collection; +import java.util.stream.Collectors; + +/** + * @author F43nd1r + * @since 10.01.2018 + */ + +class DelegateMethod extends AbstractElement implements BuilderElement.Interface { + private final Collection parameters; + private final Collection typeVariables; + private final Collection modifiers; + private final String javadoc; + + DelegateMethod(@NonNull String name, @NonNull TypeName type, @NonNull Collection annotations, @NonNull Collection parameters, + @NonNull Collection typeVariables, @NonNull Collection modifiers, @Nullable String javadoc) { + super(name, type, annotations); + this.parameters = parameters; + this.typeVariables = typeVariables; + this.modifiers = modifiers; + this.javadoc = javadoc; + } + + @Override + public void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, @NonNull CodeBlock.Builder constructorWhenAnnotationPresent) { + final MethodSpec.Builder method = baseMethod(builderName); + if (getType().equals(TypeName.VOID)) { + method.addStatement("$L.$L($L)", Strings.FIELD_DELEGATE, getName(), parameters.stream().map(p -> p.name).collect(Collectors.joining(", "))) + .addStatement("return this"); + } else { + method.addStatement("return $L.$L($L)", Strings.FIELD_DELEGATE, getName(), parameters.stream().map(p -> p.name).collect(Collectors.joining(", "))); + } + builder.addMethod(method.build()); + } + + private MethodSpec.Builder baseMethod(@NonNull ClassName builderName) { + final MethodSpec.Builder method = MethodSpec.methodBuilder(getName()) + .addModifiers(modifiers) + .addParameters(parameters) + .addTypeVariables(typeVariables) + .addAnnotations(getAnnotations()); + if (javadoc != null) { + method.addJavadoc(javadoc.replaceAll("(\n|^) ", "$1")); + } + if (getType().equals(TypeName.VOID)) { + method.returns(builderName) + .addAnnotation(Types.NON_NULL) + .addJavadoc("@return this instance\n"); + } else { + method.returns(getType()); + } + return method; + } + + @Override + public void addToBuilderInterface(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName) { + if (modifiers.contains(Modifier.PUBLIC)) { + builder.addMethod(baseMethod(builderName).addModifiers(Modifier.ABSTRACT).build()); + } + } + + static class Config extends DelegateMethod implements org.acra.processor.element.ConfigElement { + Config(@NonNull String name, @NonNull TypeName type, @NonNull Collection annotations, @NonNull Collection parameters, + @NonNull Collection typeVariables, @NonNull Collection modifiers, @NonNull String javadoc) { + super(name, type, annotations, parameters, typeVariables, modifiers, javadoc); + } + } +} diff --git a/annotationprocessor/src/main/java/org/acra/definition/Method.java b/annotationprocessor/src/main/java/org/acra/processor/element/Element.java similarity index 66% rename from annotationprocessor/src/main/java/org/acra/definition/Method.java rename to annotationprocessor/src/main/java/org/acra/processor/element/Element.java index 71f3355aa..46df8e08e 100644 --- a/annotationprocessor/src/main/java/org/acra/definition/Method.java +++ b/annotationprocessor/src/main/java/org/acra/processor/element/Element.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 + * Copyright (c) 2018 the ACRA team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,29 +14,22 @@ * limitations under the License. */ -package org.acra.definition; +package org.acra.processor.element; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; -import org.acra.ModelUtils; - -import java.util.List; +import java.util.Collection; /** * @author F43nd1r - * @since 12.06.2017 + * @since 12.01.2018 */ -public interface Method { - boolean shouldPropagate(); - - void writeTo(TypeSpec.Builder builder, ModelUtils utils); - +public interface Element { String getName(); - List getAnnotations(); + TypeName getType(); - TypeName getReturnType(); + Collection getAnnotations(); } diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.java b/annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.java new file mode 100644 index 000000000..1713ab99c --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/ElementFactory.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.element; + +import android.support.annotation.NonNull; +import com.squareup.javapoet.*; +import org.apache.commons.lang3.tuple.Pair; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.util.Elements; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author F43nd1r + * @since 10.01.2018 + */ + +public class ElementFactory { + + private final Elements elements; + + + public ElementFactory(@NonNull Elements elements) { + this.elements = elements; + } + + @NonNull + private static Pair, Set> getAnnotations(@NonNull ExecutableElement method) { + final List specs = method.getAnnotationMirrors().stream().map(AnnotationSpec::get).collect(Collectors.toList()); + final Set markerAnnotations = new HashSet<>(); + for (final Iterator iterator = specs.iterator(); iterator.hasNext(); ) { + final AnnotationSpec spec = iterator.next(); + for (ClassName a : org.acra.processor.util.Types.MARKER_ANNOTATIONS) { + if (a.equals(spec.type)) { + iterator.remove(); + markerAnnotations.add(a); + } + } + } + return Pair.of(specs, markerAnnotations); + } + + @NonNull + public Element fromAnnotationMethod(@NonNull ExecutableElement method) { + final Pair, Set> annotations = getAnnotations(method); + return new AnnotationField.Normal(method.getSimpleName().toString(), TypeName.get(method.getReturnType()), annotations.getLeft(), annotations.getRight(), method.getDefaultValue(), + elements.getDocComment(method)); + } + + @NonNull + public Element fromStringResourceAnnotationMethod(@NonNull ExecutableElement method) { + final Pair, Set> annotations = getAnnotations(method); + return new AnnotationField.StringResource(method.getSimpleName().toString(), annotations.getLeft(), annotations.getRight(), + method.getDefaultValue() != null, elements.getDocComment(method)); + } + + @NonNull + public Element fromBuilderDelegateMethod(@NonNull ExecutableElement method) { + return new DelegateMethod(method.getSimpleName().toString(), TypeName.get(method.getReturnType()), getAnnotations(method).getLeft(), + method.getParameters().stream().map(p -> ParameterSpec.builder(TypeName.get(p.asType()), p.getSimpleName().toString()).build()).collect(Collectors.toList()), + method.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList()), method.getModifiers(), elements.getDocComment(method)); + } + + @NonNull + public Element fromConfigDelegateMethod(@NonNull ExecutableElement method) { + return new DelegateMethod.Config(method.getSimpleName().toString(), TypeName.get(method.getReturnType()), getAnnotations(method).getLeft(), + method.getParameters().stream().map(p -> ParameterSpec.builder(TypeName.get(p.asType()), p.getSimpleName().toString()).build()).collect(Collectors.toList()), + method.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList()), method.getModifiers(), elements.getDocComment(method)); + } + + @NonNull + public Element fromPreBuildDelegateMethod(@NonNull ExecutableElement method) { + return new PreBuildMethod(method.getSimpleName().toString()); + } + + @NonNull + public Element fromTransformDelegateMethod(@NonNull ExecutableElement method, Element transform) { + if (transform instanceof TransformedField.Transformable) { + return new TransformedField(method.getSimpleName().toString(), TypeName.get(method.getReturnType()), (TransformedField.Transformable) transform); + } + return transform; + } + + @NonNull + public Element fromDelegateConstructor(@NonNull ExecutableElement constructor, boolean hasContextParameter) { + return new BuilderElement.Delegate(TypeName.get(constructor.getEnclosingElement().asType()), hasContextParameter); + } +} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/PreBuildMethod.java b/annotationprocessor/src/main/java/org/acra/processor/element/PreBuildMethod.java new file mode 100644 index 000000000..eeab0d058 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/PreBuildMethod.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.element; + +import android.support.annotation.NonNull; + +import org.acra.processor.creator.BuildMethodCreator; + +import java.util.Collections; + +/** + * @author F43nd1r + * @since 11.01.2018 + */ + +class PreBuildMethod extends AbstractElement implements ValidatedElement { + PreBuildMethod(@NonNull String name) { + super(name, null, Collections.emptyList()); + } + + @Override + public void addToBuildMethod(@NonNull BuildMethodCreator method) { + method.addDelegateCall(getName()); + } +} diff --git a/annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.java b/annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.java new file mode 100644 index 000000000..cdc0aefbd --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/element/TransformedField.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.element; + +import android.support.annotation.NonNull; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.CodeBlock; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import org.acra.processor.creator.BuildMethodCreator; +import org.acra.processor.util.Strings; + +import java.util.Collection; + +/** + * @author F43nd1r + * @since 11.01.2018 + */ + +class TransformedField implements ConfigElement, BuilderElement.Interface, ValidatedElement { + private final String name; + private final TypeName type; + private final Transformable transform; + + TransformedField(@NonNull String name, @NonNull TypeName type, @NonNull Transformable transform) { + this.name = name; + this.type = type; + this.transform = transform; + } + + @Override + public void addToBuilder(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName, @NonNull CodeBlock.Builder constructorAlways, + @NonNull CodeBlock.Builder constructorWhenAnnotationPresent) { + transform.addWithoutGetter(builder, builderName, constructorAlways, constructorWhenAnnotationPresent); + addGetter(builder); + } + + @Override + public void configureGetter(@NonNull MethodSpec.Builder builder) { + builder.addStatement("return $L.$L($L)", Strings.FIELD_DELEGATE, name, getName()); + } + + @Override + public void addToBuildMethod(@NonNull BuildMethodCreator method) { + transform.addToBuildMethod(method); + } + + @Override + public void addToBuilderInterface(@NonNull TypeSpec.Builder builder, @NonNull ClassName builderName) { + transform.addToBuilderInterface(builder, builderName); + } + + @Override + public String getName() { + return transform.getName(); + } + + @Override + public TypeName getType() { + return type; + } + + @Override + public Collection getAnnotations() { + return transform.getAnnotations(); + } + + public interface Transformable extends ConfigElement, Interface, ValidatedElement { + void addWithoutGetter(TypeSpec.Builder builder, ClassName builderName, CodeBlock.Builder constructorAlways, CodeBlock.Builder constructorWhenAnnotationPresent); + } +} diff --git a/annotationprocessor/src/main/java/org/acra/definition/FieldMethod.java b/annotationprocessor/src/main/java/org/acra/processor/element/ValidatedElement.java similarity index 66% rename from annotationprocessor/src/main/java/org/acra/definition/FieldMethod.java rename to annotationprocessor/src/main/java/org/acra/processor/element/ValidatedElement.java index 202d8bfb0..757a1b116 100644 --- a/annotationprocessor/src/main/java/org/acra/definition/FieldMethod.java +++ b/annotationprocessor/src/main/java/org/acra/processor/element/ValidatedElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 + * Copyright (c) 2018 the ACRA team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,21 +14,16 @@ * limitations under the License. */ -package org.acra.definition; +package org.acra.processor.element; + +import android.support.annotation.NonNull; +import org.acra.processor.creator.BuildMethodCreator; /** * @author F43nd1r - * @since 12.06.2017 + * @since 11.01.2018 */ -abstract class FieldMethod implements Method { - private final Field field; - - FieldMethod(Field field) { - this.field = field; - } - - Field getField() { - return field; - } +public interface ValidatedElement extends Element { + void addToBuildMethod(@NonNull BuildMethodCreator method); } diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/InitializerVisitor.java b/annotationprocessor/src/main/java/org/acra/processor/util/InitializerVisitor.java new file mode 100644 index 000000000..af263d79d --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/util/InitializerVisitor.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.util; + +import android.support.annotation.NonNull; +import com.squareup.javapoet.ArrayTypeName; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; + +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleAnnotationValueVisitor9; +import java.util.List; +import java.util.stream.Collectors; + +/** + * @author F43nd1r + * @since 12.01.2018 + */ +public class InitializerVisitor extends SimpleAnnotationValueVisitor9> { + private final TypeName type; + + public InitializerVisitor(TypeName type) { + this.type = type; + } + + @NonNull + @Override + protected String defaultAction(Object o, @NonNull List objects) { + objects.add(o); + return "$L"; + } + + @NonNull + @Override + public String visitString(String s, @NonNull List objects) { + objects.add(s); + return "$S"; + } + + @NonNull + @Override + public String visitEnumConstant(@NonNull VariableElement c, @NonNull List objects) { + objects.add(c.asType()); + objects.add(c.getSimpleName()); + return "$T.$L"; + } + + @NonNull + @Override + public String visitType(TypeMirror t, @NonNull List objects) { + objects.add(t); + return "$T.class"; + } + + @NonNull + @Override + public String visitArray(@NonNull List values, @NonNull List objects) { + ArrayTypeName arrayTypeName = (ArrayTypeName) type; + if (arrayTypeName.componentType instanceof ParameterizedTypeName) { + arrayTypeName = ArrayTypeName.of(((ParameterizedTypeName) arrayTypeName.componentType).rawType); + } + objects.add(arrayTypeName); + return "new $T" + values.stream().map(value -> value.accept(this, objects)).collect(Collectors.joining(", ", "{", "}")); + } +} diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/Strings.java b/annotationprocessor/src/main/java/org/acra/processor/util/Strings.java new file mode 100644 index 000000000..e872cfcee --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/util/Strings.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.util; + +import android.support.annotation.NonNull; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; + +import javax.annotation.processing.Filer; +import java.io.IOException; +import java.text.DateFormat; +import java.util.Calendar; + +/** + * @author F43nd1r + * @since 08.01.2018 + */ + +public final class Strings { + public static final String PREFIX_RES = "res"; + public static final String PREFIX_SETTER = "set"; + public static final String PARAM_0 = "arg0"; + public static final String VAR_ANNOTATION = "annotation"; + public static final String FIELD_DELEGATE = "delegate"; + public static final String FIELD_CONTEXT = "context"; + public static final String FIELD_ENABLED = "enabled"; + public static final String PACKAGE = "org.acra.config"; + public static final String CONTEXT = "android.content.Context"; + public static final String CONFIGURATION_BUILDER_FACTORY = "org.acra.config.ConfigurationBuilderFactory"; + private static final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance(); + + private Strings() { + } + + public static void addClassJavadoc(@NonNull TypeSpec.Builder builder, @NonNull TypeName base) { + builder.addJavadoc("Class generated based on {@link $T} ($L)\n", base, DATE_FORMAT.format(Calendar.getInstance().getTime())); + } + + /** + * Writes the given class to a respective file in the configuration package + * + * @param filer filer to write to + * @param typeSpec the class + * @throws IOException if writing fails + */ + public static void writeClass(@NonNull Filer filer, @NonNull TypeSpec typeSpec) throws IOException { + JavaFile.builder(PACKAGE, typeSpec) + .skipJavaLangImports(true) + .indent(" ") + .addFileComment("Copyright (c) " + Calendar.getInstance().get(Calendar.YEAR) + "\n\n" + + "Licensed under the Apache License, Version 2.0 (the \"License\");\n" + + "you may not use this file except in compliance with the License.\n\n" + + "http://www.apache.org/licenses/LICENSE-2.0\n\n" + + "Unless required by applicable law or agreed to in writing, software\n" + + "distributed under the License is distributed on an \"AS IS\" BASIS,\n" + + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + + "See the License for the specific language governing permissions and\n" + + "limitations under the License.") + .build() + .writeTo(filer); + } +} diff --git a/annotationprocessor/src/main/java/org/acra/processor/util/Types.java b/annotationprocessor/src/main/java/org/acra/processor/util/Types.java new file mode 100644 index 000000000..f4b8ae441 --- /dev/null +++ b/annotationprocessor/src/main/java/org/acra/processor/util/Types.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.processor.util; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterSpec; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeVariableName; + +import org.acra.annotation.AnyNonDefault; +import org.acra.annotation.BuilderMethod; +import org.acra.annotation.Configuration; +import org.acra.annotation.ConfigurationValue; +import org.acra.annotation.Instantiatable; +import org.acra.annotation.NonEmpty; +import org.acra.annotation.PreBuild; +import org.acra.annotation.Transform; +import org.acra.collections.ImmutableList; +import org.acra.collections.ImmutableMap; +import org.acra.collections.ImmutableSet; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementFilter; +import javax.tools.Diagnostic; + +/** + * @author F43nd1r + * @since 11.01.2018 + */ + +public final class Types { + public static final ClassName IMMUTABLE_MAP = ClassName.get(ImmutableMap.class); + public static final ClassName IMMUTABLE_SET = ClassName.get(ImmutableSet.class); + public static final ClassName IMMUTABLE_LIST = ClassName.get(ImmutableList.class); + public static final ClassName MAP = ClassName.get(Map.class); + public static final ClassName SET = ClassName.get(Set.class); + public static final ClassName LIST = ClassName.get(List.class); + public static final ClassName STRING = ClassName.get(String.class); + public static final AnnotationSpec NULLABLE = AnnotationSpec.builder(Nullable.class).build(); + public static final AnnotationSpec NON_NULL = AnnotationSpec.builder(NonNull.class).build(); + public static final AnnotationSpec STRING_RES = AnnotationSpec.builder(StringRes.class).build(); + public static final ClassName ANY_NON_DEFAULT = ClassName.get(AnyNonDefault.class); + public static final ClassName BUILDER_METHOD = ClassName.get(BuilderMethod.class); + public static final ClassName CONFIGURATION = ClassName.get(Configuration.class); + public static final ClassName CONFIGURATION_VALUE = ClassName.get(ConfigurationValue.class); + public static final ClassName INSTANTIATABLE = ClassName.get(Instantiatable.class); + public static final ClassName NON_EMPTY = ClassName.get(NonEmpty.class); + public static final ClassName PRE_BUILD = ClassName.get(PreBuild.class); + public static final ClassName TRANSFORM = ClassName.get(Transform.class); + public static final ClassName CONTEXT = ClassName.bestGuess(Strings.CONTEXT); + public static final ClassName CONFIGURATION_BUILDER_FACTORY = ClassName.bestGuess(Strings.CONFIGURATION_BUILDER_FACTORY); + public static final List MARKER_ANNOTATIONS = Arrays.asList(ANY_NON_DEFAULT, BUILDER_METHOD, CONFIGURATION, CONFIGURATION_VALUE, INSTANTIATABLE, NON_EMPTY, PRE_BUILD, TRANSFORM); + + private Types() { + } + + public static MethodSpec.Builder overriding(ExecutableElement method) { + return MethodSpec.methodBuilder(method.getSimpleName().toString()) + .addAnnotation(Override.class) + .addModifiers(method.getModifiers().stream().filter(modifier -> modifier != Modifier.ABSTRACT).collect(Collectors.toList())) + .returns(TypeName.get(method.getReturnType())) + .varargs(method.isVarArgs()) + .addExceptions(method.getThrownTypes().stream().map(TypeName::get).collect(Collectors.toList())) + .addTypeVariables(method.getTypeParameters().stream().map(TypeVariableName::get).collect(Collectors.toList())) + .addParameters(method.getParameters().stream().map(element -> ParameterSpec.get(element).toBuilder() + .addAnnotations(element.getAnnotationMirrors().stream().map(AnnotationSpec::get).collect(Collectors.toList())).build()).collect(Collectors.toList())); + } + + public static ExecutableElement getOnlyMethod(ProcessingEnvironment processingEnv, String className) { + final TypeElement typeElement = processingEnv.getElementUtils().getTypeElement(className); + final List elements = ElementFilter.methodsIn(typeElement.getEnclosedElements()); + if (elements.size() == 1) { + return elements.get(0); + } else { + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Needs exactly one method", typeElement); + throw new IllegalArgumentException(); + } + } +} diff --git a/annotations/src/main/java/org/acra/annotation/BuilderMethod.java b/annotations/src/main/java/org/acra/annotation/BuilderMethod.java new file mode 100644 index 000000000..d7376c92a --- /dev/null +++ b/annotations/src/main/java/org/acra/annotation/BuilderMethod.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author F43nd1r + * @since 10.01.2018 + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +public @interface BuilderMethod { +} diff --git a/annotations/src/main/java/org/acra/annotation/Configuration.java b/annotations/src/main/java/org/acra/annotation/Configuration.java index 4ab65920b..c57c1865e 100644 --- a/annotations/src/main/java/org/acra/annotation/Configuration.java +++ b/annotations/src/main/java/org/acra/annotation/Configuration.java @@ -32,5 +32,5 @@ @Target(ElementType.ANNOTATION_TYPE) public @interface Configuration { Class baseBuilderClass() default Object.class; - boolean createBuilderFactory() default true; + boolean isPlugin() default true; } diff --git a/annotations/src/main/java/org/acra/annotation/ConfigurationValue.java b/annotations/src/main/java/org/acra/annotation/ConfigurationValue.java new file mode 100644 index 000000000..0069b6dbe --- /dev/null +++ b/annotations/src/main/java/org/acra/annotation/ConfigurationValue.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2018 the ACRA team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.acra.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author F43nd1r + * @since 10.01.2018 + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +public @interface ConfigurationValue { +} diff --git a/build.gradle b/build.gradle index 08db42d0c..f829ea195 100644 --- a/build.gradle +++ b/build.gradle @@ -103,7 +103,6 @@ subprojects { plugins.withType(LibraryPlugin) { android { compileSdkVersion Integer.parseInt(androidVersion) - buildToolsVersion androidBuildToolsVersion defaultConfig { minSdkVersion androidMinVersion targetSdkVersion androidVersion @@ -120,13 +119,17 @@ subprojects { testOptions { unitTests { includeAndroidResources = true + all { + systemProperty 'robolectric.logging.enabled', true + } } } } dependencies { testImplementation "junit:junit:$junitVersion" - testImplementation "org.robolectric:robolectric:3.5.1" + testImplementation "org.robolectric:robolectric:3.6.1" + testImplementation "org.ow2.asm:asm:6.0" } task sourcesJar(type: Jar) { diff --git a/gradle.properties b/gradle.properties index a87b63e7a..df79b242c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,22 +18,20 @@ # gradle options org.gradle.daemon=true -org.gradle.jvmargs=-Xmx4g -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 +org.gradle.jvmargs=-Xmx4g -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 --add-modules java.se.ee --add-opens java.base/jdk.internal.misc=ALL-UNNAMED org.gradle.configureondemand=true org.gradle.parallel=true org.gradle.caching=true -android.enableAapt2=false # upload properties group=ch.acra # versions -version=5.0.3-SNAPSHOT -supportVersion=25.4.0 -androidVersion=26 -androidMinVersion=9 -androidBuildToolsVersion=26.0.2 -androidBuildPluginVersion=3.0.1 +version=5.1.0-SNAPSHOT +supportVersion=27.0.2 +androidVersion=27 +androidMinVersion=14 +androidBuildPluginVersion=3.1.0-beta1 bintrayPluginVersion=1.8.0 releasePluginVersion=2.6.0 -autoServiceVersion=1.0-rc3 +autoServiceVersion=1.0-rc4 junitVersion=4.12 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 115a148a4..9249fd771 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-all.zip diff --git a/gradlew b/gradlew old mode 100644 new mode 100755