/* * Copyright 2013 serso aka se.solovyev * * 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. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Contact details * * Email: se.solovyev@gmail.com * Site: http://se.solovyev.org */ package org.solovyev.android.calculator.functions; import android.support.annotation.NonNull; import jscl.JsclMathEngine; import jscl.math.function.CustomFunction; import jscl.math.function.Function; import jscl.math.function.IFunction; import org.simpleframework.xml.Serializer; import org.simpleframework.xml.core.Persister; import org.solovyev.android.Check; import org.solovyev.android.calculator.R; import org.solovyev.android.calculator.entities.BaseEntitiesRegistry; import org.solovyev.android.calculator.entities.Category; import org.solovyev.android.calculator.entities.Entities; import org.solovyev.android.calculator.json.Json; import org.solovyev.android.calculator.json.Jsonable; import org.solovyev.android.io.FileSaver; import org.solovyev.common.text.Strings; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Singleton; import java.io.File; import java.util.*; import static android.text.TextUtils.isEmpty; @Singleton public class FunctionsRegistry extends BaseEntitiesRegistry<Function> { { addDescription("sin", R.string.c_fun_description_sin); addDescription("cos", R.string.c_fun_description_cos); addDescription("tan", R.string.c_fun_description_tan); addDescription("cot", R.string.c_fun_description_cot); addDescription("asin", R.string.c_fun_description_asin); addDescription("acos", R.string.c_fun_description_acos); addDescription("atan", R.string.c_fun_description_atan); addDescription("acot", R.string.c_fun_description_acot); addDescription("ln", R.string.c_fun_description_ln); addDescription("lg", R.string.c_fun_description_lg); addDescription("log", R.string.c_fun_description_log); addDescription("exp", R.string.c_fun_description_exp); addDescription("√", R.string.c_fun_description_sqrt); addDescription("sqrt", R.string.c_fun_description_sqrt); addDescription("cubic", R.string.c_fun_description_cubic); addDescription("abs", R.string.c_fun_description_abs); addDescription("sgn", R.string.c_fun_description_sgn); addDescription("eq", R.string.c_fun_description_eq); addDescription("le", R.string.c_fun_description_le); addDescription("ge", R.string.c_fun_description_ge); addDescription("ne", R.string.c_fun_description_ne); addDescription("lt", R.string.c_fun_description_lt); addDescription("gt", R.string.c_fun_description_gt); addDescription("rad", R.string.c_fun_description_rad); addDescription("dms", R.string.c_fun_description_dms); addDescription("deg", R.string.c_fun_description_deg); } @Inject public FunctionsRegistry(@Nonnull JsclMathEngine mathEngine) { super(mathEngine.getFunctionsRegistry()); } public void addOrUpdate(@Nonnull Function newFunction, @Nullable Function oldFunction) { final Function function = addOrUpdate(newFunction); if (oldFunction == null) { bus.post(new AddedEvent(function)); } else { bus.post(new ChangedEvent(oldFunction, function)); } } @Override public void init() { Check.isNotMainThread(); try { migrateOldFunctions(); final List<CustomFunction.Builder> functions = new ArrayList<>(); functions.add(new CustomFunction.Builder(true, "log", Arrays.asList("base", "x"), "ln(x)/ln(base)")); functions.add(new CustomFunction.Builder(true, "√3", Collections.singletonList("x"), "x^(1/3)")); functions.add(new CustomFunction.Builder(true, "√4", Collections.singletonList("x"), "x^(1/4)")); functions.add(new CustomFunction.Builder(true, "√n", Arrays.asList("x", "n"), "x^(1/n)")); functions.add(new CustomFunction.Builder(true, "re", Collections.singletonList("x"), "(x+conjugate(x))/2")); functions.add(new CustomFunction.Builder(true, "im", Collections.singletonList("x"), "(x-conjugate(x))/(2*i)")); for (CppFunction function : loadEntities(CppFunction.JSON_CREATOR)) { functions.add(function.toJsclBuilder()); } addSafely(functions); } finally { setInitialized(); } } /** * As some functions might depend on not-yet-loaded functions we need to try to add all functions first and then * re-run again if there are functions left. This process should continue until we can't add more functions * @param functions functions to add */ private void addSafely(@Nonnull List<CustomFunction.Builder> functions) { final List<Exception> exceptions = new ArrayList<>(); while (functions.size() > 0) { final int sizeBefore = functions.size(); // prepare exceptions list for new round exceptions.clear(); addSafely(functions, exceptions); final int sizeAfter = functions.size(); if (sizeBefore == sizeAfter) { break; } } if (functions.size() > 0) { // report exceptions for (Exception exception : exceptions) { errorReporter.onException(exception); } } } private void addSafely(@Nonnull List<CustomFunction.Builder> functions, @Nonnull List<Exception> exceptions) { for (Iterator<CustomFunction.Builder> iterator = functions.iterator(); iterator.hasNext(); ) { final CustomFunction.Builder function = iterator.next(); try { addSafely(function.create()); iterator.remove(); } catch (Exception e) { exceptions.add(e); } } } @Override public void remove(@Nonnull Function function) { super.remove(function); bus.post(new RemovedEvent(function)); } @Nullable @Override protected Jsonable toJsonable(@NonNull Function function) { if (function instanceof IFunction) { return CppFunction.builder((IFunction) function).build(); } return null; } private void migrateOldFunctions() { final String xml = preferences.getString(OldFunctions.PREFS_KEY, null); if (isEmpty(xml)) { return; } try { final Serializer serializer = new Persister(); final OldFunctions oldFunctions = serializer.read(OldFunctions.class, xml); if (oldFunctions != null) { List<CppFunction> functions = OldFunctions.toCppFunctions(oldFunctions); FileSaver.save(getEntitiesFile(), Json.toJson(functions).toString()); } preferences.edit().remove(OldFunctions.PREFS_KEY).apply(); } catch (Exception e) { errorReporter.onException(e); } } @Override @NonNull protected File getEntitiesFile() { return new File(filesDir, "functions.json"); } @Override public Category getCategory(@Nonnull Function function) { return Entities.getCategory(function, FunctionCategory.values()); } @Nullable @Override public String getDescription(@Nonnull String name) { final Function function = get(name); String description = null; if (function instanceof CustomFunction) { description = ((CustomFunction) function).getDescription(); } if (!Strings.isEmpty(description)) { return description; } return super.getDescription(name); } public static final class RemovedEvent { @NonNull public final Function function; public RemovedEvent(@NonNull Function function) { this.function = function; } } public static final class AddedEvent { @NonNull public final Function function; public AddedEvent(@NonNull Function function) { this.function = function; } } public static final class ChangedEvent { @NonNull public final Function oldFunction; @NonNull public final Function newFunction; public ChangedEvent(@NonNull Function oldFunction, @NonNull Function newFunction) { this.oldFunction = oldFunction; this.newFunction = newFunction; } } }