diff --git a/.gitignore b/.gitignore index bc1d8bf6..22c4f6c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,141 @@ -gen/ +/.hg/ + +# Created by https://www.toptal.com/developers/gitignore/api/androidstudio +# Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files bin/ +gen/ +out/ + +# Gradle files +.gradle +.gradle/ +build/ + +# Signing files +.signing/ +signing.properties + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio +/*/build/ +/*/local.properties +/*/out +/*/*/build +/*/*/production +captures/ +.navigation/ +*.ipr +*~ +*.swp + +# Keystore files +*.jks +*.keystore + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Android Patch +gen-external-apklibs + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# NDK +obj/ + +# IntelliJ IDEA +*.iml +*.iws +/out/ + +# User-specific configurations +.idea/caches/ +.idea/libraries/ +.idea/shelf/ +.idea/workspace.xml +.idea/tasks.xml +.idea/deploymentTargetDropDown.xml +.idea/.name +.idea/compiler.xml +.idea/copyright/profiles_settings.xml +.idea/encodings.xml +.idea/misc.xml +.idea/modules.xml +.idea/scopes/scope_settings.xml +.idea/dictionaries +.idea/vcs.xml +.idea/jsLibraryMappings.xml +.idea/datasources.xml +.idea/dataSources.ids +.idea/sqlDataSources.xml +.idea/dynamic.xml +.idea/uiDesigner.xml +.idea/assetWizardSettings.xml +.idea/gradle.xml +.idea/jarRepositories.xml +.idea/navEditor.xml + +# Legacy Eclipse project files .classpath .project +.cproject .settings/ + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid* + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Mongo Explorer plugin +.idea/mongoSettings.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### AndroidStudio Patch ### + +!/gradle/wrapper/gradle-wrapper.jar + +# End of https://www.toptal.com/developers/gitignore/api/androidstudio + diff --git a/.hgignore b/.hgignore new file mode 100644 index 00000000..944ee804 --- /dev/null +++ b/.hgignore @@ -0,0 +1,137 @@ +# Created by https://www.toptal.com/developers/gitignore/api/androidstudio +# Edit at https://www.toptal.com/developers/gitignore?templates=androidstudio + +### AndroidStudio ### +# Covers files to be ignored for android development using Android Studio. + +# Built application files +\.apk +\.ap_ +\.aab + +# Files for the ART/Dalvik VM +\.dex + +# Java class files +\.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +^\.gradle +^\.gradle/ +build/ + +# Signing files +^\.signing/ +signing\.properties + +# Local configuration file (sdk path, etc) +^local\.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +\.log + +# Android Studio +/.*/build/ +/.*/local\.properties +/.*/out +/.*/production +captures/ +.navigation/ +\.ipr +~$ +\.swp + +# Keystore files +\.jks +\.keystore + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Android Patch +gen-external-apklibs + +# External native build folder generated in Android Studio 2.2 and later +\.externalNativeBuild + +# NDK +obj/ + +# IntelliJ IDEA +\.iml +\.iws +/out/ + +# User-specific configurations +\.idea/caches/ +\.idea/libraries/ +\.idea/shelf/ +\.idea/workspace\.xml +\.idea/tasks\.xml +\.idea/deploymentTargetDropDown\.xml +\.idea/\.name +\.idea/compiler\.xml +\.idea/copyright/profiles_settings\.xml +\.idea/encodings\.xml +\.idea/misc\.xml +\.idea/modules\.xml +\.idea/scopes/scope_settings\.xml +\.idea/dictionaries +\.idea/vcs\.xml +\.idea/jsLibraryMappings\.xml +\.idea/datasources\.xml +\.idea/dataSources\.ids +\.idea/sqlDataSources\.xml +\.idea/dynamic\.xml +\.idea/uiDesigner\.xml +\.idea/assetWizardSettings\.xml +\.idea/gradle\.xml +\.idea/jarRepositories\.xml +\.idea/navEditor\.xml + +# Legacy Eclipse project files +\.classpath +\.project +\.cproject +\.settings/ + +# Mobile Tools for Java (J2ME) +\.mtj\.tmp/ + +# Package Files # +\.war +\.ear + +# virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) +hs_err_pid.* + +## Plugin-specific files: + +# mpeltonen/sbt-idea plugin +\.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin\.xml + +# Mongo Explorer plugin +\.idea/mongoSettings\.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings\.xml +crashlytics\.properties +crashlytics-build\.properties +fabric\.properties + +### AndroidStudio Patch ### + +!/gradle/wrapper/gradle-wrapper.jar + +# End of https://www.toptal.com/developers/gitignore/api/androidstudio diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..eaf91e2a --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 00000000..48052b24 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/AndroidManifest.xml b/AndroidManifest.xml deleted file mode 100644 index 9036cf6b..00000000 --- a/AndroidManifest.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/README.md b/README.md index a8542a29..bd8937cb 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,96 @@ -# Android Units +# Units -![][0] +![App logo](app/src/main/res/mipmap-mdpi/ic_launcher.png) -Android Units is a powerful unit-aware calculator that can also easily perform simple unit conversions. It's inspired by GNU Units. +Units is a powerful unit-aware calculator that can perform a variety of unit conversions +and also doubles as a simple scientific calculator. It's inspired by GNU Units. + +This edition is a continuation of [Steve Pomeroy's original app](https://github.com/xxv/Units). ## Download -[![](market_qrcode.png)](market://search?q=pname:info.staticfree.android.units)You can [download Units from the Android market][1] or directly from [Units.apk][2]. The [changelog][3] is below. + + + + +… or download it directly from the [Github Releases page](https://github.com/buttercookie42/Units/releases). ## Usage -### key +| key | +| --- | +| Anything that is shown within a box can be entered into Units. Eg. `m³÷hr` | + +Place the value and the unit you want to convert from in the "you have" box (e.g. `4.9inches`) and +the unit you wish to convert to in the "you want" box (e.g. `cm`). To enter units, either press the +"unit" button and select from the list, or tap on the input box a second time and an on-screen +keyboard should pop up. + +You can enter simple units, such as `cm`/`centimeter` or complex units, such as `m^3÷hr`. You can +press the "unit" button to show a list of all the units. If you don't specify a "to" unit, it will +provide a definition in base units. You can also convert into lists of units, for example `ft; in`. +For a list of examples, please see below. + +### Auto-completion + +[![](extra/sshot02.thumb.png)](extra/sshot02.png) + +Both "you have" and "you want" will auto-complete with all the known units, sorted by usage. The +more you use a given unit, the closer it is to the top of the list. The usage is seeded with initial +usage amounts for common units, including some regional adjustments based on your device's locale. + +Units will not start off auto-completing every possible permutation of metric prefix (eg. `deci`, +`centi`, `milli`) with unit name (`meter`, `liter`). Once you make a calculation with a prefix+unit +combination, Units will remember it and auto-complete it from then-on. + +### Reciprocal detection + +Units will auto-detect instances where you may have forgotten to convert from the reciprocal of a +given value. For instance, if you enter `100mpg` and ask for `liter÷100km`, it will compute it using +the reciprocal of the "from" value (`1÷(100mpg)`) and provide a warning that it used the reciprocal. + +### Unit lists -Anything that is shown within a dotted box can be entered into Units. Eg. `m³÷hr` +You can convert values into arbitrary lists of units, for example time durations into hours, minutes +and seconds, lengths into foots and inches, or weights into +[libs and ozzes](https://www.gocomics.com/peanuts/1966/03/10). Enter the units separated by a +semicolon (such as `ft; in`), and autocomplete will work as usual. You can also convert into +fractional units like `ft; in; 1|8 in`. -Place the value and the unit you want to convert from in the "you have" box (eg. `4.9inches`) and the unit you wish to convert to in the "you want" box (eg. `cm`). To enter units, either press the "unit" button and select from the list, or tap on the input box a second time and an on-screen keyboard should pop up. +To split up display of the last unit into a decimal and an integer part, add a trailing semicolon. +For rounding the last unit given, add two trailing semicolons. -You can enter simple units, such as `cm`/`centimeter` or complex units, such as `m^3÷hr`. You can press the "unit" button to show a list of all the units. If you don't specify a "to" unit, it will provide a definition in base units. For a list of examples, please see below. +For example, converting from `42 cm` gives the following results: -## Auto-completion +| You want | Result | +|------------|----------------------------------------------------| +| `ft; in` | **1**`ft` + **4.5354331**`in` | +| `ft; in;` | **1**`ft` + **4**`in` + **0.5354331**`in` | +| `ft; in;;` | **1**`ft` + **5**`in` (rounded up to nearest `in`) | -[![](extra/sshot02.thumb.png)](extra/sshot02.png)Both "you have" and "you want" will auto-complete with all the known units, sorted by usage. The more you use a given unit, the closer it is to the top of the list. The usage is seeded with initial usage amounts for common units, including some regional adjustments based on your device's locale. +You can also use the same syntax for rounding single units. For example `3 ft + 7 in` to `cm` will +give **109.22**`cm`, however the same length converted to `cm;;` will result in **109**`cm (rounded +down to nearest cm)`. -Units will not start off auto-completing every possible permutation of metric prefix (eg. `deci`, `centi`, `milli`) with unit name (`meter`, `liter`). Once you make a calculation with a prefix+unit combination, Units will remember it and auto-complete it from then-on. +To make things easier, Units also includes aliases for a few commonly used unit combinations: -## Reciprocal detection +| Alias | Corresponds to | +|---------|--------------------------------------------------------------------------------------------------| +| `hms` | `hr; min; sec` | +| `time` | `year; day; hr; min; sec` | +| `dms` | `deg; arcmin; arcsec` | +| `ftin` | `ft; in; 1\|8 in` | +| `usvol` | `cup; 3\|4 cup; 2\|3 cup; 1\|2 cup; 1\|3 cup; 1\|4 cup; tbsp; tsp; 1\|2 tsp; 1\|4 tsp; 1\|8 tsp` | +| `lsd` | `poundsterling; shilling; oldpence; farthing` | -Units will auto-detect instances where you may have forgotten to convert from the reciprocal of a given value. For instance, if you enter `100mpg` and ask for `liter÷100km`, it will compute it using the reciprocal of the "from" value (`1÷(100mpg)`) and provide a warning that it used the reciprocal. +### Calculations -## Calculations +[![](extra/sshot01.thumb.png)](extra/sshot01.png) -[![](extra/sshot01.thumb.png)](extra/sshot01.png)You can enter complex calculations in both input areas. For example, you can add two conformable units such as, `2 cm + (4 + (1/2)) in` or multiply values to higher dimensions, such as, `3m × 1m × 2cm` to convert to `liter` or `m^3`. You can even convert to an arbitrary complex unit, provided that everything conforms properly. +You can enter complex calculations in both input areas. For example, you can add two conformable +units such as, `2 cm + (4 + (1/2)) in` or multiply values to higher dimensions, such as, +`3m × 1m × 2cm` to convert to `liter` or `m^3`. You can even convert to an arbitrary complex unit, +provided that everything conforms properly. ## Examples @@ -38,6 +98,8 @@ Units will auto-detect instances where you may have forgotten to convert from th - US→EU height conversion `5ft + 6in` = **167.64**`cm` +- EU→US height conversion +`180 cm` = **5**`ft` + **10**`in` + **6.9291339** *`1|8in` - UK→US mass conversion `13 stone` = **182**`lbs` - Volume conversion @@ -53,18 +115,22 @@ Units will auto-detect instances where you may have forgotten to convert from th `1cup sugar ÷0.5gallon` = **25.0**`gram ÷8floz` - What percent juice is it? `1cup ÷0.5gallon` = **12.5**`percent` -In comparison (per. 8floz), Minute Maid® Lemonade has 3% juice and 27g sugar. Coke has 46g sugar per. 8floz. + +In comparison (per 8 floz), Minute Maid® Lemonade has 3% juice and 27g sugar. Coke has 46g sugar per +8floz. ### Fun conversions -As you can see above, Units knows a number of units that are useful in the kitchen. Most are for volume↔mass conversion (which can be found under "density"), such as `flour_scooped`, `sugar`, and `butter`. It also has a few known masses, such as `eggs`, `stickbutter` and `venusmass`. +As you can see above, Units knows a number of units that are useful in the kitchen. Most are for +volume↔mass conversion (which can be found under "density"), such as `flour_scooped`, `sugar`, and +`butter`. It also has a few known masses, such as `eggs`, `stickbutter` and `venusmass`. - Your mass in eggs `78kg` = **1560**`eggs` - Time to walk across the U.S. (without sleeping) -`2600mile ÷3mph` = **5.15873**`week` +`2600mile ÷3mph` = **5**`week` + **1.1111111**`day` - Time to walk across the U.S. (assuming walking only 8 hours per day) -`2600mile ÷(3mph ×(8hour ÷day))` = **15.47619**`week` +`2600mile ÷(3mph ×(8hour ÷day))` = **15**`week` + **3.3333333**`day` - Gasoline efficiency in an alternative unit `1÷(25mile ÷gallon)` = **79.36508**`microhogshead ÷furlong` @@ -81,64 +147,57 @@ As you can see above, Units knows a number of units that are useful in the kitch ## Changelog -* 15 September, 2011: 1.0 - Added advanced keypad and improved keypad UI. Adds Russian translations. Numerous bug fixes. -* 13 January, 2011: 0.9 - Improved interface graphics. Fixed bug with imperial units showing up in non-US,UK locales. Fixed loss of precision of output. Fixed various crashes and database bugs. -* 10 December, 2010: 0.8 - Fixed temperature conversion and other functions. Added parenthesis auto-complete. -* 02 December, 2010: 0.7 - Added search and browse. Added Italian translation. -* 30 August, 2010: 0.6 - Added unit classification to unit browser. -* 17 August, 2010: 0.5 - Added pop-up unit browser; major overhaul of unit database backend. -* 12 August, 2010: 0.4.1 - Added database-backed history; improved saved state so rotation doesn't clear recent result -* 20 July, 2010: 0.4 - Units entry now conforms to opposite entry field. -* 12 July, 2010: 0.3 - First version to hit the Market - -### Support Android Units - -[![Flattr this](http://api.flattr.com/button/flattr-badge-large.png "Flattr this")](http://flattr.com/thing/86174/Android-Units) - -![][4] - -If you like Units and wish to donate to the devlopers, please visit the [Units project page][5]. +* 20 January, 2024: 1.2 – Ability to convert into lists of units. Support for Android 13's + per-app language preferences. Use US units by default for English units + outside of the UK. +* 05 January, 2024: 1.1 – Improved compatibility with modern Android versions. Adds German and + Spanish translations. Various improvements and fixes. +* 15 September, 2011: 1.0 – Added advanced keypad and improved keypad UI. Adds Russian translations. + Numerous bug fixes. +* 13 January, 2011: 0.9 – Improved interface graphics. Fixed bug with imperial units showing up in + non-US,UK locales. Fixed loss of precision of output. Fixed various + crashes and database bugs. +* 10 December, 2010: 0.8 – Fixed temperature conversion and other functions. Added parenthesis + auto-complete. +* 02 December, 2010: 0.7 – Added search and browse. Added Italian translation. +* 30 August, 2010: 0.6 – Added unit classification to unit browser. +* 17 August, 2010: 0.5 – Added pop-up unit browser; major overhaul of unit database backend. +* 12 August, 2010: 0.4.1 – Added database-backed history; improved saved state so rotation doesn't + clear recent result +* 20 July, 2010: 0.4 – Units entry now conforms to opposite entry field. +* 12 July, 2010: 0.3 – First version to hit the Market ## This is free software / open source -Units is an Android interpretation of the classic [GNU Units][6] utility, using [Units in Java][7] for all the heavy lifting. +Units is an Android interpretation of the classic [GNU Units](http://www.gnu.org/software/units/) +utility, using [Units in Java](http://units-in-java.sourceforge.net/) for all the heavy lifting. -If you wish to get the source to this application and/or contribute to its ongoing development, please visit the [Android Units project page][5]. - -The source is available by way of the [Android Units git repository][8]. You can grab a copy for yourself by running: - - git clone git://staticfree.info/git/Units/ - -Units needs translators! If you'd like to contribute a translation, please visit the [Units Translation Page][9] and request access. +Units needs translators! If you'd like to contribute a translation, please open a pull request. ## License (GPLv3) Android Units ©2010-2011 Steve Pomeroy, contributors (below) -This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +This program is free software: you can redistribute it and/or modify it under the terms of the GNU +General Public License as published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. -This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without +even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. -You should have received a copy of the GNU General Public License along with this program. If not, see [http://www.gnu.org/licenses/][10]. +You should have received a copy of the GNU General Public License along with this program. If not, +see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). ## Contributors Units is an ongoing project, with volunteers contributing freely to it. We would like to thank: -* [Andrejs Gorbunovs][11] - Russian translations - - +* [Adrien Benoit à la Guillaume](mailto:abalg@laposte.net) – French translations +* [Andrejs Gorbunovs](mailto:andrejs.gorbunovs@inbox.lv) – Russian translations +* [ingfabby](https://github.com/ingfabby) – Spanish translations +* [Jan Henning](https://github.com/buttercookie42) – German translations and code fixes -[0]: res/drawable-mdpi/icon.png -[1]: market://search?q=pname:info.staticfree.android.units -[2]: Units.apk -[3]: #changelog -[4]: https://www.paypalobjects.com/WEBSCR-640-20110401-1/en_US/i/scr/pixel.gif -[5]: http://staticfree.info/projects/units/ -[6]: http://www.gnu.org/software/units/ -[7]: http://units-in-java.sourceforge.net/ -[8]: git://staticfree.info/git/Units/ -[9]: http://mygengo.com/string/p/android-units-1/ -[10]: http://www.gnu.org/licenses/ -[11]: mailto:andrejs.gorbunovs@inbox.lv +A special thanks also goes out to [Roman Redziejowski](https://units-in-java.sourceforge.net/) as +the original creator of the Java port of GNU Units. diff --git a/Units.apk b/Units.apk deleted file mode 100644 index 94e42201..00000000 Binary files a/Units.apk and /dev/null differ diff --git a/android_style.css b/android_style.css deleted file mode 100644 index 9a632ec1..00000000 --- a/android_style.css +++ /dev/null @@ -1 +0,0 @@ -/* this file intentionally left blank */ diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 00000000..3aae6fe3 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,86 @@ +import com.android.build.api.dsl.ApkSigningConfig + +plugins { + id("com.android.application") + id("com.sidneysimmons.gradle-plugin-external-properties") +} + +externalProperties { + propertiesFileResolver(file("signing.properties")) +} + +android { + namespace = "de.buttercookie.units" + compileSdk = 34 + + defaultConfig { + applicationId = "de.buttercookie.units" + minSdk = 4 + targetSdk = 34 + versionCode = 12 + versionName = "1.2.1" + } + + androidResources { + generateLocaleConfig = true; + } + + buildFeatures { + buildConfig = true + } + + buildTypes { + release { + isMinifyEnabled = true + isShrinkResources = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + packaging { + resources { + // Not needed as long as we don't use reflection with Kotlin + excludes.add("**/*.kotlin_builtins") + excludes.add("**/*.kotlin_module") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + signingConfigs { + named("debug") { + if (checkExternalSigningConfig()) { + applyExternalSigningConfig() + } else { + defaultConfig.signingConfig + } + } + create("release") { + if (checkExternalSigningConfig()) { + applyExternalSigningConfig() + android.buildTypes.getByName("release").signingConfig = this + } + } + } +} + +dependencies { + compileOnly("androidx.annotation:annotation:1.7.1") + implementation(files("libs/andro-views.jar")) +} + +fun ApkSigningConfig.checkExternalSigningConfig(): Boolean { + return props.exists("$name.keyStore") && + file(props.get("$name.keyStore")).exists() && + props.exists("$name.storePassword") && + props.exists("$name.keyAlias") && + props.exists("$name.keyPassword") +} + +fun ApkSigningConfig.applyExternalSigningConfig() { + storeFile = file(props.get("$name.keyStore")) + storePassword = props.get("$name.storePassword") + keyAlias = props.get("$name.keyAlias") + keyPassword = props.get("$name.keyPassword") +} diff --git a/libs/andro-views.jar b/app/libs/andro-views.jar similarity index 100% rename from libs/andro-views.jar rename to app/libs/andro-views.jar diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..283e6415 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,23 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +-dontobfuscate \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..e54bab39 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/assets/README.xhtml b/app/src/main/assets/README.xhtml new file mode 100644 index 00000000..2300346e --- /dev/null +++ b/app/src/main/assets/README.xhtml @@ -0,0 +1,307 @@ + + + + + Units + + + + + + + +

Units is a powerful unit-aware calculator that can perform a variety of unit conversions + and also doubles as a simple scientific calculator. It's inspired by GNU Units.

+ +

Usage

+ +

key

Anything that is shown within a dotted box can be entered + into Units. Eg. m³÷hr
+

Place the value and the unit you want to convert from in the "you have" box (e.g. + 4.9inches) and the unit you wish to convert to in the "you want" box (e.g. + cm). To enter units, either press the "unit" button and select from the list, or tap + on the input box a second time and an on-screen keyboard should pop up.

+ +

You can enter simple units, such as cm/centimeter or complex units, such as + m^3÷hr. You can press the "unit" button to show a list of all the units. If you don't + specify a "to" unit, it will provide a definition in base units. You can also convert into lists + of units, for example ft; in. For a list of examples, please see below.

+ +

Auto-completion

+ +

Both "you have" and "you want" will auto-complete with all the known units, sorted by usage. The + more you use a given unit, the closer it is to the top of the list. The usage is seeded with + initial usage amounts for common units, including some regional adjustments based on your + device's locale.

+ +

Units will not start off auto-completing every possible permutation of metric prefix (eg. deci, + centi, milli) with unit name (meter, liter). Once + you make a calculation with a prefix+unit combination, Units will remember it and auto-complete + it from then-on.

+ +

Reciprocal detection

+ +

Units will auto-detect instances where you may have forgotten to convert from the reciprocal of a + given value. For instance, if you enter 100mpg and ask for liter÷100km, it + will compute it using the reciprocal of the "from" value (1÷(100mpg)) and provide a + warning that it used the reciprocal.

+ +

Unit lists

+ +

You can convert values into arbitrary lists of units, for example time durations into hours, + minutes and seconds, lengths into foots and inches, or weights into + libs and ozzes. Enter the units + separated by a semicolon (such as ft; in), and autocomplete will work as usual. You + can also convert into fractional units like ft; in; 1|8 in.

+

To split up display of the last unit into a decimal and an integer part, add a trailing + semicolon. For rounding the last unit given, add two trailing semicolons.

+

For example, converting from 42 cm gives the following results:

+ + + + + + + + + + + + + + + + + + + + + +
You wantResult
ft; in1 ft + 4.5354331 in
ft; in;1 ft + 4 in + 0.5354331 in +
ft; in;;1 ft + 5 in (rounded up to + nearest in) +
+

You can also use the same syntax for rounding single units. For example 3 ft + 7 in to + cm will give 109.22 cm, however the same length + converted to cm;; will result in 109 cm (rounded down to + nearest cm).

+ +

To make things easier, Units also includes aliases for a few commonly used unit combinations:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AliasCorresponds to
hmshr; min; sec
timeyear; day; hr; min; sec
dmsdeg; arcmin; arcsec
ftinft; in; 1|8 in
usvolcup; 3|4 cup; 2|3 cup; 1|2 cup; 1|3 cup; 1|4 cup; tbsp; tsp; 1|2 tsp; 1|4 tsp; 1|8 tsp
lsdpoundsterling; shilling; oldpence; farthing
+ +

Calculations

+ +

You can enter complex calculations in both input areas. For example, you can add two conformable + units such as, 2 cm + (4 + (1/2)) in or multiply values to higher dimensions, such + as, 3m × 1m × 2cm to convert to liter or m^3. You can even + convert to an arbitrary complex unit, provided that everything conforms properly.

+ +

Examples

+

Practical conversions

+
+
US→EU height conversion
+
5ft + 6in = 167.64 cm
+
EU→US height conversion
+
5ft + 6in = 5 ft + 10 in + + 6.9291339 * 1|8in
+
UK→US mass conversion
+
13 stone = 182 lbs
+
Volume conversion
+
10cm×5cm ×3cm = 0.15 liter
+
Kitchen: volume to mass
+
1cup flour_sifted = 113.398094 g
+
Driving: time to distance
+
100kph ×5minute = 5.1780934 mile
+
+ +

Making lemonade, nutritional info

+
+
How much sugar does it have compared to other drinks?
+
1cup sugar ÷0.5gallon = 25.0 gram ÷8floz
+
What percent juice is it?
+
1cup ÷0.5gallon = 12.5 percent
+
+ +

In comparison (per 8 floz), Minute Maid® Lemonade has 3 % juice and 27 g + sugar. Coke has 46 g sugar per 8 floz.

+ +

Fun conversions

+

As you can see above, Units knows a number of units that are useful in the kitchen. Most are for + volume↔mass conversion (which can be found under "density"), such as flour_scooped, + sugar, and butter. It also has a few known masses, such as eggs, + stickbutter and venusmass.

+
+
Your mass in eggs
+
78kg = 1560 eggs
+
Time to walk across the U.S. (without sleeping)
+
2600mile ÷3mph = 5 week + + 1.1111111 day
+
Time to walk across the U.S. (assuming walking only 8 hours per day)
+
2600mile ÷(3mph ×(8hour ÷day)) = 15 week + + 3.3333333 day
+
Gasoline efficiency in an alternative unit
+
1÷(25mile ÷gallon) = 79.36508 microhogshead ÷furlong +
+
+ +

Science & Physics

+
+
Electricity
+
3volt ×150mA = 0.45 watt
+
Approximate time it takes light to get from the sun to the Earth.
+
sundist ÷c = 8.316755 minute
+
EM frequency to wavelength
+
light ÷2.417GHz = 12.403494 cm
+
using e=mc² to figure out how much energy 1g of matter is equivalent to
+
1gram energy = 21.480764 kiloton tnt
+
+ +

Changelog

+ + +

This is free software / open source

+ +

Units is an Android interpretation of the classic GNU + Units utility, using Units in Java for + all the heavy lifting.

+ +

If you wish to get the source to this application and/or contribute to its ongoing development, + please visit the Android Units project + page.

+ +

Units needs translators! If you'd like to contribute a translation, please open a pull + request on Github.

+ + +

License (GPLv3)

+

Android Units
+ ©2010-2011 Steve Pomeroy, contributors (below)

+ +

This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version.

+ +

This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details.

+ +

You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. +

+ +

Contributors

+

Units is an ongoing project, with volunteers contributing freely to it. We would like to + thank:

+ +

A special thanks also goes out to Roman + Redziejowski as the original creator of the Java port of GNU Units.

+ + diff --git a/assets/android_style.css b/app/src/main/assets/android_style.css similarity index 100% rename from assets/android_style.css rename to app/src/main/assets/android_style.css diff --git a/assets/units.dat b/app/src/main/assets/units.dat similarity index 92% rename from assets/units.dat rename to app/src/main/assets/units.dat index 52a18da8..077ce46e 100644 --- a/assets/units.dat +++ b/app/src/main/assets/units.dat @@ -1,11 +1,11 @@ # # This file is the units database for use with GNU units, a units conversion -# program by Adrian Mariano adrian@cam.cornell.edu +# program by Adrian Mariano adrianm@gnu.org # -# 14 February 2010 Version 1.50 +# 17 November 2011 Version 1.53 # # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2004, 2005, 2006 -# 2007, 2008, 2009, 2010 +# 2007, 2008, 2009, 2010, 2011 # Free Software Foundation, Inc # # This program is free software; you can redistribute it and/or modify @@ -21,8 +21,7 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, -# Boston, MA 02110-1301 USA -# +# Boston, MA 02110-1301 USA# ############################################################################ # # Improvements and corrections are welcome. @@ -57,16 +56,52 @@ # http://www.ukmetrication.com/history.htm # # Thanks to Jeff Conrad for assistance in ferreting out unit definitions. -# + ########################################################################### # # If units you use are missing or defined incorrectly, please contact me. +# If your country's local units are missing and you are willing to supply +# them, please send me a list. # # I added shoe size information but I'm not convinced that it's correct. # If you know anything about shoe sizes please contact me. # ########################################################################### +########################################################################### +# +# Brief Philosophy of this file +# +# Most unit definitions are made in terms of integers or simple fractions of +# other definitions. The typical exceptions are when converting between two +# different unit systems, or the values of measured physical constants. In +# this file definitions are given in the most natural and revealing way in +# terms of integer factors. +# +# The file is USA-centric, but there is some modest effort to support other +# countries. This file is now coded in UTF-8. To support environments where +# UTF-8 is not available, definitions that require this character set are +# wrapped in !utf8 directives. +# +# When a unit name is used in different countries with the different meanings +# the system should be as follows: +# +# Suppose countries ABC and XYZ both use the "foo". Then globally define +# +# ABCfoo +# XYZfoo +# +# The using the !locale directive, define the "foo" appropriately for each of +# the two countries with a definition like +# +# !locale ABC +# foo ABCfoo +# !endlocale +# +# If you make changes be sure to run 'units --check' to check your work. +# +########################################################################### + ########################################################################### # # # Primitive units. Any unit defined to contain a '!' character is a # @@ -128,7 +163,7 @@ radian !dimensionless # The angle subtended at the center of a circle by # circle sr !dimensionless # Solid angle which cuts off an area of the surface steradian sr # of the sphere equal to that of a square with - # sides of length equal to the radius of the + # sides of length equal to the radius of the # sphere # @@ -179,7 +214,7 @@ demi- 0.5 hemi- 0.5 half- 0.5 double- 2 -triple- 3 +triple- 3 treble- 3 kibi- 2^10 # In response to the convention of illegally @@ -451,15 +486,17 @@ funal sthene pieze sthene / m^2 quintal 100 kg bar 1e5 Pa # About 1 atm +b bar vac millibar micron micrometer # One millionth of a meter bicron picometer # One brbillionth of a meter cc cm^3 are 100 m^2 +a are liter 1000 cc # The liter was defined in 1901 as the oldliter 1.000028 dm^3 # space occupied by 1 kg of pure water at -l liter # the temperature of its maximum density -L liter # under a pressure of 1 atm. This was +L liter # the temperature of its maximum density +l liter # under a pressure of 1 atm. This was # supposed to be 1000 cubic cm, but it # was discovered that the original # measurement was off. In 1964, the @@ -584,7 +621,7 @@ mas milli arcsec # Used by astronomers seclongitude circle (seconds/day) # Astronomers measure longitude # (which they call right ascension) in # time units by dividing the equator into - # 24 hours instead of 360 degrees. + # 24 hours instead of 360 degrees. # # Some geometric formulas # @@ -708,7 +745,7 @@ degreaumur 10|8 degC # The Reaumur scale was used in Europe and # divisible by many numbers. degK K # "Degrees Kelvin" is forbidden usage. -tempK K # For consistency. +tempK K # For consistency # Gas mark is implemented below but in a terribly ugly way. There is # a simple formula, but it requires a conditional which is not @@ -745,7 +782,7 @@ gasmark[degR] \ # air temperature and wind on the human body. The index was first defined # by the American Antarctic explorer Paul Siple in 1939. As currently used # by U.S. meteorologists, the wind chill index is computed from the -# temperature T (in F) and wind speed V (in mi/hr) using the formula: +# temperature T (in °F) and wind speed V (in mi/hr) using the formula: # WCI = 0.0817(3.71 sqrt(V) + 5.81 - 0.25V)(T - 91.4) + 91.4. # For very low wind speeds, below 4 mi/hr, the WCI is actually higher than # the air temperature, but for higher wind speeds it is lower than the air @@ -753,7 +790,7 @@ gasmark[degR] \ # # heat index (HI or HX) a measure of the combined effect of heat and # humidity on the human body. U.S. meteorologists compute the index -# from the temperature T (in F) and the relative humidity H (as a +# from the temperature T (in °F) and the relative humidity H (as a # value from 0 to 1). # HI = -42.379 + 2.04901523 T + 1014.333127 H - 22.475541 TH # - .00683783 T^2 - 548.1717 H^2 + 0.122874 T^2 H + 8.5282 T H^2 @@ -776,14 +813,9 @@ h 6.62606896e-34 J s # Planck constant hbar h / 2 pi spin hbar G 6.67428e-11 N m^2 / kg^2 # Newtonian gravitational constant - # This is the NIST 2002 value. - # Note that NIST increased the - # uncertainty of G to 1500 ppm - # as a result of disagreements - # between experiments performed in - # the late 1990s. Some other - # sources give conflicting values - # with a much lower uncertainty. + # This is the NIST 2006 value. + # The relative uncertainty on this + # is 1e-4. coulombconst 1/4 pi epsilon0 # listed as "k" sometimes # Physico-chemical constants @@ -866,6 +898,8 @@ wc water # water column mach 331.46 m/s # speed of sound in dry air at STP standardtemp 273.15 K # standard temperature stdtemp standardtemp +normaltemp tempF(70) # for gas density, from NIST +normtemp normaltemp # Handbook 44 # Weight of mercury and water at different temperatures using the standard # force of gravity. @@ -1219,11 +1253,11 @@ moonlum 2500 cd/m^2 # Photographic Exposure Value # -# The Additive Photographic EXposure (APEX) system developed in Germany in -# the 1960s was an attempt to simplify exposure determination for people -# who relied on exposure tables rather than exposure meters. Shortly -# thereafter, nearly all cameras incorporated exposure meters, so the APEX -# system never caught on, but the concept of Exposure Value (EV) given by +# The Additive Photographic EXposure (APEX) system proposed in ASA PH2.5-1960 +# was an attempt to simplify exposure determination for people who relied on +# exposure tables rather than exposure meters. Shortly thereafter, nearly all +# cameras incorporated exposure meters, so the APEX system never caught on, +# but the concept of Exposure Value (EV) given by # # A^2 LS ES # 2^EV = --- = -- = -- @@ -1246,15 +1280,25 @@ moonlum 2500 cd/m^2 # are in even 1/3-step increments, the exact value is 64 * 2^(2|3)). # Calibration constants vary among camera and meter manufacturers: Canon, # Nikon, and Sekonic use a value of 12.5 for reflected-light meters, while -# Minolta and Pentax use a value of 14. Minolta and Sekonic use a value -# of 250 for incident-light meters with flat receptors. +# Kenko (formerly Minolta) and Pentax use a value of 14. Kenko and +# Sekonic use a value of 250 for incident-light meters with flat +# receptors. + +# This was stated in ASA PH2.5-1960, but it assumed APEX, which never +# found widespread acceptance. + +#s100 64 * 2^(2|3) / lx s # exact speed for ISO 100 film -s100 64 * 2^(2|3) / lx s # exact speed for ISO 100 film +# ISO speed standards (e.g., ISO 6:1993) do not discuss "exact" values; +# this value assumes ISO 100 is exact. + +s100 100 / lx s # ISO 100 speed +iso100 s100 -# Reflected-light meter calibration constant with ISO 100 film +# Reflected-light meter calibration constant with ISO 100 speed k1250 12.5 (cd/m2) / lx s # For Canon, Nikon, and Sekonic -k1400 14 (cd/m2) / lx s # For Minolta and Pentax +k1400 14 (cd/m2) / lx s # For Kenko (Minolta) and Pentax # Incident-light meter calibration constant with ISO 100 film @@ -1262,9 +1306,9 @@ c250 250 lx / lx s # flat-disc receptor # Exposure value to scene luminance with ISO 100 film -# For Minolta or Pentax +# For Kenko (Minolta) or Pentax #ev100(x) [;cd/m^2] 2^x k1400 / s100; log2(ev100 s100 / k1400) -# For Canon, Nikon or Sekonic +# For Canon, Nikon, or Sekonic ev100(x) [;cd/m^2] 2^x k1250 / s100; log2(ev100 s100 / k1250) # Exposure value to scene illuminance with ISO 100 film @@ -1570,7 +1614,6 @@ atomictime hbar^3/coulombconst^2 atomicmass e^4 # Period of first atomicvelocity atomiclength / atomictime atomicenergy hbar / atomictime hartree atomicenergy -Hartree hartree # # These thermal units treat entropy as charge, from [5] @@ -1591,17 +1634,28 @@ thermalvolt K # thermal potential difference # linear measure -# The US Metric Law of 1866 legalized the metric system in the USA and defined -# the meter in terms of the British system with the exact 1 meter = 39.37 -# inches. On April 5, 1893 Corwin Mendenhall decided, in what has become known -# as the "Mendenhall Order" that the meter and kilogram would be the -# fundamental standards in the USA. The definition from 1866 was turned around -# to give an exact definition of the foot as 1200|3937 meters. This definition -# was used until July of 1959 when the definition was changed to bring the US -# into agreement with other countries. Since 1959, the foot has been exactly -# 0.3048 meters. At the same time it was decided that any data expressed in -# feet derived from geodetic surveys within the US would continue to use the -# old definition and call the old unit the "survey foot". +# The US Metric Law of 1866 legalized the metric system in the USA and +# defined the meter in terms of the British system with the exact +# 1 meter = 39.37 inches. On April 5, 1893 Thomas Corwin Mendenhall, +# Superintendent of Weights and Measures, decided, in what has become +# known as the "Mendenhall Order" that the meter and kilogram would be the +# fundamental standards in the USA. The definition from 1866 was turned +# around to give an exact definition of the yard as 3600|3937 meters This +# definition was used until July of 1959 when the definition was changed +# to bring the US and other English-speaking countries into agreement; the +# Canadian value of 1 yard = 0.9144 meter (exactly) was chosen because it +# was approximately halfway between the British and US values; it had the +# added advantage of making 1 inch = 25.4 mm (exactly). Since 1959, the +# "international" foot has been exactly 0.3048 meters. At the same time, +# it was decided that any data expressed in feet derived from geodetic +# surveys within the US would continue to use the old definition and call +# the old unit the "survey foot." The US continues to define the statute +# mile, furlong, chain, rod, link, and fathom in terms of the US survey +# foot. +# Sources: +# NIST Special Publication 447, Sects. 5, 7, and 8. +# NIST Handbook 44, 2011 ed., Appendix C. +# Canadian Journal of Physics, 1959, 37:(1) 84, 10.1139/p59-014. US 1200|3937 m/ft # These four values will convert US- US # international measures to @@ -1658,8 +1712,23 @@ gurleylink 1|50 gurleychain # same length wingchain 66 feet # Chain from 1664, introduced by winglink 1|80 wingchain # Vincent Wing, also found in a # 33 foot length with 40 links. - - +# early US length standards + +# The US has had four standards for the yard: one by Troughton of London +# (1815); bronze yard #11 (1856); the Mendhall yard (1893), consistent +# with the definition of the meter in the metric joint resolution of +# Congress in 1866, but defining the yard in terms of the meter; and the +# international yard (1959), which standardized definitions for Australia, +# Canada, New Zealand, South Africa, the UK, and the US. +# Sources: Pat Naughtin (2009), Which Inch?, www.metricationmatters.com; +# Lewis E. Barbrow and Lewis V. Judson (1976). NBS Special Publication +# 447, Weights and Measures Standards of the United States: A Brief +# History. + +troughtonyard 914.42190 mm +bronzeyard11 914.39980 mm +mendenhallyard surveyyard +internationalyard yard # nautical measure @@ -2189,6 +2258,11 @@ austblsp australiatablespoon australiateaspoon 1|4 australiatablespoon austsp australiateaspoon +# Italian + +etto 100 g # Used for buying items like meat and +etti etto # cheese. + # Chinese catty 0.5 kg @@ -2296,13 +2370,56 @@ eggyolkvolume 3.5 ustsp # 171 99.6 # +# +# This is from "Boiling point elevation of technical sugarcane solutions and +# its use in automatic pan boiling" by Michael Saska. International Sugar +# Journal, 2002, 104, 1247, pp 500-507. +# +# But I'm using equation (3) which is credited to Starzak and Peacock, +# "Water activity coefficient in aqueous solutions of sucrose--A comprehensive +# data analyzis. Zuckerindustrie, 122, 380-387. +# +# Note that the range of validity is uncertain, but answers are in agreement +# with the above table all the way to 99.6. +# +# It is probably necessary to switch this to a table in order to get +# invertibility. +# + +sc(x) (x / 342.3) / (( x/342.3) + (100-x)/18.02); \ + 100 sc 342.3|18.02 / (sc (342.3|18.02-1)+1) + +sss(Wds) Ax (Wds/(100-Wds))^Bx ((stdtemp+tbw)/100 K)^Cx + +Ax 0.1660 +Bx 1.1394 +Cx 1.9735 + + +Bsp 3797.06 degC +Csp 226.28 degC +tbw 100 degC +QQ -17638 J/mol +asp -1.0038 +bsp -0.24653 + + +sug(x) ((1-QQ/R Bsp * x^2 (1+asp x + bsp x^2) (tbw + Csp) /(tbw+stdtemp)) / (1+(tbw + Csp)/Bsp *ln(1-x))-1) * (tbw + Csp) + +# Boiling point elevation of sugar, x is concentration +sugar_bpe(x) ((1+ 0.48851085 * sc(x)^2 (1+ -1.0038 sc(x) + -0.24653 sc(x)^2)) / (1+0.08592964 ln(1-sc(x)))-1) 326.28 K + + + + + # Degrees Baume is used in European recipes to specify the density of a sugar # syrup. An entirely different definition is used for densities below # 1 g/cm^3. An arbitrary constant appears in the definition. This value is # equal to 145 in the US, but was according to [], the old scale used in # Holland had a value of 144, and the new scale or Gerlach scale used 146.78. -baumeconst 144 # US value +baumeconst 145 # US value baume(d) [1;g/cm^3] (baumeconst/(baumeconst+-d)) g/cm^3 ; \ (baume+((-g)/cm^3)) baumeconst / baume @@ -2314,30 +2431,31 @@ quevenne(x) [1;g/cm^3] (1 + 0.001 x) g / cm^3 ; 1000 (quevenne / (g/cm^3) +- 1) # Degrees brix measures sugar concentration by weigh as a percentage, so a # solution that is 3 degrees brix is 3% sugar by weight. This unit was named # after Adolf Brix who invented a hydrometer that read this percentage -# directly. This table converts brix to density at 20 degrees Celsius. - -brix[g/cm^3] \ - 0.0 0.9982, 0.5 1.0002, 1.0 1.0021 \ - 1.5 1.0040, 2.0 1.0060, 2.5 1.0079 \ - 3.0 1.0099, 3.5 1.0119, 4.0 1.0139 \ - 5.0 1.0178, 5.5 1.0198, 6.0 1.0218 \ - 6.5 1.0238, 7.0 1.0259, 7.5 1.0279 \ - 8.0 1.0299, 8.5 1.0320, 9.0 1.0340 \ - 9.5 1.0361, 10.0 1.0381, 11.0 1.0423 \ - 12.0 1.0465, 13.0 1.0507, 14.0 1.0549 \ - 15.0 1.0592, 16.0 1.0635, 17.0 1.0678 \ - 18.0 1.0722, 19.0 1.0766, 20.0 1.0810 \ - 22.0 1.0899, 24.0 1.0990, 26.0 1.1082 \ - 28.0 1.1175, 30.0 1.1270, 32.0 1.1366 \ - 34.0 1.1464, 36.0 1.1562, 38.0 1.1663 \ - 40.0 1.1765, 42.0 1.1868, 44.0 1.1972 \ - 46.0 1.2079, 48.0 1.2186, 50.0 1.2295 \ - 52.0 1.2406, 54.0 1.2518, 56.0 1.2632 \ - 58.0 1.2747, 60.0 1.2864, 62.0 1.2983 \ - 64.0 1.3103, 66.0 1.3224, 68.0 1.3348 \ - 70.0 1.3472, 72.0 1.3599, 74.0 1.3726 \ - 76.0 1.3855, 78.0 1.3986, 80.0 1.4117 \ - 82.0 1.4250, 84.0 1.4383 +# directly. This data is from Table 114 of NIST Circular 440, "Polarimetry, +# Saccharimetry and the Sugars". It gives apparent specific gravity at 20 +# degrees Celsius of various sugar concentrations. As rendered below this +# data is converted to apparent density at 20 degrees Celsius using the +# density figure for water given in the same NIST reference. They use the +# word "apparent" to refer to measurements being made in air with brass +# weights rather than vacuum. + +brix[0.99717g/cm^3]\ + 0 1.00000 1 1.00390 2 1.00780 3 1.01173 4 1.01569 5 1.01968 \ + 6 1.02369 7 1.02773 8 1.03180 9 1.03590 10 1.04003 11 1.04418 \ + 12 1.04837 13 1.05259 14 1.05683 15 1.06111 16 1.06542 17 1.06976 \ + 18 1.07413 19 1.07853 20 1.08297 21 1.08744 22 1.09194 23 1.09647 \ + 24 1.10104 25 1.10564 26 1.11027 27 1.11493 28 1.11963 29 1.12436 \ + 30 1.12913 31 1.13394 32 1.13877 33 1.14364 34 1.14855 35 1.15350 \ + 36 1.15847 37 1.16349 38 1.16853 39 1.17362 40 1.17874 41 1.18390 \ + 42 1.18910 43 1.19434 44 1.19961 45 1.20491 46 1.21026 47 1.21564 \ + 48 1.22106 49 1.22652 50 1.23202 51 1.23756 52 1.24313 53 1.24874 \ + 54 1.25439 55 1.26007 56 1.26580 57 1.27156 58 1.27736 59 1.28320 \ + 60 1.28909 61 1.29498 62 1.30093 63 1.30694 64 1.31297 65 1.31905 \ + 66 1.32516 67 1.33129 68 1.33748 69 1.34371 70 1.34997 71 1.35627 \ + 72 1.36261 73 1.36900 74 1.37541 75 1.38187 76 1.38835 77 1.39489 \ + 78 1.40146 79 1.40806 80 1.41471 81 1.42138 82 1.42810 83 1.43486 \ + 84 1.44165 85 1.44848 86 1.45535 87 1.46225 88 1.46919 89 1.47616 \ + 90 1.48317 91 1.49022 92 1.49730 93 1.50442 94 1.51157 95 1.51876 # Density measure invented by the American Petroleum Institute. Lighter # petroleum products are more valuable, and they get a higher API degree. @@ -2373,6 +2491,8 @@ kip 1000 lbf # from kilopound ksi kip / in^2 mil 0.001 inch thou 0.001 inch +tenth 0.0001 inch # one tenth of one thousandth of an inch +millionth 1e-6 inch # one millionth of an inch circularinch 1|4 pi in^2 # area of a one-inch diameter circle circleinch circularinch # A circle with diameter d inches has # an area of d^2 circularinches @@ -2832,7 +2952,7 @@ fournierpoint 0.1648 inch / 12 # First definition of the printers # point made by Pierre Fournier who # defined it in 1737 as 1|12 of a # cicero which was 0.1648 inches. -olddidotpoint 1|72 frenchinch # Franois Ambroise Didot, one of +olddidotpoint 1|72 frenchinch # François Ambroise Didot, one of # a family of printers, changed # Fournier's definition around 1770 # to fit to the French units then in @@ -3156,6 +3276,7 @@ megalerg megaerg # 'L' added to make it pronounceable [18]. # $ dollar +usdollar dollar mark germanymark bolivar venezuelabolivar bolivarfuerte bolivar # The currency was revalued by @@ -3320,7 +3441,7 @@ saudiarabiariyal 0.2667 US$ solomonislandsdollar 0.1273 US$ seychellesrupee 0.08753 US$ sudanpound 0.4464 US$ -swedenkronor 13.719 US$ +swedenkrona 13.719 US$ singaporedollar 0.7065 US$ sainthelenapound 1.5627 US$ sierraleoneleone 0.000255 US$ @@ -3480,7 +3601,7 @@ SAR saudiarabiariyal SBD solomonislandsdollar SCR seychellesrupee SDG sudanpound -SEK swedenkronor +SEK swedenkrona SGD singaporedollar SHP sainthelenapound SLL sierraleoneleone @@ -3849,7 +3970,7 @@ screwgauge(g) [;m] (.06 + .013 g) in ; (screwgauge/in + (-.06)) / .013 # several previous organizations. One of the old organizations was # CAMI (Coated Abrasives Manufacturers' Institute). # -# UAMA has a web page with plots showing abrasve particle ranges for +# UAMA has a web page with plots showing abrasive particle ranges for # various different grits and comparisons between standards. # # http://www.uama.org/Abrasives101/101Standards.html @@ -3910,6 +4031,9 @@ grit_P[micron] \ 2000 10.3 \ 2500 8.4 +# The F grit is the European standard for bonded abrasives such as +# grinding wheels + grit_F[micron] \ 4 4890 \ 5 4125 \ @@ -3957,7 +4081,7 @@ grit_F[micron] \ # from the UAMA web site and represent the average of the "d50" range # endpoints listed there. -grit_ansibonded[micron] \ +ansibonded[micron] \ 4 4890 \ 5 4125 \ 6 3460 \ @@ -3995,6 +4119,8 @@ grit_ansibonded[micron] \ 1000 5.8 \ 1200 3.8 +grit_ansibonded(x) [1;micron] ansibonded(x); ~ansibonded(grit_ansibonded) + # Like the bonded grit, the coated macrogrits below 240 are taken from the # FEPA F table. Data above this is from the UAMA site. Note that the coated # and bonded standards are evidently the same from 240 up to 600 grit, but @@ -4046,6 +4172,9 @@ ansicoated[micron] \ 6000 2 \ 8000 1.2 +grit_ansicoated(x) [1;micron] ansicoated(x); ~ansicoated(grit_ansicoated) + + # # Is this correct? This is the JIS Japanese standard used on waterstones # @@ -4429,6 +4558,22 @@ yttrium 88.90585 zinc 65.39 zirconium 91.224 +# from NASA Earth Fact Sheet (accessed 4 November 2011) +# http://nssdc.gsfc.nasa.gov/planetary/factsheet/earthfact.html +# Atmospheric composition: +# Nitrogen (N2) 78.08% +# Oxygen (O2) 20.95% +# Argon (Ar) 9340 ppm +# Carbon Dioxide (CO2) 380 ppm +# Neon (Ne) 18.18 ppm +# Helium (He) 5.24 ppm +# Methane (CH4) 1.7 ppm +# Krypton (Kr) 1.14 ppm +# Hydrogen (H2) 0.55 ppm + +air 28.967 + + # # population units # @@ -4489,6 +4634,14 @@ kanejakusun sun # Alias to emphasize architectural name kanejaku shaku kanejakujou jou +# http://en.wikipedia.org/wiki/Taiwanese_units_of_measurement +taichi shaku # http://zh.wikipedia.org/wiki/台尺 +taicun sun # http://zh.wikipedia.org/wiki/台制 +!utf8 +台尺 taichi # via Hanyu Pinyin romanizations +台寸 taicun +!endutf8 + # In context of clothing, shaku is different from architecture # http://www.scinet.co.jp/sci/sanwa/kakizaki-essay54.html @@ -4517,6 +4670,18 @@ se 30 tsubo tan_area 10 se chou_area 10 tan_area +# http://en.wikipedia.org/wiki/Taiwanese_units_of_measurement +ping tsubo # http://zh.wikipedia.org/wiki/坪 +jia 2934 ping # http://zh.wikipedia.org/wiki/甲_(单位) +fen 1|10 jia # http://zh.wikipedia.org/wiki/分 +fen_area 1|10 jia # Protection against future collisions +!utf8 +坪 ping # via Hanyu Pinyin romanizations +甲 jia +分 fen +分地 fen_area # Protection against future collisions +!endutf8 + # Japanese architecture is based on a "standard" size of tatami mat. # Room sizes today are given in number of tatami, and this number # determines the spacing between colums and hence sizes of sliding @@ -4559,10 +4724,10 @@ koku 10 to # No longer used; historically a measure of rice # Not really used anymore. -rin_weight 1|10 bu +rin_weight 1|10 bu_weight bu_weight 1|10 monme fun 1|10 monme -monme 15|4 g +monme momme kin 160 monme kan 1000 monme kwan kan # This was the old pronounciation of the unit. @@ -4570,6 +4735,17 @@ kwan kan # This was the old pronounciation of the unit. # longer and was not changed until around # 1950. +# http://en.wikipedia.org/wiki/Taiwanese_units_of_measurement +# says: "Volume measure in Taiwan is largely metric". +taijin kin # http://zh.wikipedia.org/wiki/台斤 +tailiang 10 monme # http://zh.wikipedia.org/wiki/台斤 +taiqian monme # http://zh.wikipedia.org/wiki/台制 +!utf8 +台斤 taijin # via Hanyu Pinyin romanizations +台兩 tailiang +台錢 taiqian +!endutf8 + # # Australian unit # @@ -5079,30 +5255,10 @@ periot 1|20 droit blanc 1|24 periot # -# Some definitions using ISO 8859-1 characters +# Localization # -- 1|4 -- 1|2 -- 3|4 -- micro - cent - britainpound - japanyen -ngstrm angstrom - angstrom -rntgen roentgen -C degC -F degF -K K # K is incorrect notation -R degR - degree - -# -# Localisation -# - -!locale en_US +# !locale en_US hundredweight ushundredweight ton uston scruple apscruple @@ -5124,7 +5280,7 @@ firkin usfirkin hogshead ushogshead acre usacre acrefoot usacrefoot -!endlocale +# !endlocale !locale en_GB hundredweight brhundredweight @@ -5152,6 +5308,192 @@ acre intacre acrefoot intacrefoot !endlocale +!utf8 +⅛- 1|8 +¼- 1|4 +⅜- 3|8 +½- 1|2 +⅝- 5|8 +¾- 3|4 +⅞- 7|8 +⅙- 1|6 +⅓- 1|3 +⅔- 2|3 +⅚- 5|6 +⅕- 1|5 +⅖- 2|5 +⅗- 3|5 +⅘- 4|5 +# U+2150- 1|7 For some reason these characters are getting +# U+2151- 1|9 flagged as invalid UTF8. +# U+2152- 1|10 +ℯ exp(1) # U+212F, base of natural log + +µ- micro # micro sign U+00B5 +μ- micro # small mu U+03BC +ångström angstrom +Å angstrom # angstrom symbol U+212B +Å angstrom # A with ring U+00C5 +röntgen roentgen +°C degC +°F degF +°K K # °K is incorrect notation +°R degR +° degree +℃ degC +℉ degF +K K # Kelvin symbol, U+212A +ℓ liter # unofficial abbreviation used in some places +¢ cent +£ britainpound +¥ japanyen +€ euro +₩ southkoreawon +₪ israelnewshekel +₤ lira +₨ rupee +₹ rupee + +Ω ohm # Ohm symbol U+2126 +Ω ohm # Greek capital omega U+03A9 +℧ mho +ʒ dram # U+0292 +℈ scruple +℥ ounce +℔ lb +ℎ h +ℏ hbar +‰ 1|1000 +‱ 1|10000 +′ ' # U+2032 +″ " # U+2033 + +# +# Square unicode symbols starting at U+3371 +# + +㍱ hPa +㍲ da +㍳ au +㍴ bar +# ㍵ oV??? +㍶ pc +㍷ dm +㍸ dm^2 +㍹ dm^3 +㎀ pA +㎁ nA +㎂ µA +㎃ mA +㎄ kA +㎅ kB +㎆ MB +㎇ GB +㎈ cal +㎉ kcal +㎊ pF +㎋ nF +㎌ µF +㎍ µg +㎎ mg +㎏ kg +㎐ Hz +㎑ kHz +㎒ MHz +㎓ GHz +㎔ THz +㎕ µL +㎖ mL +㎗ dL +㎘ kL +㎙ fm +㎚ nm +㎛ µm +㎜ mm +㎝ cm +㎞ km +㎟ mm^2 +㎠ cm^2 +㎡ m^2 +㎢ km^2 +㎣ mm^3 +㎤ cm^3 +㎥ m^3 +㎦ km^3 +㎧ m/s +㎨ m/s^2 +㎩ Pa +㎪ kPa +㎫ MPa +㎬ GPa +㎭ rad +㎮ rad/s +㎯ rad/s^2 +㎰ ps +㎱ ns +㎲ µs +㎳ ms +㎴ pV +㎵ nV +㎶ µV +㎷ mV +㎸ kV +㎹ MV +㎺ pW +㎻ nW +㎼ µW +㎽ mW +㎾ kW +㎿ MW +㏀ kΩ +㏁ MΩ +㏃ Bq +㏄ cc +㏅ cd +㏆ C/kg +㏈(x) [1;1] dB(x); ~dB(㏈) +㏉ Gy +㏊ ha +# ㏋ HP?? +㏌ in +# ㏍ KK?? +# ㏎ KM??? +㏏ kt +㏐ lm +# ㏑ ln +# ㏒ log +㏓ lx +㏔ mb +㏕ mil +㏖ mol +㏗(x) [;mol/liter] pH(x); ~pH(㏗) +㏙ ppm +# ㏚ PR??? +㏛ sr +㏜ Sv +㏝ Wb +㏞ V/m +㏟ A/m +㏿ gal + +!endutf8 + +############################################################################ +# +# Unit list aliases +# +# These provide a shorthand for conversions to unit lists. +# +############################################################################ + +!unitlist hms hr;min;sec +!unitlist time year;day;hr;min;sec +!unitlist dms deg;arcmin;arcsec +!unitlist ftin ft;in;1|8 in +!unitlist usvol cup;3|4 cup;2|3 cup;1|2 cup;1|3 cup;1|4 cup;\ + tbsp;tsp;1|2 tsp;1|4 tsp;1|8 tsp +!unitlist lsd poundsterling;shilling;oldpence;farthing + ############################################################################ # # The following units were in the unix units database but do not appear in @@ -5174,7 +5516,3 @@ acrefoot intacrefoot # a single channel occupied for one hour. # ############################################################################ - - - - diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 00000000..09e1c2d3 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/src/com/android/calculator2/ColorButton.java b/app/src/main/java/com/android/calculator2/ColorButton.java similarity index 57% rename from src/com/android/calculator2/ColorButton.java rename to app/src/main/java/com/android/calculator2/ColorButton.java index ba69f253..70a5ea0f 100644 --- a/src/com/android/calculator2/ColorButton.java +++ b/app/src/main/java/com/android/calculator2/ColorButton.java @@ -16,10 +16,9 @@ package com.android.calculator2; -import info.staticfree.android.units.R; -import info.staticfree.android.units.Units; import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Align; @@ -32,16 +31,32 @@ import android.view.View.OnClickListener; import android.widget.Button; +import de.buttercookie.units.R; + /** * Button with click-animation effect. */ class ColorButton extends Button implements OnClickListener { + + private static final int PADDING_DP = 8; + + private static final int LONGPRESS_MINIMUM_TEXT_SIZE_SP = 6; + private static final int LONGPRESS_PADDING_DP = 3; + private static final int LONGPRESS_MAIN_TEXT_MARGIN_DP = 1; + + private static final int MAX_ALPHA = 64; + private int CLICK_FEEDBACK_COLOR; private static final int CLICK_FEEDBACK_INTERVAL = 10; private static final int CLICK_FEEDBACK_DURATION = 350; private float mTextX; private float mTextY; + private float mOriginalTextSize; + private int mPadding; + private int mLongpressMinimumTextSize; + private int mLongpressPadding; + private int mLongpressMainTextMargin; private long mAnimStart; private OnClickListener mListener; private Paint mFeedbackPaint; @@ -50,16 +65,22 @@ class ColorButton extends Button implements OnClickListener { private final String mLongpressText; private Paint mLongpressTextPaint; + // TODO: make longpress hints RTL-compatible public ColorButton(Context context, AttributeSet attrs) { super(context, attrs); init(); - if (attrs.getAttributeBooleanValue(Units.XMLNS, "longpressEllipsis", false)){ - final Resources res = getResources(); - mEllipsis = res.getDrawable(R.drawable.button_ellipsis); - } - mLongpressText = attrs.getAttributeValue(Units.XMLNS, "longpressText"); + final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ColorButton); + try { + if (a.getBoolean(R.styleable.ColorButton_longpressEllipsis, false)) { + final Resources res = getResources(); + mEllipsis = res.getDrawable(R.drawable.button_ellipsis); + } + mLongpressText = a.getString(R.styleable.ColorButton_longpressText); + } finally { + a.recycle(); + } } private void init() { @@ -71,13 +92,20 @@ private void init() { mFeedbackPaint.setStrokeWidth(2); final Paint textPaint = getPaint(); textPaint.setColor(res.getColor(R.color.button_text)); + mOriginalTextSize = textPaint.getTextSize(); mLongpressTextPaint = new Paint(textPaint); mLongpressTextPaint.setAlpha(127); - mAnimStart = -1; - - // + mPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, PADDING_DP, + res.getDisplayMetrics()); + mLongpressMinimumTextSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, + LONGPRESS_MINIMUM_TEXT_SIZE_SP, res.getDisplayMetrics()); + mLongpressPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + LONGPRESS_PADDING_DP, res.getDisplayMetrics()); + mLongpressMainTextMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + LONGPRESS_MAIN_TEXT_MARGIN_DP, res.getDisplayMetrics()); + mAnimStart = -1; } /** @@ -85,12 +113,13 @@ private void init() { * This allows arbitrary text to be placed on the buttons. */ public void adjustFontSizeToFit() { + final int MAX_ITERATIONS = 10; final Paint newPaint = new Paint(getPaint()); float newX = mTextX; - for (int i = 0; newX < 10 && i < 10; i++){ + for (int i = 0; newX < mPadding && i < MAX_ITERATIONS; i++) { - newPaint.setTextSize(newPaint.getTextSize() * 0.9f); - newX = (getWidth() - newPaint.measureText(getText().toString())) / 2; + newPaint.setTextSize(newPaint.getTextSize() * 0.9f); + newX = (getWidth() - newPaint.measureText(getText().toString())) / 2; } setTextSize(TypedValue.COMPLEX_UNIT_PX, newPaint.getTextSize()); @@ -104,31 +133,43 @@ public void onClick(View view) { @Override public void onSizeChanged(int w, int h, int oldW, int oldH) { - measureText(); + measureText(false); adjustFontSizeToFit(); - measureText(); + measureText(true); } - private void measureText() { + private void measureText(boolean measureLongpressText) { final Paint paint = getPaint(); - mTextX = (getWidth() - paint.measureText(getText().toString())) / 2; + final int buttonWidth = getWidth(); + mTextX = (buttonWidth - paint.measureText(getText().toString())) / 2; mTextY = (getHeight() - paint.ascent() - paint.descent()) / 2; - if (mLongpressText != null){ - mLongpressTextPaint.measureText(mLongpressText); - final float textSize = (getHeight() - paint.getTextSize()) / 2 - 4; - mLongpressTextPaint.setTextAlign(Align.RIGHT); + if (mLongpressText != null && measureLongpressText) { + final float mainButtonTextSize = paint.getTextSize(); + final int availWidth = buttonWidth - 2 * mPadding; + final float curTextSize = mLongpressTextPaint.getTextSize(); + final float curTextWidth = mLongpressTextPaint.measureText(mLongpressText); + final float textSizeByWidth = curTextSize * availWidth / curTextWidth; + + final float textSizeByHeight = + (getHeight() - mainButtonTextSize) / 2 + - mLongpressPadding + - mLongpressMainTextMargin; + mLongpressTextPaint.setTextAlign(Align.RIGHT); + + float textSize = Math.min(textSizeByWidth, textSizeByHeight); + textSize = Math.min(textSize, mOriginalTextSize); + textSize = Math.max(textSize, mLongpressMinimumTextSize); mLongpressTextPaint.setTextSize(textSize); } } @Override protected void onTextChanged(CharSequence text, int start, int before, int after) { - measureText(); + measureText(true); } - private static final int MAX_ALPHA = 64; private void drawMagicFlame(int duration, Canvas canvas) { final int alpha = MAX_ALPHA - MAX_ALPHA * duration / CLICK_FEEDBACK_DURATION; final int color = CLICK_FEEDBACK_COLOR | (alpha << 24); @@ -137,14 +178,13 @@ private void drawMagicFlame(int duration, Canvas canvas) { canvas.drawRect(1, 1, getWidth() - 1, getHeight() - 1, mFeedbackPaint); } - private static final int PADDING = 10; @Override public void onDraw(Canvas canvas) { - if (mEllipsis != null){ - mEllipsis.setBounds(canvas.getClipBounds()); - mEllipsis.draw(canvas); - } + if (mEllipsis != null) { + mEllipsis.setBounds(canvas.getClipBounds()); + mEllipsis.draw(canvas); + } if (mAnimStart != -1) { final int animDuration = (int) (System.currentTimeMillis() - mAnimStart); @@ -162,9 +202,10 @@ public void onDraw(Canvas canvas) { final CharSequence text = getText(); canvas.drawText(text, 0, text.length(), mTextX, mTextY, getPaint()); - if (mLongpressText != null){ + if (mLongpressText != null) { - canvas.drawText(mLongpressText, getWidth() - PADDING, -mLongpressTextPaint.ascent() + PADDING, mLongpressTextPaint); + canvas.drawText(mLongpressText, getWidth() - mLongpressPadding, + -mLongpressTextPaint.ascent() + mLongpressPadding, mLongpressTextPaint); } } @@ -182,7 +223,7 @@ public boolean onTouchEvent(MotionEvent event) { animateClickFeedback(); break; case MotionEvent.ACTION_DOWN: - setPressed(true); // not sure why this is needed here + setPressed(true); // not sure why this is needed here case MotionEvent.ACTION_CANCEL: diff --git a/app/src/main/java/de/buttercookie/units/Application.java b/app/src/main/java/de/buttercookie/units/Application.java new file mode 100644 index 00000000..e2ee3e7e --- /dev/null +++ b/app/src/main/java/de/buttercookie/units/Application.java @@ -0,0 +1,163 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.buttercookie.units; + +import static de.buttercookie.units.SharedPrefs.PREF_LAST_CLASSIFICATION_LOCALE; +import static de.buttercookie.units.SharedPrefs.PREF_LAST_CLASSIFICATION_VERSION_CODE; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.os.Build; +import android.os.StrictMode; +import android.util.Log; + +import androidx.annotation.NonNull; + +import net.sourceforge.unitsinjava.Env; +import net.sourceforge.unitsinjava.Tables; +import net.sourceforge.unitsinjava.UnitsFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; +import java.util.Vector; + +public class Application extends android.app.Application { + private final static String TAG = "Units"; + private final static String UIJ_TAG = "UnitsCore"; + + private boolean mUnitsLocaleUpdateRequired = false; + + @Override + public void onCreate() { + if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { + /*StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyLog() + //.penaltyDeath() + .build()); + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() + .detectAll() + .penaltyLog() + //.penaltyDeath() + .build());*/ + } + + super.onCreate(); + initUnits(); + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + if (localeChanged(this)) { + mUnitsLocaleUpdateRequired = true; + } + + super.onConfigurationChanged(newConfig); + } + + private void initUnits() { + Env.filenames = new Vector<>(); + Env.filenames.add("units.dat"); + + Env.locale = getCurrentLocale(this); + Env.quiet = true; + Env.oneline = true; + + + Env.out = new Env.Writer() { + @Override + public void print(String s) { + Log.i(UIJ_TAG, s); + + } + + @Override + public void println(String s) { + Log.i(UIJ_TAG, s); + + } + }; + + UnitsFile.fileAcc = new UnitsFile.FileAcc() { + + @Override + public InputStream open(String name) { + try { + Log.d(TAG, "reading definitions from " + name); + return getAssets().open(name); + } catch (final IOException ioe) { + ioe.printStackTrace(); + } + return null; + } + }; + + Tables.build(); + } + + public void updateUnitsLocaleIfRequired() { + if (!mUnitsLocaleUpdateRequired) { + return; + } + mUnitsLocaleUpdateRequired = false; + + Log.d(TAG, "Re-reading units after locale change"); + Env.locale = getCurrentLocale(this); + Tables.clean(); + Tables.build(); + } + + private static boolean localeChanged(Context context) { + final SharedPreferences prefs = SharedPrefs.getAppPrefs(context); + String storedLocale = prefs.getString(PREF_LAST_CLASSIFICATION_LOCALE, null); + return !getCurrentLocale(context).equals(storedLocale); + } + + static boolean localeAndVersionUnchanged(Context context) { + final SharedPreferences prefs = SharedPrefs.getAppPrefs(context); + String storedLocale = prefs.getString(PREF_LAST_CLASSIFICATION_LOCALE, null); + int storedVersion = prefs.getInt(PREF_LAST_CLASSIFICATION_VERSION_CODE, 0); + + return getCurrentLocale(context).equals(storedLocale) && + BuildConfig.VERSION_CODE == storedVersion; + } + + @SuppressLint("ApplySharedPref") + // apply() not available in old SDK + static void storeLocaleAndVersion(Context context) { + final SharedPreferences.Editor editor = SharedPrefs.getAppPrefs(context).edit(); + editor.putString(PREF_LAST_CLASSIFICATION_LOCALE, getCurrentLocale(context)); + editor.putInt(PREF_LAST_CLASSIFICATION_VERSION_CODE, BuildConfig.VERSION_CODE); + editor.commit(); + } + + public static String getCurrentLocale(Context context) { + final Configuration config = context.getResources().getConfiguration(); + Locale locale; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + locale = config.getLocales().get(0); + } else { + locale = config.locale; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + locale = locale.stripExtensions(); + } + return locale.toString(); + } +} diff --git a/app/src/main/java/de/buttercookie/units/ClassificationEntry.java b/app/src/main/java/de/buttercookie/units/ClassificationEntry.java new file mode 100644 index 00000000..d3be41f8 --- /dev/null +++ b/app/src/main/java/de/buttercookie/units/ClassificationEntry.java @@ -0,0 +1,40 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.buttercookie.units; + +import android.net.Uri; +import android.provider.BaseColumns; + +public class ClassificationEntry implements BaseColumns { + public final static String + _DESCRIPTION = "description", + _FACTOR_FPRINT = "factors"; + + public final static String + PATH = "classification", + PATH_BY_FPRINT = "fprint"; + + public final static String[] + PROJECTION = {_ID, _DESCRIPTION, _FACTOR_FPRINT}; + + + public final static Uri + CONTENT_URI = Uri.parse("content://" + UnitsContentProvider.AUTHORITY + "/" + PATH); + + public static Uri getFprintUri(String fprint) { + return Uri.withAppendedPath(CONTENT_URI, PATH_BY_FPRINT + "/" + fprint); + } +} diff --git a/app/src/main/java/de/buttercookie/units/HistoryAdapter.java b/app/src/main/java/de/buttercookie/units/HistoryAdapter.java new file mode 100644 index 00000000..41e5b44d --- /dev/null +++ b/app/src/main/java/de/buttercookie/units/HistoryAdapter.java @@ -0,0 +1,56 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.buttercookie.units; + +import android.content.Context; +import android.database.Cursor; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.TextView; + +public class HistoryAdapter extends CursorAdapter { + public static final String[] + PROJECTION = {HistoryEntry._ID, + HistoryEntry._HAVE, + HistoryEntry._WANT, + HistoryEntry._RESULT}; + + private final int have_col, want_col, result_col, time_col; + + public HistoryAdapter(Context context, Cursor c) { + super(context, c, true); + have_col = c.getColumnIndex(HistoryEntry._HAVE); + want_col = c.getColumnIndex(HistoryEntry._WANT); + result_col = c.getColumnIndex(HistoryEntry._RESULT); + time_col = c.getColumnIndex(HistoryEntry._WHEN); + + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + + ((TextView) view.findViewById(android.R.id.text1)).setText(HistoryEntry.toCharSequence(cursor, have_col, want_col, result_col)); + + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + final LayoutInflater inflater = LayoutInflater.from(context); + return inflater.inflate(android.R.layout.simple_list_item_1, parent, false); + } +} diff --git a/app/src/main/java/de/buttercookie/units/HistoryEntry.java b/app/src/main/java/de/buttercookie/units/HistoryEntry.java new file mode 100644 index 00000000..a8fb66ad --- /dev/null +++ b/app/src/main/java/de/buttercookie/units/HistoryEntry.java @@ -0,0 +1,54 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.buttercookie.units; + +import android.database.Cursor; +import android.net.Uri; +import android.provider.BaseColumns; + +import net.sourceforge.unitsinjava.Util; + +public class HistoryEntry implements BaseColumns { + public final static String + _HAVE = "have", + _WANT = "want", + _RESULT = "result", + _WHEN = "whenadded"; + + public final static String + PATH = "history"; + + public final static Uri + CONTENT_URI = Uri.parse("content://" + UnitsContentProvider.AUTHORITY + "/" + PATH); + + public final static String SORT_DEFAULT = _ID + " ASC"; + + public static CharSequence toCharSequence(Cursor c, int haveCol, int wantCol, int resultCol) { + return toCharSequence(c.getString(haveCol), c.getString(wantCol), c.isNull(resultCol) ? null : c.getDouble(resultCol)); + } + + public static CharSequence toCharSequence(String have, String want, Double result) { + final StringBuilder historyText = new StringBuilder(); + historyText.append(have); + historyText.append(" = "); + if (result != null) { + historyText.append(Util.shownumber(result)); + historyText.append(' '); + } + historyText.append(want); + return historyText; + } +} diff --git a/app/src/main/java/de/buttercookie/units/SharedPrefs.java b/app/src/main/java/de/buttercookie/units/SharedPrefs.java new file mode 100644 index 00000000..16706998 --- /dev/null +++ b/app/src/main/java/de/buttercookie/units/SharedPrefs.java @@ -0,0 +1,30 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.buttercookie.units; + +import android.content.Context; +import android.content.SharedPreferences; + +public class SharedPrefs { + private static final String APP_PREFS = "Units"; + + public static final String PREF_LAST_CLASSIFICATION_VERSION_CODE = "lastClassificationVersionCode"; + public static final String PREF_LAST_CLASSIFICATION_LOCALE = "lastClassificationLocale"; + + public static SharedPreferences getAppPrefs(Context context) { + return context.getSharedPreferences(APP_PREFS, Context.MODE_PRIVATE); + } +} diff --git a/app/src/main/java/de/buttercookie/units/UnitDetails.java b/app/src/main/java/de/buttercookie/units/UnitDetails.java new file mode 100644 index 00000000..bb85f508 --- /dev/null +++ b/app/src/main/java/de/buttercookie/units/UnitDetails.java @@ -0,0 +1,88 @@ +/* + * UnitDetails.java + * Copyright (C) 2010 Steve Pomeroy + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.buttercookie.units; + +import android.app.Activity; +import android.content.ContentUris; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; + +import de.buttercookie.units.R; + +public class UnitDetails extends Activity { + final String[] usageEntryProjection = {UsageEntry._ID, UsageEntry._UNIT, UsageEntry._FACTOR_FPRINT}; + + @Override + protected void onCreate(Bundle savedInstanceState) { + + setContentView(R.layout.unit_details); + super.onCreate(savedInstanceState); + + final Intent intent = getIntent(); + final String action = intent.getAction(); + + + if (Intent.ACTION_VIEW.equals(action)) { + final Cursor c = managedQuery(intent.getData(), usageEntryProjection, null, null, UsageEntry.SORT_DEFAULT); + if (c.moveToFirst()) { + loadFromCursor(c); + } else { + Log.e("UnitDetails", "Could not load " + intent.getDataString()); + finish(); + } + } + + } + + private void loadFromCursor(Cursor c) { + final String unitName = c.getString(c.getColumnIndex(UsageEntry._UNIT)); + final String factorFprint = c.getString(c.getColumnIndex(UsageEntry._FACTOR_FPRINT)); + + final Cursor classification = managedQuery(ClassificationEntry.getFprintUri(factorFprint), ClassificationEntry.PROJECTION, null, null, null); + if (classification.moveToFirst()) { + final String classificationName = classification.getString(classification.getColumnIndex(ClassificationEntry._DESCRIPTION)); + setTitle(classificationName + ": " + unitName); + } else { + setTitle("Unit: " + unitName); + } + + final String[] conformingSelectionArgs = {factorFprint}; + final Cursor conforming = managedQuery(UsageEntry.CONTENT_URI, usageEntryProjection, UsageEntry._FACTOR_FPRINT + "=?", conformingSelectionArgs, UsageEntry.SORT_DEFAULT); + + final String[] from = {UsageEntry._UNIT}; + final int[] to = {android.R.id.text1}; + final ListView conformable = findViewById(R.id.conformable); + conformable.setAdapter(new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, conforming, from, to)); + conformable.setOnItemClickListener(new OnItemClickListener() { + public void onItemClick(AdapterView arg0, View arg1, int arg2, + long id) { + startActivity(new Intent(UnitDetails.this, UnitDetails.class) + .setAction(Intent.ACTION_VIEW). + setData(ContentUris.withAppendedId(UsageEntry.CONTENT_URI, id))); + } + }); + } +} diff --git a/app/src/main/java/de/buttercookie/units/UnitListActivity.java b/app/src/main/java/de/buttercookie/units/UnitListActivity.java new file mode 100644 index 00000000..5e220843 --- /dev/null +++ b/app/src/main/java/de/buttercookie/units/UnitListActivity.java @@ -0,0 +1,243 @@ +/* + * UnitList.java + * Copyright (C) 2010 Steve Pomeroy + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.buttercookie.units; + +import android.annotation.SuppressLint; +import android.app.ListActivity; +import android.app.SearchManager; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.text.Html; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; +import android.widget.TextView; + +/** + * Displays a list of units, with an optional search query to search a + * substring of all unit names. + * + * @author Steve Pomeroy + */ +public class UnitListActivity extends ListActivity implements OnClickListener { + private SearchHighlightAdapter adapter; + + /** + * Add this to the PICK intent to select from a subset of all units. + * Parameter is a string. + */ + public static final String + EXTRA_UNIT_QUERY = "de.buttercookie.units.EXTRA_UNIT_QUERY"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + setContentView(R.layout.unit_list); + super.onCreate(savedInstanceState); + + getListView().setEmptyView(findViewById(R.id.empty)); + findViewById(R.id.search).setOnClickListener(this); + + loadFromIntent(getIntent()); + + registerForContextMenu(this.getListView()); + + } + + // This exists mostly for the search interaction to allow searching from within this activity. + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + loadFromIntent(intent); + } + + /** + * Load from the given intent. Can be called multiple times in a running activity. + * + * @param intent + */ + private void loadFromIntent(Intent intent) { + final String[] from = {UsageEntry._UNIT, ClassificationEntry._DESCRIPTION}; + final int[] to = {android.R.id.text1, android.R.id.text2}; + final String[] projection = {UsageEntry._ID, UsageEntry._UNIT, ClassificationEntry._DESCRIPTION}; + + final Cursor c; + String query; + + final Uri data = UsageEntry.CONTENT_URI_WITH_CLASSIFICATION; + + final Bundle extras = intent.getExtras(); + if (extras.containsKey(SearchManager.QUERY)) { + query = extras.getString(SearchManager.QUERY); + } else { + query = extras.getString(EXTRA_UNIT_QUERY); + } + if (query != null) { + + query = query.toLowerCase(); + + final String[] selectionArgs = {"%" + query + "%"}; + c = managedQuery(data, projection, UsageEntry._UNIT + " LIKE ?", selectionArgs, UsageEntry.SORT_DEFAULT); + + setTitle(getString(R.string.search_title_unit, query)); + } else { + c = managedQuery(data, projection, null, null, UsageEntry.SORT_DEFAULT); + } + + if (adapter == null) { + adapter = new SearchHighlightAdapter(this, android.R.layout.simple_list_item_2, c, from, to, android.R.id.text1); + adapter.setStringConversionColumn(c.getColumnIndex(UsageEntry._UNIT)); + setListAdapter(adapter); + } else { + stopManagingCursor(adapter.getCursor()); + adapter.changeCursor(c); + } + + adapter.setQuery(query); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + final Cursor c = (Cursor) adapter.getItem(position); + final String unitName = c.getString(c.getColumnIndex(UsageEntry._UNIT)); + + final Intent intent = getIntent(); + if (Intent.ACTION_PICK.equals(intent.getAction())) { + pickUnit(ContentUris.withAppendedId(UsageEntry.CONTENT_URI, id), unitName); + + } else { + viewUnit(ContentUris.withAppendedId(UsageEntry.CONTENT_URI, id), unitName); + } + } + + private void pickUnit(Uri unit, String unitName) { + final Intent pickedUnit = new Intent(); + pickedUnit.setData(unit); + + pickedUnit.putExtra(Units.EXTRA_UNIT_NAME, unitName); + setResult(RESULT_OK, pickedUnit); + finish(); + } + + private void viewUnit(Uri unit, String unitName) { + final Intent viewUnit = new Intent(this, UnitDetails.class) + .setAction(Intent.ACTION_VIEW).setData(unit); + + viewUnit.putExtra(Units.EXTRA_UNIT_NAME, unitName); + startActivity(viewUnit); + } + + private static final int + MENU_PICK_UNIT = 0, + MENU_UNIT_DETAILS = 1; + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + + if (Intent.ACTION_PICK.equals(getIntent().getAction())) { + menu.add(0, MENU_PICK_UNIT, 0, R.string.menu_pick_unit); + } + menu.add(0, MENU_UNIT_DETAILS, 0, R.string.menu_unit_details); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + final AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + final Uri data = ContentUris.withAppendedId(UsageEntry.CONTENT_URI, info.id); + + final Cursor c = (Cursor) adapter.getItem(info.position); + final String unitName = c.getString(c.getColumnIndex(UsageEntry._UNIT)); + + switch (item.getItemId()) { + case MENU_PICK_UNIT: + pickUnit(data, unitName); + return true; + + case MENU_UNIT_DETAILS: + viewUnit(data, unitName); + return true; + + default: + return super.onContextItemSelected(item); + } + } + + /** + * Highlights the query specified by setQuery(). Will look for occurrences in + * TextView specified by setSearchedView() + * + * @author steve + */ + private static class SearchHighlightAdapter extends SimpleCursorAdapter { + private String query; + private final int searchedId; + + /** + * Set the query string to find within the searchedTextView + * + * @param query + */ + public void setQuery(String query) { + this.query = query; + } + + public SearchHighlightAdapter(Context context, int layout, Cursor c, + String[] from, int[] to, int searchedTextView) { + super(context, layout, c, from, to); + this.searchedId = searchedTextView; + } + + + @Override + public void setViewText(TextView v, String text) { + if (query != null) { + final int start = text.toLowerCase().indexOf(query); + if (start >= 0 && v.getId() == searchedId) { + v.setText(Html.fromHtml((start > 0 ? text.substring(0, start) : "") + + "" + text.substring(start, start + query.length()) + "" + + (text.length() - start > query.length() ? text.substring(start + query.length()) : ""))); + } else { + super.setViewText(v, text); + } + } else { + super.setViewText(v, text); + } + } + } + + @SuppressLint("NonConstantResourceId") + public void onClick(View v) { + switch (v.getId()) { + case R.id.search: + onSearchRequested(); + break; + } + } +} diff --git a/app/src/main/java/de/buttercookie/units/UnitUsageDBHelper.java b/app/src/main/java/de/buttercookie/units/UnitUsageDBHelper.java new file mode 100644 index 00000000..9bfa95be --- /dev/null +++ b/app/src/main/java/de/buttercookie/units/UnitUsageDBHelper.java @@ -0,0 +1,678 @@ +/* + * UnitUsageDBHelper.java + * Copyright (C) 2010 Steve Pomeroy + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.buttercookie.units; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.View; +import android.view.View.OnFocusChangeListener; +import android.widget.SimpleCursorAdapter; +import android.widget.TextView; + +import net.sourceforge.unitsinjava.Alias; +import net.sourceforge.unitsinjava.BuiltInFunction; +import net.sourceforge.unitsinjava.DefinedFunction; +import net.sourceforge.unitsinjava.EvalError; +import net.sourceforge.unitsinjava.Unit; +import net.sourceforge.unitsinjava.UnitList; +import net.sourceforge.unitsinjava.Value; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * In order to sort through all the possible units, a database of weights is used. + * Units with a higher weight are shown first in all lists. + * Weights are initialized from static JSON files, including regional weights (eg. + * to put imperial units above metric in the US). + * + * @author steve + */ +public class UnitUsageDBHelper extends SQLiteOpenHelper { + public final static String TAG = UnitUsageDBHelper.class.getSimpleName(); + public static final String + DB_NAME = "units", + DB_USAGE_TABLE = "usage", + DB_CLASSIFICATION_TABLE = "classification", + + DB_USAGE_INDEX = "factor_fprints", + DB_CLASSIFICATION_INDEX = "factor_fprints_classification"; + + private final Context context; + + private static final int DB_VERSION = 6; + + private HashMap mDebugFingerprints; + + public UnitUsageDBHelper(Context context) { + super(context, DB_NAME, null, DB_VERSION); + this.context = context; + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE '" + DB_USAGE_TABLE + + "' (" + + "'" + UsageEntry._ID + "' INTEGER PRIMARY KEY," + + "'" + UsageEntry._UNIT + "' TEXT UNIQUE ON CONFLICT IGNORE," + + "'" + UsageEntry._USE_COUNT + "' INTEGER," + + "'" + UsageEntry._FACTOR_FPRINT + "' TEXT," + + "'" + UsageEntry._IS_UNIT_ALIAS + "' INTEGER DEFAULT 0" + + ")"); + db.execSQL("CREATE INDEX '" + DB_USAGE_INDEX + "' ON " + DB_USAGE_TABLE + " (" + UsageEntry._FACTOR_FPRINT + ")"); + + db.execSQL("CREATE TABLE '" + DB_CLASSIFICATION_TABLE + + "' (" + + "'" + ClassificationEntry._ID + "' INTEGER PRIMARY KEY," + + "'" + ClassificationEntry._DESCRIPTION + "' TEXT," + + "'" + ClassificationEntry._FACTOR_FPRINT + "' TEXT UNIQUE" + + ")"); + db.execSQL("CREATE UNIQUE INDEX '" + DB_CLASSIFICATION_INDEX + "' ON " + DB_CLASSIFICATION_TABLE + " (" + ClassificationEntry._FACTOR_FPRINT + ")"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.d(TAG, String.format("Upgrading database from version %1$d to %2$d", oldVersion, newVersion)); + switch (oldVersion) { + case 1: + case 2: + case 3: + case 4: + dropTables(db); + onCreate(db); + break; + case 5: + upgrade5to6(db); + } + } + + private void upgrade5to6(SQLiteDatabase db) { + db.execSQL("ALTER TABLE '" + DB_USAGE_TABLE + + "' ADD COLUMN " + + "'" + UsageEntry._IS_UNIT_ALIAS + "' INTEGER DEFAULT 0"); + } + + private void dropTables(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS " + DB_USAGE_TABLE); + db.execSQL("DROP TABLE IF EXISTS " + DB_CLASSIFICATION_TABLE); + + db.execSQL("DROP INDEX IF EXISTS " + DB_USAGE_INDEX); + db.execSQL("DROP INDEX IF EXISTS " + DB_CLASSIFICATION_INDEX); + } + + private int getUnitUsageDbCount(SQLiteDatabase db) { + final String[] proj = {UsageEntry._ID}; + if (!db.isOpen()) { + return -1; + } + final Cursor c = db.query(DB_USAGE_TABLE, proj, null, null, null, null, null); + c.moveToFirst(); + final int count = c.getCount(); + c.close(); + return count; + } + + private class Fingerprints { + + private final HashMap mFingerprints = new HashMap<>(); + + Fingerprints() { + loadFingerprintsFromFile(); + } + + private void loadFingerprintsFromFile() { + try { + final JSONObject fprints = loadJsonObjectFromRawResource(context, R.raw.fingerprints); + for (final Iterator i = fprints.keys(); i.hasNext(); ) { + final String key = i.next(); + mFingerprints.put(key, fprints.optString(key)); + } + } catch (final Exception e) { + e.printStackTrace(); + } + } + + public String getFingerprint(String unitName) { + final String fpr; + if (mFingerprints.containsKey(unitName)) { + fpr = mFingerprints.get(unitName); + } else { + fpr = computeFingerprint(unitName); + mFingerprints.put(unitName, fpr); + } + return fpr; + } + + public HashMap getAllFingerprints() { + return mFingerprints; + } + } + + public static String computeFingerprint(String unitName) { + + String fpr = null; + try { + String uList = UnitList.isUnitList(unitName); + if (uList != null) { + UnitList ul = new UnitList(uList); + fpr = ValueGui.getFingerprint(ul); + } else { + Value unit; + // this is done here to allow for unicode that maps to functions + unitName = Units.unicodeToAscii(unitName); + if (unitName.endsWith("(")) { + final DefinedFunction f = DefinedFunction.table.get(unitName.substring(0, unitName.length() - 1)); + if (f != null) { + unit = f.getConformability(); + } else { + // non-DefinedFunctions are built in and compatible with numbers. + unit = Value.fromString("0"); + } + } else { + unit = ValueGui.fromUnicodeString(unitName); + + } + if (unit != null) { + fpr = ValueGui.getFingerprint(unit); + } + } + } catch (final EvalError e) { + // skip things we can't handle + } + return fpr; + } + + public void updateUnitUsage() { + final SQLiteDatabase db = getWritableDatabase(); + if (!BuildConfig.DEBUG && getUnitUsageDbCount(db) > 0 && + Application.localeAndVersionUnchanged(context)) { + Log.d(TAG, "Unit weights still valid, skipping update"); + db.close(); + return; + } + + Log.d(TAG, "getting existing weights…"); + final HashMap existingUnitWeights = getAllUnitWeights(db); + db.beginTransaction(); + db.delete(DB_USAGE_TABLE, null, null); + + // load the initial table in + final ContentValues cv = new ContentValues(); + + Log.d(TAG, "init all weights hash"); + final HashMap allUnitWeights = + new HashMap<>(Unit.table.keySet().size()); + Log.d(TAG, "adding all known weights…"); + final Pattern currencySymbol = Pattern.compile("\\p{Sc}"); + for (final String unitName : Unit.table.keySet()) { + // don't add all uppercase names, but allow currency symbols + if (!unitName.toUpperCase().equals(unitName) || + currencySymbol.matcher(unitName).matches()) { + allUnitWeights.put(unitName, 0); + } + } + Log.d(TAG, "adding all known functions…"); + for (final String functionName : BuiltInFunction.table.keySet()) { + allUnitWeights.put(functionName + "(", 0); + } + for (final String functionName : DefinedFunction.table.keySet()) { + allUnitWeights.put(functionName + "(", 0); + } + Log.d(TAG, "adding known unit list aliases"); + for (final String aliasName : Alias.table.keySet()) { + allUnitWeights.put(aliasName, 0); + } + Log.d(TAG, "adding common weights"); + addAll(loadInitialWeights(R.raw.common_weights), allUnitWeights); + Log.d(TAG, "adding regional weights"); + addAll(loadInitialWeights(R.raw.regional_weights), allUnitWeights); + + Log.d(TAG, "loading pre-computed fingerprints"); + final Fingerprints fingerprints = new Fingerprints(); + + Log.d(TAG, "applying existing usage weights"); + for (final Map.Entry entry: existingUnitWeights.entrySet()) { + final String unitName = entry.getKey(); + if (allUnitWeights.containsKey(unitName) || + // Users can generate "custom" usage entries by combining base units with one of + // the allowed prefixes. Check for the fingerprint to see if the unit is still + // valid, or whether the base unit or prefix was dropped from the units file. + fingerprints.getFingerprint(unitName) != null) { + allUnitWeights.put(unitName, entry.getValue()); + } + } + + // This is so that things of common weight end up in non-random order + // without having to do an SQL order-by. + final ArrayList sortedUnits = new ArrayList<>(allUnitWeights.keySet()); + Log.d(TAG, "Sorting units…"); + Collections.sort(sortedUnits); + Log.d(TAG, "Adding all sorted units…"); + + for (final String unitName : sortedUnits) { + cv.put(UsageEntry._UNIT, unitName); + cv.put(UsageEntry._USE_COUNT, allUnitWeights.get(unitName)); + cv.put(UsageEntry._FACTOR_FPRINT, fingerprints.getFingerprint(unitName)); + + if (Alias.table.containsKey(unitName)) { + cv.put(UsageEntry._IS_UNIT_ALIAS, 1); + } else { + cv.put(UsageEntry._IS_UNIT_ALIAS, 0); + } + + db.insert(DB_USAGE_TABLE, null, cv); + } + db.setTransactionSuccessful(); + db.endTransaction(); + db.close(); + + context.getContentResolver().notifyChange(UsageEntry.CONTENT_URI, null); + + if (BuildConfig.DEBUG) { + mDebugFingerprints = fingerprints.getAllFingerprints(); + } + Log.d(TAG, "done!"); + } + + private HashMap getAllUnitWeights(SQLiteDatabase db) { + final String[] proj = {UsageEntry._UNIT, UsageEntry._USE_COUNT}; + final Cursor c = db.query(DB_USAGE_TABLE, proj, null, null, null, null, null); + final HashMap allUnits; + + allUnits = new HashMap<>(c.getCount()); + if (c.getCount() > 0) { + final int unitsCol = c.getColumnIndexOrThrow(UsageEntry._UNIT); + final int usageCol = c.getColumnIndexOrThrow(UsageEntry._USE_COUNT); + c.moveToFirst(); + do { + allUnits.put(c.getString(unitsCol), c.getInt(usageCol)); + } while (c.moveToNext()); + } + + c.close(); + return allUnits; + } + + public boolean canDebugDumpFingerprints() { + return mDebugFingerprints != null; + } + + public void debugDumpFingerprints() { + if (mDebugFingerprints != null) { + dumpFingerprints(mDebugFingerprints); + } + } + + private void dumpFingerprints(HashMap fingerprints) { + final File dumpFolder; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + dumpFolder = context.getExternalFilesDir(null); + } else { + dumpFolder = context.getFilesDir(); + } + final File fprintsOutput = new File(dumpFolder, "fingerprints.json"); + final JSONObject jo = new JSONObject(fingerprints); + try { + final FileWriter fw = new FileWriter(fprintsOutput); + fw.write(jo.toString(1)); + fw.close(); + Log.i(TAG, "fingerprints written to: " + fprintsOutput.getCanonicalPath()); + + } catch (final Exception e) { + e.printStackTrace(); + } + } + + public void loadUnitClassifications() { + if (!BuildConfig.DEBUG && Application.localeAndVersionUnchanged(context)) { + Log.d(TAG, "Unit classifications still valid, skipping update."); + return; + } + + final SQLiteDatabase db = getWritableDatabase(); + final JSONObject jo = loadInitialWeights(R.raw.unit_classification); + + db.beginTransaction(); + db.delete(DB_CLASSIFICATION_TABLE, null, null); + final ContentValues cv = new ContentValues(); + for (final Iterator i = jo.keys(); i.hasNext(); ) { + final String unit = i.next(); + final String description = jo.optString(unit); + final String fprint = computeFingerprint(unit); + cv.put(ClassificationEntry._FACTOR_FPRINT, fprint); + cv.put(ClassificationEntry._DESCRIPTION, description); + db.insert(DB_CLASSIFICATION_TABLE, null, cv); + } + db.setTransactionSuccessful(); + db.endTransaction(); + db.close(); + Log.d(TAG, "Successfully added " + jo.length() + " classification entries."); + + Application.storeLocaleAndVersion(context); + } + + private void addAll(JSONObject unitWeights, HashMap allWeights) { + for (final Iterator i = unitWeights.keys(); i.hasNext(); ) { + final String key = i.next(); + if (allWeights.containsKey(key)) { + allWeights.put(key, allWeights.get(key) + unitWeights.optInt(key)); + } else { + allWeights.put(key, unitWeights.optInt(key)); + } + } + } + + /** + * A weight file is just a static JSON file used to set the initial + * weights for unit recommendation precedence. + * + * @param resourceId + * @return + */ + private JSONObject loadInitialWeights(int resourceId) { + try { + + final JSONObject jo = loadJsonObjectFromRawResource(context, resourceId); + + // remove all "comments", which are just key entries that start with "--" + for (final Iterator i = jo.keys(); i.hasNext(); ) { + final String key = i.next(); + if (key.startsWith("--")) { + i.remove(); + } + } + + return jo; + + } catch (final Exception e) { + e.printStackTrace(); + return null; + } + } + + private static JSONObject loadJsonObjectFromRawResource(Context context, int resourceId) throws IOException, JSONException { + final InputStream is = context.getResources().openRawResource(resourceId); + + final StringBuilder jsonString = new StringBuilder(); + + for (final BufferedReader isReader = new BufferedReader(new InputStreamReader(is), 16000); + isReader.ready(); ) { + jsonString.append(isReader.readLine()); + } + return new JSONObject(jsonString.toString()); + } + + public final static String USAGE_SORT = UsageEntry._USE_COUNT + " DESC, " + UsageEntry._UNIT + " ASC"; + private static final String CONFORMING_SELECTION = UsageEntry._FACTOR_FPRINT + " = ?"; + + private static String cachedEntryText; + private static String[] cachedEntryFprintArgs; + + /** + * @param otherEntry + * @return + */ + private synchronized static String[] getConformingSelectionArgs(TextView otherEntry) { + final String otherEntryText = otherEntry.getText().toString(); + if (otherEntryText.length() > 0) { + if (otherEntryText.equals(cachedEntryText)) { + return cachedEntryFprintArgs; + } + try { + final String[] conformingSelectionArgs = {ValueGui.getFingerprint(ValueGui.fromUnicodeString(ValueGui.closeParens(otherEntryText)))}; + cachedEntryText = otherEntryText; + cachedEntryFprintArgs = conformingSelectionArgs; + return conformingSelectionArgs; + } catch (final EvalError e) { + return null; + } + } + return null; + } + + public static class UnitCursorAdapter extends SimpleCursorAdapter { + + private static final int MSG_REQUERY = 0; + private boolean runningQuery; + private final ContentResolver mContentResolver; + private final Activity mActivity; + private final Uri mQueryUri; + private final TextView mOtherEntry; + private final Handler mHandler = new Handler() { + private int retryCount = 0; + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_REQUERY: + if (!runningQuery) { + getFilter().filter(null); + //notifyDataSetInvalidated(); + retryCount = 0; + } else { + // XXX hack to work around race condition when clearing + // both fields at the same time. + if (retryCount < 2) { + mHandler.sendEmptyMessageDelayed(MSG_REQUERY, 100); + retryCount++; + } + } + break; + } + } + }; + + public UnitCursorAdapter(Activity context, Uri queryUri, TextView otherEntry) { + super(context, android.R.layout.simple_dropdown_item_1line, + context.managedQuery(queryUri, null, null, null, UnitUsageDBHelper.USAGE_SORT), + new String[]{UsageEntry._UNIT}, + new int[]{android.R.id.text1}); + + mActivity = context; + mQueryUri = queryUri; + mContentResolver = context.getContentResolver(); + setStringConversionColumn(getCursor().getColumnIndex(UsageEntry._UNIT)); + + final TextUpdateWatcher tuw = new TextUpdateWatcher(this); + otherEntry.addTextChangedListener(tuw); + otherEntry.setOnFocusChangeListener(tuw); + + mOtherEntry = otherEntry; + } + + public synchronized void otherEntryUpdated() { + if (!mHandler.hasMessages(MSG_REQUERY)) { + mHandler.sendEmptyMessage(MSG_REQUERY); + } + } + + @Override + public void changeCursor(Cursor c) { + mActivity.stopManagingCursor(getCursor()); + + super.changeCursor(c); + mActivity.startManagingCursor(c); + } + + @Override + public Cursor runQueryOnBackgroundThread(CharSequence constraint) { + if (getFilterQueryProvider() != null) { + return getFilterQueryProvider().runQuery(constraint); + } + + runningQuery = true; + + final String[] selectionArgs = null; + final String selection = null; + Cursor c; + + if (constraint == null || constraint.length() == 0) { + c = queryWithConforming(mOtherEntry, selection, selectionArgs); + } else { + final Matcher m = UNIT_EXTRACT_REGEX.matcher(constraint); + String modConstraint; + if (m.matches()) { + modConstraint = m.group(1); + c = queryWithConforming(mOtherEntry, UsageEntry._UNIT + " GLOB ?", new String[]{modConstraint + "*"}); + } else { + c = queryWithConforming(mOtherEntry, null, null); + } + } + runningQuery = false; + return c; + } + + private Cursor queryWithConforming(TextView otherEntry, String selection, String[] selectionArgs) { + String conformingSelection = null; + String[] conformingSelectionArgs = getConformingSelectionArgs(otherEntry); + + if (selection != null) { + if (conformingSelectionArgs != null) { + conformingSelection = CONFORMING_SELECTION + " AND " + selection; + final ArrayList args = new ArrayList<>(); + args.add(conformingSelectionArgs[0]); + args.addAll(Arrays.asList(selectionArgs)); + conformingSelectionArgs = args.toArray(new String[]{}); + } else { + conformingSelection = selection; + conformingSelectionArgs = selectionArgs; + } + } else if (conformingSelectionArgs != null) { + conformingSelection = CONFORMING_SELECTION; + } + Cursor c = mContentResolver.query(mQueryUri, null, conformingSelection, conformingSelectionArgs, USAGE_SORT); + // If we don't get anything by conforming, the user may be attempting to ask for + // a complex result and we should just return everything. + if (c.getCount() == 0) { + c.close(); + c = mContentResolver.query(mQueryUri, null, selection, selectionArgs, USAGE_SORT); + } + return c; + } + + private static class TextUpdateWatcher implements TextWatcher, OnFocusChangeListener { + private boolean dirty = false; + + private final UnitCursorAdapter adapter; + + public TextUpdateWatcher(UnitCursorAdapter adapter) { + this.adapter = adapter; + } + + public void afterTextChanged(Editable s) { + } + + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + } + + public void onTextChanged(CharSequence s, int start, int before, + int count) { + if (s.length() == 0) { + // change immediately if cleared. + adapter.otherEntryUpdated(); + dirty = false; + } else { + dirty = true; + } + } + + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus && dirty) { + dirty = false; + adapter.otherEntryUpdated(); + } + } + } + } + + public static final String UNIT_REGEX_PATTERN = "(\\p{Alpha}[\\w\\p{Sc}]+)"; + private static final Pattern UNIT_REGEX = Pattern.compile(UNIT_REGEX_PATTERN); + private static final Pattern UNIT_EXTRACT_REGEX = Pattern.compile(".*?" + UNIT_REGEX_PATTERN); + + private final static String[] INCREMENT_QUERY_PROJECTION = {UsageEntry._ID, UsageEntry._USE_COUNT, UsageEntry._UNIT}; + + /** + * Increments the usage counter for the given unit. + * + * @param unit name of the unit + * @param cr ContentResolver for the unit usage database + */ + public static void logUnitUsed(String unit, ContentResolver cr) { + final String[] selectionArgs = {unit}; + final Cursor c = cr.query(UsageEntry.CONTENT_URI, INCREMENT_QUERY_PROJECTION, UsageEntry._UNIT + "=?", selectionArgs, null); + if (c.getCount() > 0) { + c.moveToFirst(); + final int useCount = c.getInt(c.getColumnIndexOrThrow(UsageEntry._USE_COUNT)); + final int id = c.getInt(c.getColumnIndexOrThrow(UsageEntry._ID)); + final ContentValues cv = new ContentValues(); + cv.put(UsageEntry._USE_COUNT, useCount + 1); + + cr.update(ContentUris.withAppendedId(UsageEntry.CONTENT_URI, id), cv, null, null); + } else { + final ContentValues cv = new ContentValues(); + cv.put(UsageEntry._UNIT, unit); + cv.put(UsageEntry._USE_COUNT, 1); + cv.put(UsageEntry._FACTOR_FPRINT, computeFingerprint(unit)); + cr.insert(UsageEntry.CONTENT_URI, cv); + } + c.close(); + } + + public static void logUnitsInExpression(String expression, ContentResolver cr) { + final Matcher m = UNIT_REGEX.matcher(expression); + while (m.find()) { + String unit = m.group(1); + // functions are noted by ending in an open paren. + if (DefinedFunction.table.containsKey(unit) || BuiltInFunction.table.containsKey(unit)) { + unit += "("; + } + logUnitUsed(unit, cr); + } + } + +} diff --git a/app/src/main/java/de/buttercookie/units/Units.java b/app/src/main/java/de/buttercookie/units/Units.java new file mode 100644 index 00000000..2fd12a45 --- /dev/null +++ b/app/src/main/java/de/buttercookie/units/Units.java @@ -0,0 +1,1214 @@ +/* + * Units.java + * Copyright (C) 2010 Steve Pomeroy + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.buttercookie.units; + +import static android.content.res.Configuration.SCREENLAYOUT_SIZE_LARGE; +import static android.content.res.Configuration.SCREENLAYOUT_SIZE_MASK; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.app.SearchManager; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; +import android.database.Cursor; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.os.SystemClock; +import android.text.ClipboardManager; +import android.text.Editable; +import android.text.InputType; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.ContextThemeWrapper; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnFocusChangeListener; +import android.view.View.OnLongClickListener; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; +import android.view.inputmethod.InputMethodManager; +import android.webkit.WebView; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ExpandableListAdapter; +import android.widget.ExpandableListView; +import android.widget.ExpandableListView.OnChildClickListener; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.MultiAutoCompleteTextView; +import android.widget.SimpleCursorAdapter; +import android.widget.SimpleCursorTreeAdapter; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.sourceforge.unitsinjava.DefinedFunction; +import net.sourceforge.unitsinjava.EvalError; +import net.sourceforge.unitsinjava.Function; +import net.sourceforge.unitsinjava.UnitList; +import net.sourceforge.unitsinjava.Value; + +import org.jared.commons.ui.WorkspaceView; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; + +import de.buttercookie.units.ValueGui.ConversionException; +import de.buttercookie.units.ValueGui.ReciprocalException; + +// TODO med: add implicit "last result" eg. "1+1=" then press "+1" to get "3" +// TODO med: auto-ranging for metric units (auto add "kilo-" or "micro-") +// TODO med: add date headers for history, to consolidate items ("yesterday", "1 week ago", etc.) +// TODO med: show keyboard icon for 2nd tap (can't do this easily, as one can't detect if soft keyboard is shown or not). May need to scrap this idea. +// TODO low: longpress on unit for description (look in unit addition error message for hints) +// TODO low: Auto-scale text for display (square) +public class Units extends Activity implements OnClickListener, OnEditorActionListener, OnTouchListener, OnLongClickListener { + @SuppressWarnings("unused") + private final static String TAG = Units.class.getSimpleName(); + + private MultiAutoCompleteTextView wantEditText; + private MultiAutoCompleteTextView haveEditText; + private TextView resultView; + private ListView history; + private LinearLayout historyDrawer; + private Button historyClose; + private WorkspaceView workspace; + + private UnitUsageDBHelper unitUsageDBHelper; + + private HistoryAdapter mHistoryAdapter; + + private InitialisationTask mInitialisationTask; + private boolean runInitialisationTask = false; + private boolean invalidateUnitsList = false; + + public final static String + ACTION_USE_UNIT = "de.buttercookie.units.ACTION_USE_UNIT", + EXTRA_UNIT_NAME = "de.buttercookie.units.EXTRA_UNIT_NAME"; + + public final static String + STATE_RESULT_TEXT = "de.buttercookie.units.RESULT_TEXT", + STATE_DRAWER_OPENED = "de.buttercookie.units.DRAWER_OPENED", + STATE_DIALOG_UNIT_CATEGORY = "de.buttercookie.units.STATE_DIALOG_UNIT_CATEGORY"; + + private static final int REQUEST_PICK_UNIT = 0; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + wantEditText = findViewById(R.id.want); + haveEditText = findViewById(R.id.have); + defaultInputType = wantEditText.getInputType(); + wantEditText.setOnFocusChangeListener(inputBoxOnFocusChange); + haveEditText.setOnFocusChangeListener(inputBoxOnFocusChange); + + resultView = findViewById(R.id.result); + history = findViewById(R.id.history_list); + historyDrawer = findViewById(R.id.history_drawer); + historyClose = findViewById(R.id.history_close); + workspace = findViewById(R.id.numpad_switcher); + //workspace.setTouchSlop(); // XXX scale + //workspace.setShowTabIndicator(false); + + mHistoryAdapter = new HistoryAdapter(this, managedQuery(HistoryEntry.CONTENT_URI, HistoryAdapter.PROJECTION, null, null, HistoryEntry.SORT_DEFAULT)); + history.setAdapter(mHistoryAdapter); + // TODO consolidate listeners + history.setOnItemClickListener(new AdapterView.OnItemClickListener() { + + public void onItemClick(AdapterView parent, View v, int position, long id) { + + setCurrentEntry(ContentUris.withAppendedId(HistoryEntry.CONTENT_URI, mHistoryAdapter.getItemId(position))); + + setHistoryVisible(false); + } + }); + history.setOnCreateContextMenuListener(this); + + resultView.setOnClickListener(this); + resultView.setOnCreateContextMenuListener(this); + historyClose.setOnClickListener(this); + + // Go through the numberpad and add all the onClick listeners. + // Make sure to update if the layout changes. + setGridChildrenListener(findViewById(R.id.numberpad), buttonListener, buttonListener); + setGridChildrenListener(findViewById(R.id.numberpad2), buttonListener, buttonListener); + + final View backspace = findViewById(R.id.backspace); + backspace.setOnClickListener(buttonListener); + backspace.setOnLongClickListener(buttonListener); + + unitUsageDBHelper = new UnitUsageDBHelper(this); + + final Object instance = getLastNonConfigurationInstance(); + if (instance instanceof InitialisationTask) { + mInitialisationTask = (InitialisationTask) instance; + mInitialisationTask.attach(this); + } else { + runInitialisationTask = true; + } + + wantEditText.setOnEditorActionListener(this); + + final UnitsMultiAutoCompleteTokenizer tokenizer = new UnitsMultiAutoCompleteTokenizer(); + haveEditText.setTokenizer(tokenizer); + wantEditText.setTokenizer(tokenizer); + haveEditText.setOnTouchListener(this); + wantEditText.setOnTouchListener(this); + + if (savedInstanceState != null) { + resultView.setText(savedInstanceState.getCharSequence(STATE_RESULT_TEXT)); + setHistoryVisible(savedInstanceState.getBoolean(STATE_DRAWER_OPENED, false), false); + mDialogUnitCategoryUnit = savedInstanceState.getString(STATE_DIALOG_UNIT_CATEGORY); + } + + final Intent intent = getIntent(); + handleIntent(intent); + + } + + private void handleIntent(Intent intent) { + final String action = intent.getAction(); + if (Intent.ACTION_SEARCH.equals(action)) { + final Intent pickUnit = new Intent(this, UnitListActivity.class) + .setAction(Intent.ACTION_PICK).setData(UsageEntry.CONTENT_URI); + final String query = intent.getExtras().getString(SearchManager.QUERY); + pickUnit.putExtra(UnitListActivity.EXTRA_UNIT_QUERY, query); + + startActivityForResult(pickUnit, REQUEST_PICK_UNIT); + + } else if (ACTION_USE_UNIT.equals(action)) { + sendUnitAsSoftKeyboard(intent.getData()); + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + handleIntent(intent); + } + + @Override + protected void onStart() { + super.onStart(); + haveEditText.setAdapter(new UnitUsageDBHelper.UnitCursorAdapter(this, + UsageEntry.CONTENT_URI_NO_UNIT_ALIAS, + wantEditText)); + wantEditText.setAdapter(new UnitUsageDBHelper.UnitCursorAdapter(this, + UsageEntry.CONTENT_URI, + haveEditText)); + } + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + if (runInitialisationTask) { + runInitialisationTask = false; + mInitialisationTask = new InitialisationTask(this); + mInitialisationTask.execute(); + } + + super.onPostCreate(savedInstanceState); + } + + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putCharSequence(STATE_RESULT_TEXT, resultView.getText()); + outState.putBoolean(STATE_DRAWER_OPENED, isHistoryVisible()); + outState.putString(STATE_DIALOG_UNIT_CATEGORY, mDialogUnitCategoryUnit); + } + + @Override + public Object onRetainNonConfigurationInstance() { + if (mInitialisationTask != null) { + mInitialisationTask.detach(); + } + return mInitialisationTask; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case REQUEST_PICK_UNIT: { + if (resultCode == RESULT_OK) { + sendTextAsSoftKeyboard(data.getExtras().getString(EXTRA_UNIT_NAME) + " "); + } + } + break; + } + } + + /** + * @param vg Given a view group with view groups inside it, set all children to have the same onClickListeners. + * @param onClickListener + * @param onLongClickListener + */ + private void setGridChildrenListener(ViewGroup vg, OnClickListener onClickListener, OnLongClickListener onLongClickListener) { + // Go through the children and add all the onClick listeners. + // Make sure to update if the layout changes. + final int rows = vg.getChildCount(); + for (int row = 0; row < rows; row++) { + final ViewGroup v = (ViewGroup) vg.getChildAt(row); + final int columns = v.getChildCount(); + for (int column = 0; column < columns; column++) { + final View button = v.getChildAt(column); + button.setOnClickListener(onClickListener); + button.setOnLongClickListener(onLongClickListener); + } + } + } + + private boolean isHistoryVisible() { + return historyDrawer.getVisibility() == View.VISIBLE; + } + + private void setHistoryVisible(boolean visible) { + setHistoryVisible(visible, true); + } + + private void setHistoryVisible(boolean visible, boolean animate) { + if (visible && historyDrawer.getVisibility() == View.INVISIBLE) { + if (animate) { + historyDrawer.startAnimation(AnimationUtils.loadAnimation(getApplicationContext(), R.anim.history_show)); + } + historyDrawer.setVisibility(View.VISIBLE); + historyDrawer.requestFocus(); + + } else if (!visible && historyDrawer.getVisibility() == View.VISIBLE) { + if (animate) { + historyDrawer.startAnimation(AnimationUtils.loadAnimation(getApplicationContext(), R.anim.history_hide)); + } + historyDrawer.setVisibility(View.INVISIBLE); + } + } + + /** + * Adds the given entry to the history database. + * + * @author steve + */ + private class AddToHistoryRunnable implements Runnable { + private final String haveExpr, wantExpr; + private final Double result; + + public AddToHistoryRunnable(String haveExpr, String wantExpr, Double result) { + this.haveExpr = haveExpr; + this.wantExpr = wantExpr; + this.result = result; + } + + public void run() { + final ContentValues cv = new ContentValues(); + cv.put(HistoryEntry._HAVE, haveExpr); + cv.put(HistoryEntry._WANT, wantExpr); + cv.put(HistoryEntry._RESULT, result); + + getContentResolver().insert(HistoryEntry.CONTENT_URI, cv); + + } + } + + // TODO make reciprocal notice better animated so it doesn't modify main layout + public void addToHistory(@NonNull String haveExpr, @NonNull String wantExpr, + @Nullable String unitAlias, + Double result, boolean reciprocal) { + haveExpr = haveExpr.trim(); + wantExpr = wantExpr.trim(); + new AddToUsageTask().execute(haveExpr, + unitAlias != null ? unitAlias.trim() : wantExpr); + haveExpr = reciprocal ? "1÷(" + haveExpr + ")" : haveExpr; + resultView.setText(HistoryEntry.toCharSequence(haveExpr, wantExpr, result)); + + // done on a new thread to avoid hanging the UI while the DB is being updated. + new Thread(new AddToHistoryRunnable(haveExpr, wantExpr, result)).start(); + + final View reciprocalNotice = findViewById(R.id.reciprocal_notice); + if (reciprocal) { + resultView.requestFocus(); + + reciprocalNotice.setVisibility(View.VISIBLE); + reciprocalNotice.startAnimation(AnimationUtils.makeInAnimation(this, true)); + } else { + reciprocalNotice.setVisibility(View.GONE); + resultView.setError(null); + } + } + + + private final static String[] PROJECTION_LOAD_FROM_HISTORY = {HistoryEntry._HAVE, HistoryEntry._WANT, HistoryEntry._RESULT}; + + /** + * Set the current entries to that of a history entry. + * + * @param entry a history entry URI + */ + private void setCurrentEntry(Uri entry) { + final Cursor c = getContentResolver().query(entry, PROJECTION_LOAD_FROM_HISTORY, null, null, null); + if (c.moveToFirst()) { + setCurrentEntry(c.getString(c.getColumnIndex(HistoryEntry._HAVE)), c.getString(c.getColumnIndex(HistoryEntry._WANT))); + } + c.close(); + } + + @SuppressLint("SetTextI18n") + private void setCurrentEntry(String have, String want) { + haveEditText.setText(have + " ");// extra space is to prevent auto-complete from triggering. + wantEditText.setText(want + (want.length() > 0 ? " " : "")); + + haveEditText.requestFocus(); + haveEditText.setSelection(haveEditText.length()); + } + + /** + * Converts the history entry to a CharSequence. + * + * @param entry A history entry URI + * @return the name of the unit, as a CharSequence + */ + private CharSequence getEntryAsCharSequence(Uri entry) { + CharSequence text = null; + final Cursor c = getContentResolver().query(entry, PROJECTION_LOAD_FROM_HISTORY, null, null, null); + if (c.moveToFirst()) { + text = HistoryEntry.toCharSequence(c, c.getColumnIndex(HistoryEntry._HAVE), c.getColumnIndex(HistoryEntry._WANT), c.getColumnIndex(HistoryEntry._RESULT)); + } + c.close(); + return text; + } + + public static final HashMap UNICODE_TRANS = new HashMap<>(); + + static { + UNICODE_TRANS.put('÷', "/"); + UNICODE_TRANS.put('×', "*"); + UNICODE_TRANS.put('²', "^2"); + UNICODE_TRANS.put('³', "^3"); + UNICODE_TRANS.put('⁴', "^4"); + UNICODE_TRANS.put('−', "-"); + UNICODE_TRANS.put('π', "pi"); + UNICODE_TRANS.put('Π', "pi"); + UNICODE_TRANS.put('√', "sqrt"); + UNICODE_TRANS.put('∛', "cuberoot"); + } + + public static String unicodeToAscii(String unicodeInput) { + final StringBuilder sb = new StringBuilder(); + final int len = unicodeInput.length(); + for (int i = 0; i < len; i++) { + final char c = unicodeInput.charAt(i); + final String sub = UNICODE_TRANS.get(c); + if (sub != null) { + sb.append(sub); + } else { + sb.append(c); + } + } + return sb.toString(); + } + + // TODO filter error messages and output translate to unicode from engine. error msgs and Inifinity → ∞ + public void go() { + String haveStr = haveEditText.getText().toString().trim(); + String wantStr = wantEditText.getText().toString().trim(); + + try { + Value have; + try { + if (haveStr.length() == 0) { + haveEditText.requestFocus(); + haveEditText.setError(getText(R.string.err_have_empty)); + return; + } + haveStr = ValueGui.closeParens(haveStr); + have = ValueGui.fromUnicodeString(haveStr); + + } catch (final EvalError e) { + haveEditText.requestFocus(); + haveEditText.setError(e.getLocalizedMessage()); + return; + } + + Double resultVal; + boolean reciprocal = false; + + String uList = UnitList.isUnitList(wantStr); + String unitAlias = null; + if (uList != null) { + unitAlias = wantStr; + UnitList ul; + try { + ul = new UnitList(uList); + } catch (final EvalError e) { + wantEditText.requestFocus(); + wantEditText.setError(e.getLocalizedMessage()); + return; + } + + wantEditText.setError(null); + // unit lists are a special case and don't have a reciprocal, so the result + // is just stored in the wantStr. + resultVal = null; + wantStr = ValueGui.convertNonInteractive(this, haveStr, have, ul); + + } else { + Value want = null; + Function func; + try { + func = DefinedFunction.table.get(wantStr); + if (func == null && wantStr.endsWith("(")) { + func = DefinedFunction.table.get(wantStr.substring(0, wantStr.length() - 1)); + } + if (func == null) { + wantStr = ValueGui.closeParens(wantStr); + want = ValueGui.fromUnicodeString(wantStr); + } + + } catch (final EvalError e) { + wantEditText.requestFocus(); + wantEditText.setError(e.getLocalizedMessage()); + return; + } + + try { + wantEditText.setError(null); + + // if no want value is specified, provide a definition. + if (wantStr.length() > 0) { + if (func != null) { + // functions are a special case and don't have a reciprocal, so the result + // is just stored in the wantStr. + resultVal = null; + wantStr = ValueGui.convertNonInteractive(ValueGui.fromUnicodeString(haveStr), func); + } else { + resultVal = ValueGui.convertNonInteractive(have, want); + } + + } else { + resultVal = have.factor; + final StringBuilder haveDef = new StringBuilder(); + + haveDef.append(have.numerator.asString()); + + if (have.denominator.size() > 0) { + haveDef.append(" ÷").append(have.denominator.asString()); + } + + wantStr = haveDef.toString(); + } + + + } catch (final ReciprocalException re) { + reciprocal = true; + + resultVal = ValueGui.convertNonInteractive(re.reciprocal, want); + } + } + + allClear(); + + addToHistory(haveStr, wantStr, unitAlias, resultVal, reciprocal); + + } catch (final ConversionException e) { + resultView.setText(null); + wantEditText.requestFocus(); + wantEditText.setError(getText(R.string.err_no_conform)); + } + } + + public void allClear() { + haveEditText.getEditableText().clear(); + haveEditText.setError(null); + wantEditText.getEditableText().clear(); + wantEditText.setError(null); + haveEditText.requestFocus(); + } + + @SuppressLint("NonConstantResourceId") + public void onClick(View v) { + switch (v.getId()) { + case R.id.result: + setHistoryVisible(true); + break; + + case R.id.history_close: + setHistoryVisible(false); + break; + } + } + + + @SuppressLint("NonConstantResourceId") + public boolean onLongClick(View v) { + switch (v.getId()) { + case R.id.result: + v.showContextMenu(); + return true; + } + return false; + } + + private static final int + MENU_COPY = 0, + MENU_SEND = 1, + MENU_USE_RESULT = 2, + MENU_REEDIT = 3; + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + + menu.add(Menu.NONE, MENU_REEDIT, Menu.FIRST, R.string.ctx_menu_reedit); + menu.add(Menu.NONE, MENU_COPY, Menu.CATEGORY_SYSTEM, android.R.string.copy); + menu.add(Menu.NONE, MENU_SEND, Menu.CATEGORY_SYSTEM, R.string.ctx_menu_send); + menu.add(Menu.NONE, MENU_USE_RESULT, Menu.CATEGORY_SECONDARY, R.string.ctx_menu_use_result); + + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + final ContextMenuInfo ctxMenuInfo = item.getMenuInfo(); + int position = history.getCount() - 1; + if (ctxMenuInfo instanceof AdapterContextMenuInfo) { + position = ((AdapterContextMenuInfo) ctxMenuInfo).position; + } + final Uri itemUri = ContentUris.withAppendedId(HistoryEntry.CONTENT_URI, mHistoryAdapter.getItemId(position)); + + switch (item.getItemId()) { + case MENU_COPY: { + final CharSequence itemText = getEntryAsCharSequence(itemUri); + final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + clipboard.setText(itemText); + Toast.makeText(this, getString(R.string.toast_copy, itemText), + Toast.LENGTH_SHORT).show(); + } + break; + + case MENU_REEDIT: { + setCurrentEntry(itemUri); + setHistoryVisible(false); + } + break; + + case MENU_SEND: { + final CharSequence itemText = getEntryAsCharSequence(itemUri); + startActivity(Intent.createChooser( + new Intent(Intent.ACTION_SEND) + .setType("text/plain") + .putExtra(Intent.EXTRA_TEXT, + itemText), + getText(R.string.ctx_menu_send_title))); + } + break; + + case MENU_USE_RESULT: { + final Cursor c = getContentResolver().query(itemUri, PROJECTION_LOAD_FROM_HISTORY, null, null, null); + if (c.moveToFirst()) { + final int resultCol = c.getColumnIndex(HistoryEntry._RESULT); + setCurrentEntry((c.isNull(resultCol) ? "" : (c.getDouble(resultCol) + + " ")) + c.getString(c.getColumnIndex(HistoryEntry._WANT)), ""); + setHistoryVisible(false); + } + c.close(); + } + break; + } + + return super.onContextItemSelected(item); + } + + @Override + public void openOptionsMenu() { + Configuration config = getResources().getConfiguration(); + if (requireTabletMenuHack(config)) { + final int originalScreenSize = config.screenLayout & SCREENLAYOUT_SIZE_MASK; + config.screenLayout &= ~SCREENLAYOUT_SIZE_MASK; + config.screenLayout |= SCREENLAYOUT_SIZE_LARGE; + + super.openOptionsMenu(); + + config.screenLayout &= ~SCREENLAYOUT_SIZE_MASK; + config.screenLayout |= originalScreenSize; + } else { + super.openOptionsMenu(); + } + } + + private boolean requireTabletMenuHack(Configuration config) { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && + (config.screenLayout & SCREENLAYOUT_SIZE_MASK) + > SCREENLAYOUT_SIZE_LARGE; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + if (BuildConfig.DEBUG) { + MenuItem item = menu.findItem(R.id.dump_fingerprints); + item.setVisible(unitUsageDBHelper.canDebugDumpFingerprints()); + } + return super.onPrepareOptionsMenu(menu); + } + + @SuppressLint("NonConstantResourceId") + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.swap_inputs: { + if (getCurrentFocus() == haveEditText) { + swapInputs(haveEditText, wantEditText); + + } else if (getCurrentFocus() == wantEditText) { + swapInputs(wantEditText, haveEditText); + } else { + swapInputs(null, null); + } + return true; + } + case R.id.about: + showDialog(DIALOG_ABOUT); + return true; + + case R.id.show_history: + setHistoryVisible(true); + return true; + + case R.id.clear_history: + getContentResolver().delete(HistoryEntry.CONTENT_URI, null, null); + resultView.setText(null); + return true; + + case R.id.search: + onSearchRequested(); + return true; + + case R.id.dump_fingerprints: + new AsyncTask() { + @Override + protected Void doInBackground(Void... voids) { + unitUsageDBHelper.debugDumpFingerprints(); + return null; + } + }.execute(); + Toast.makeText(this, getString(R.string.toast_dump_fingerprints), + Toast.LENGTH_SHORT).show(); + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onOptionsMenuClosed(Menu menu) { + super.onOptionsMenuClosed(menu); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // Workaround for https://issuetracker.google.com/issues/315761686 + invalidateOptionsMenu(); + } + } + + /** + * Read an InputStream into a String until it hits EOF. + * + * @param in + * @return the complete contents of the InputStream + * @throws IOException + */ + static public String inputStreamToString(InputStream in) throws IOException { + final int bufsize = 8196; + final char[] cbuf = new char[bufsize]; + + final StringBuilder buf = new StringBuilder(bufsize); + + final InputStreamReader in_reader = new InputStreamReader(in); + + for (int readBytes = in_reader.read(cbuf, 0, bufsize); + readBytes > 0; + readBytes = in_reader.read(cbuf, 0, bufsize)) { + buf.append(cbuf, 0, readBytes); + } + + return buf.toString(); + } + + private void sendTextAsSoftKeyboard(String text) { + dispatchKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), text, Units.class.hashCode(), KeyEvent.FLAG_SOFT_KEYBOARD)); + } + + private void sendTextAsSoftKeyboard(String text, boolean moveToDefault) { + dispatchKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), text, Units.class.hashCode(), KeyEvent.FLAG_SOFT_KEYBOARD)); + if (moveToDefault) { + workspace.moveToDefaultScreen(); + } + } + + private void sendUnitAsSoftKeyboard(Uri unit) { + final String[] projection = {UsageEntry._ID, UsageEntry._UNIT}; + final Cursor c = getContentResolver().query(unit, projection, null, null, null); + if (c.moveToFirst()) { + sendTextAsSoftKeyboard(c.getString(c.getColumnIndex(UsageEntry._UNIT)) + " "); + } + c.close(); + } + + private final OnChildClickListener allUnitChildClickListener = new OnChildClickListener() { + + public boolean onChildClick(ExpandableListView parent, View v, + int groupPosition, int childPosition, long id) { + sendTextAsSoftKeyboard(((TextView) v).getText().toString() + " "); + dismissDialog(DIALOG_ALL_UNITS); + return true; + } + }; + + private final DialogInterface.OnClickListener dialogUnitCategoryOnClickListener = new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + + sendUnitAsSoftKeyboard(ContentUris.withAppendedId(UsageEntry.CONTENT_URI, dialogUnitCategoryList.getItemId(which))); + workspace.moveToDefaultScreen(); + } + }; + + private static final int + DIALOG_ABOUT = 0, + DIALOG_ALL_UNITS = 1, + DIALOG_LOADING_UNITS = 2, + DIALOG_UNIT_CATEGORY = 3; + + + + @Override + protected Dialog onCreateDialog(int id) { + switch (id) { + case DIALOG_ABOUT: { + final Builder builder = new AlertDialog.Builder(this); + + builder.setTitle(R.string.dialog_about_title); + builder.setIcon(R.mipmap.ic_launcher); + + try { + final WebView wv = new WebView(this); + final InputStream is = getAssets().open("README.xhtml"); + wv.loadDataWithBaseURL("file:///android_asset/", inputStreamToString(is), "application/xhtml+xml", "utf-8", null); + wv.setBackgroundColor(0); + builder.setView(wv); + } catch (final IOException e) { + builder.setMessage(R.string.err_no_load_about); + e.printStackTrace(); + } + + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + setResult(RESULT_OK); + } + }); + return builder.create(); + } + + case DIALOG_ALL_UNITS: { + final Builder b = new Builder(Units.this); + b.setTitle(R.string.dialog_all_units_title); + final ExpandableListView unitExpandList = new ExpandableListView(Units.this); + unitExpandList.setId(android.R.id.list); + unitExpandList.setAdapter(getAllUnits()); + unitExpandList.setCacheColorHint(0); + unitExpandList.setOnChildClickListener(allUnitChildClickListener); + b.setView(unitExpandList); + return b.create(); + } + + case DIALOG_UNIT_CATEGORY: { + final Builder b = new Builder(new ContextThemeWrapper(this, R.style.UnitCategoryDialogTheme)); + final String[] from = {UsageEntry._UNIT}; + final int[] to = {android.R.id.text1}; + b.setTitle("all units"); + final String[] projection = {UsageEntry._ID, UsageEntry._UNIT, UsageEntry._FACTOR_FPRINT}; + final Cursor c = managedQuery(UsageEntry.CONTENT_URI, projection, null, null, UnitUsageDBHelper.USAGE_SORT); + dialogUnitCategoryList = new SimpleCursorAdapter(this, android.R.layout.select_dialog_item, c, from, to); + b.setAdapter(dialogUnitCategoryList, dialogUnitCategoryOnClickListener); + + return b.create(); + } + + case DIALOG_LOADING_UNITS: { + final ProgressDialog pd = new ProgressDialog(this); + pd.setIndeterminate(true); + pd.setTitle(R.string.app_name); + pd.setMessage(getText(R.string.dialog_loading_units)); + return pd; + } + + default: + throw new IllegalArgumentException("Unknown dialog ID:" + id); + } + } + + private ExpandableListAdapter getAllUnits() { + final String[] groupProjection = {UsageEntry._ID, UsageEntry._UNIT, UsageEntry._FACTOR_FPRINT}; + // any selection below will select from the grouping description + final Cursor cursor = managedQuery(UsageEntry.CONTENT_URI_CONFORM_TOP, groupProjection, null, null, UnitUsageDBHelper.USAGE_SORT); + + return new UnitsExpandableListAdapter(cursor, this, + android.R.layout.simple_expandable_list_item_1, android.R.layout.simple_expandable_list_item_1, + new String[]{UsageEntry._UNIT}, + new int[]{android.R.id.text1}, + new String[]{UsageEntry._UNIT}, new int[]{android.R.id.text1}); + } + + private String mDialogUnitCategoryUnit = "m"; + private SimpleCursorAdapter dialogUnitCategoryList; + + @Override + protected void onPrepareDialog(int id, Dialog dialog) { + switch (id) { + case DIALOG_UNIT_CATEGORY: { + dialog.setTitle(mDialogUnitCategoryUnit); + + final String[] projection = {UsageEntry._ID, UsageEntry._UNIT, UsageEntry._FACTOR_FPRINT}; + final Cursor c = managedQuery(UsageEntry.getEntriesMatchingFprint(UnitUsageDBHelper.computeFingerprint(mDialogUnitCategoryUnit)), + projection, null, null, UnitUsageDBHelper.USAGE_SORT); + stopManagingCursor(dialogUnitCategoryList.getCursor()); + dialogUnitCategoryList.changeCursor(c); + final ListView lv = ((AlertDialog) dialog).getListView(); + lv.setSelectionFromTop(0, 0); + + } + break; + + case DIALOG_ALL_UNITS: { + if (invalidateUnitsList) { + invalidateUnitsList = false; + final ExpandableListView unitExpandList = dialog.findViewById(android.R.id.list); + unitExpandList.setAdapter(getAllUnits()); + } + } + break; + + default: + super.onPrepareDialog(id, dialog); + } + + } + + private void swapInputs(EditText focused, EditText unfocused) { + + final Editable e = wantEditText.getText(); + int start = 0, end = 0; + if (focused != null) { + start = focused.getSelectionStart(); + end = focused.getSelectionEnd(); + } + + wantEditText.setText(haveEditText.getText()); + haveEditText.setText(e); + if (unfocused != null) { + unfocused.requestFocus(); + unfocused.setSelection(start, end); + } + } + + public class UnitsExpandableListAdapter extends SimpleCursorTreeAdapter { + private final int factorFprintColumn; + private final String[] childProjection = {UsageEntry._ID, UsageEntry._UNIT}; + + public UnitsExpandableListAdapter(Cursor cursor, Activity context, int groupLayout, + int childLayout, String[] groupFrom, int[] groupTo, String[] childrenFrom, + int[] childrenTo) { + + super(context, cursor, groupLayout, groupFrom, groupTo, childLayout, childrenFrom, + childrenTo); + factorFprintColumn = cursor.getColumnIndex(UsageEntry._FACTOR_FPRINT); + } + + @Override + protected Cursor getChildrenCursor(Cursor groupCursor) { + // Given the group, we return a cursor for all the children within that group. + final String factorFprint = groupCursor.getString(factorFprintColumn); + + final String[] selectionArgs = {factorFprint}; + return managedQuery(UsageEntry.CONTENT_URI, childProjection, UsageEntry._FACTOR_FPRINT + "=?", selectionArgs, UnitUsageDBHelper.USAGE_SORT); + } + } + + private final ButtonEventListener buttonListener = new ButtonEventListener(); + + private class ButtonEventListener implements OnClickListener, OnLongClickListener { + + @SuppressLint("NonConstantResourceId") + public void onClick(View v) { + + switch (v.getId()) { + case R.id.backspace: { + dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)); + dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); + + } + break; + + case R.id.equal: + go(); + break; + + case R.id.unit_entry: { + final View currentFocus = getCurrentFocus(); + if (currentFocus instanceof MultiAutoCompleteTextView) { + // 2000 is just a magic number that is less than the total number of units, + // but greater than the number of possibly conforming units. Discovered empirically. + if (((MultiAutoCompleteTextView) currentFocus).getAdapter().getCount() > 2000) { + onSearchRequested(); + } else { + ((MultiAutoCompleteTextView) currentFocus).setError(null); + ((MultiAutoCompleteTextView) currentFocus).showDropDown(); + } + } + + } + break; + + case R.id.length: + mDialogUnitCategoryUnit = "m"; + showDialog(DIALOG_UNIT_CATEGORY); + break; + case R.id.weight: + mDialogUnitCategoryUnit = "g"; + showDialog(DIALOG_UNIT_CATEGORY); + break; + case R.id.time: + mDialogUnitCategoryUnit = "hr"; + showDialog(DIALOG_UNIT_CATEGORY); + break; + + // functions + case R.id.sin: + case R.id.cos: + case R.id.tan: + case R.id.atan: + case R.id.log: + case R.id.ln: + sendTextAsSoftKeyboard(((Button) v).getText().toString() + "( ", true); + break; + + // constants + case R.id.pi: + case R.id.light: + case R.id.energy: + sendTextAsSoftKeyboard(((Button) v).getText().toString() + " ", true); + break; + + case R.id.square: + sendTextAsSoftKeyboard("² ", true); + break; + + case R.id.cube: + sendTextAsSoftKeyboard("³ ", true); + break; + + case R.id.milli: + case R.id.kilo: + case R.id.centi: { + String prefix = ((Button) v).getText().toString(); + prefix = prefix.substring(0, prefix.length() - 1); + sendTextAsSoftKeyboard(prefix, false); + } + break; + + + default: + sendTextAsSoftKeyboard(((Button) v).getText().toString(), true); + } + } + + @SuppressLint("NonConstantResourceId") + public boolean onLongClick(View v) { + switch (v.getId()) { + case R.id.unit_entry: { + showDialog(DIALOG_ALL_UNITS); + return true; + } + case R.id.power: { + sendTextAsSoftKeyboard("E"); + return true; + } + case R.id.div: { + sendTextAsSoftKeyboard("|"); + return true; + } + case R.id.dot: { + sendTextAsSoftKeyboard(";"); + return true; + } + case R.id.backspace: { + final View currentFocus = getCurrentFocus(); + if (currentFocus instanceof EditText) { + ((EditText) currentFocus).getEditableText().clear(); + ((EditText) currentFocus).setError(null); + } + return true; + } + case R.id.equal: { + openOptionsMenu(); + } + } + return false; + } + } + + @SuppressLint("NonConstantResourceId") + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + switch (v.getId()) { + case R.id.want: + go(); + final InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(v.getWindowToken(), 0); + + + return true; + } + return false; + } + + private int defaultInputType; + // make sure to reset the input type when losing focus. + private final OnFocusChangeListener inputBoxOnFocusChange = new OnFocusChangeListener() { + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + ((MultiAutoCompleteTextView) v).setInputType(defaultInputType); + } + } + }; + + @SuppressLint("NonConstantResourceId") + public boolean onTouch(View v, MotionEvent event) { + switch (v.getId()) { + + // this is used to prevent the first touch on these editors from triggering the IME soft keyboard. + case R.id.want: + case R.id.have: + if (event.getAction() == MotionEvent.ACTION_DOWN) { + final EditText editor = (EditText) v; + if (v.hasFocus()) { + editor.setInputType(defaultInputType); + v.requestFocus(); + return false; + + } + + editor.setInputType(InputType.TYPE_NULL); + + } + } + + return false; + } + + /** + * Add the units in the given expression(s) to the usage database. + * + * @author steve + */ + private class AddToUsageTask extends AsyncTask { + + @Override + protected Void doInBackground(String... params) { + for (final String param : params) { + UnitUsageDBHelper.logUnitsInExpression(param, getContentResolver()); + } + return null; + } + } + + /** + * Prepare the usage data database, that is load the initial usage data on the first run of the + * application and update the unit classifications as necessary. + * + * @author steve + */ + static class InitialisationTask extends AsyncTask { + @SuppressLint("StaticFieldLeak") + private Units mActivity; + + InitialisationTask(Units activity) { + attach(activity); + } + + public void attach(Units activity) { + mActivity = activity; + } + + public void detach() { + mActivity = null; + } + + @Override + protected void onPreExecute() { + mActivity.showDialog(DIALOG_LOADING_UNITS); + } + + @Override + protected Void doInBackground(Void... params) { + ((Application) mActivity.getApplication()).updateUnitsLocaleIfRequired(); + mActivity.unitUsageDBHelper.updateUnitUsage(); + mActivity.unitUsageDBHelper.loadUnitClassifications(); + + return null; + } + + @Override + protected void onPostExecute(Void result) { + try { + if (mActivity != null) { + mActivity.mInitialisationTask = null; + mActivity.invalidateUnitsList = true; + mActivity.dismissDialog(DIALOG_LOADING_UNITS); + } + } catch (final IllegalArgumentException ie) { + // it's alright if it was dismissed already. + } + } + } +} diff --git a/app/src/main/java/de/buttercookie/units/UnitsContentProvider.java b/app/src/main/java/de/buttercookie/units/UnitsContentProvider.java new file mode 100644 index 00000000..862ca058 --- /dev/null +++ b/app/src/main/java/de/buttercookie/units/UnitsContentProvider.java @@ -0,0 +1,505 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.buttercookie.units; + +import android.app.SearchManager; +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteQueryBuilder; +import android.net.Uri; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +public class UnitsContentProvider extends ContentProvider { + public final static String AUTHORITY = "de.buttercookie.units"; + public final static String + TYPE_HISTORY_ENTRY_ITEM = "vnd.android.cursor.item/vnd.de.buttercookie.units.history_entry", + TYPE_HISTORY_ENTRY_DIR = "vnd.android.cursor.dir/vnd.de.buttercookie.units.history_entry", + TYPE_UNIT_USAGE_ITEM = "vnd.android.cursor.item/vnd.de.buttercookie.units.unit_usage", + TYPE_UNIT_USAGE_DIR = "vnd.android.cursor.dir/vnd.de.buttercookie.units.unit_usage", + TYPE_CLASSIFICATION_ITEM = "vnd.android.cursor.item/vnd.de.buttercookie.units.classification", + TYPE_CLASSIFICATION_DIR = "vnd.android.cursor.dir/vnd.de.buttercookie.units.classification"; + + private final static int + MATCHER_HISTORY_ENTRY_ITEM = 1, + MATCHER_HISTORY_ENTRY_DIR = 2, + MATCHER_UNIT_USAGE_ITEM = 3, + MATCHER_UNIT_USAGE_DIR = 4, + MATCHER_UNIT_USAGE_CONFORM_TOP_DIR = 5, + MATCHER_CLASSIFICATION_ITEM = 6, + MATCHER_CLASSIFICATION_DIR = 7, + MATCHER_CLASSIFICATION_ITEM_FPRINT = 8, + MATCHER_SEARCH_DIR = 9, + MATCHER_SEARCH_ITEM = 10, + MATCHER_UNIT_USAGE_WITH_CLASSIFICATION = 11, + MATCHER_UNIT_USAGE_ITEM_FPRINT = 12, + MATCHER_UNIT_USAGE_NO_UNIT_ALIAS = 13; + + public static final UriMatcher uriMatcher; + + static { + uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + uriMatcher.addURI(AUTHORITY, HistoryEntry.PATH, MATCHER_HISTORY_ENTRY_DIR); + uriMatcher.addURI(AUTHORITY, HistoryEntry.PATH + "/#", MATCHER_HISTORY_ENTRY_ITEM); + + uriMatcher.addURI(AUTHORITY, UsageEntry.PATH, MATCHER_UNIT_USAGE_DIR); + uriMatcher.addURI(AUTHORITY, UsageEntry.PATH + "/#", MATCHER_UNIT_USAGE_ITEM); + + uriMatcher.addURI(AUTHORITY, UsageEntry.PATH_CONFORM_TOP, MATCHER_UNIT_USAGE_CONFORM_TOP_DIR); + uriMatcher.addURI(AUTHORITY, UsageEntry.PATH_WITH_CLASSIFICATION, MATCHER_UNIT_USAGE_WITH_CLASSIFICATION); + uriMatcher.addURI(AUTHORITY, UsageEntry.PATH_NO_UNIT_ALIAS, MATCHER_UNIT_USAGE_NO_UNIT_ALIAS); + uriMatcher.addURI(AUTHORITY, UsageEntry.PATH + "/" + UsageEntry.PATH_BY_FPRINT + "/*", MATCHER_UNIT_USAGE_ITEM_FPRINT); + + uriMatcher.addURI(AUTHORITY, ClassificationEntry.PATH, MATCHER_CLASSIFICATION_DIR); + uriMatcher.addURI(AUTHORITY, ClassificationEntry.PATH + "/#", MATCHER_CLASSIFICATION_ITEM); + uriMatcher.addURI(AUTHORITY, ClassificationEntry.PATH + "/" + ClassificationEntry.PATH_BY_FPRINT + "/*", MATCHER_CLASSIFICATION_ITEM_FPRINT); + + uriMatcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, MATCHER_SEARCH_DIR); + uriMatcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", MATCHER_SEARCH_ITEM); + + } + + public final static String + HISTORY_ENTRY_TABLE_NAME = "history"; + + private static class DatabaseHelper extends SQLiteOpenHelper { + private final static String DB_NAME = "content.db"; + private final static int DB_VER = 1; + + + public DatabaseHelper(Context context) { + super(context, DB_NAME, null, DB_VER); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + HISTORY_ENTRY_TABLE_NAME + " (" + + HistoryEntry._ID + " INTEGER PRIMARY KEY," + + HistoryEntry._HAVE + " TEXT," + + HistoryEntry._WANT + " TEXT," + + HistoryEntry._RESULT + " REAL," + + HistoryEntry._WHEN + " TIMESTAMP DEFAULT CURRENT_TIMESTAMP" + + ");"); + + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + HISTORY_ENTRY_TABLE_NAME); + + } + + } + + private DatabaseHelper dbHelper; + private UnitUsageDBHelper unitDbHelper; + + @Override + public boolean onCreate() { + dbHelper = new DatabaseHelper(getContext()); + unitDbHelper = new UnitUsageDBHelper(getContext()); + return true; + } + + + @Override + public String getType(@NonNull Uri uri) { + switch (uriMatcher.match(uri)) { + case MATCHER_HISTORY_ENTRY_DIR: + return TYPE_HISTORY_ENTRY_DIR; + case MATCHER_HISTORY_ENTRY_ITEM: + return TYPE_HISTORY_ENTRY_ITEM; + + case MATCHER_UNIT_USAGE_DIR: + case MATCHER_UNIT_USAGE_CONFORM_TOP_DIR: + case MATCHER_UNIT_USAGE_WITH_CLASSIFICATION: + case MATCHER_UNIT_USAGE_NO_UNIT_ALIAS: + return TYPE_UNIT_USAGE_DIR; + case MATCHER_UNIT_USAGE_ITEM: + case MATCHER_UNIT_USAGE_ITEM_FPRINT: + return TYPE_UNIT_USAGE_ITEM; + + case MATCHER_CLASSIFICATION_DIR: + return TYPE_CLASSIFICATION_DIR; + case MATCHER_CLASSIFICATION_ITEM: + case MATCHER_CLASSIFICATION_ITEM_FPRINT: + return TYPE_CLASSIFICATION_ITEM; + + default: + throw new IllegalArgumentException("Cannot get type for URI " + uri); + } + } + + @Override + public Uri insert(@NonNull Uri uri, ContentValues cv) { + + Uri newItem; + switch (uriMatcher.match(uri)) { + case MATCHER_HISTORY_ENTRY_DIR: { + final SQLiteDatabase db = dbHelper.getWritableDatabase(); + newItem = ContentUris.withAppendedId(HistoryEntry.CONTENT_URI, db.insert(HISTORY_ENTRY_TABLE_NAME, null, cv)); + } + break; + + case MATCHER_UNIT_USAGE_DIR: { + final SQLiteDatabase db = unitDbHelper.getWritableDatabase(); + newItem = ContentUris.withAppendedId(UsageEntry.CONTENT_URI, db.insert(UnitUsageDBHelper.DB_USAGE_TABLE, null, cv)); + } + break; + + default: + throw new IllegalArgumentException("Cannot insert into " + uri); + } + getContext().getContentResolver().notifyChange(uri, null); + return newItem; + } + + @Override + public Cursor query(@NonNull Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + + long id; + Cursor c; + switch (uriMatcher.match(uri)) { + case MATCHER_HISTORY_ENTRY_DIR: { + final SQLiteDatabase db = dbHelper.getWritableDatabase(); + final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + + qb.setTables(HISTORY_ENTRY_TABLE_NAME); + c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); + } + break; + + case MATCHER_HISTORY_ENTRY_ITEM: { + final SQLiteDatabase db = dbHelper.getWritableDatabase(); + final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + + qb.setTables(HISTORY_ENTRY_TABLE_NAME); + id = ContentUris.parseId(uri); + qb.appendWhere(HistoryEntry._ID + "=" + id); + c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); + } + break; + + case MATCHER_UNIT_USAGE_DIR: { + final SQLiteDatabase db = unitDbHelper.getWritableDatabase(); + final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(UnitUsageDBHelper.DB_USAGE_TABLE); + + c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); + } + break; + + case MATCHER_UNIT_USAGE_ITEM: { + final SQLiteDatabase db = unitDbHelper.getWritableDatabase(); + final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(UnitUsageDBHelper.DB_USAGE_TABLE); + id = ContentUris.parseId(uri); + qb.appendWhere(UsageEntry._ID + "=" + id); + c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); + } + break; + + case MATCHER_UNIT_USAGE_CONFORM_TOP_DIR: { + final SQLiteDatabase db = unitDbHelper.getWritableDatabase(); + + // Below is a sub-query needed in order to have the GROUP BY -reduced items + // be the most used unit with the given fingerprint. The last item in the list is the one that's used. + final String tables = + "(SELECT " + + UnitUsageDBHelper.DB_USAGE_TABLE + "." + UsageEntry._ID + " AS " + UsageEntry._ID + "," + + UnitUsageDBHelper.DB_USAGE_TABLE + "." + UsageEntry._FACTOR_FPRINT + " AS " + UsageEntry._FACTOR_FPRINT + "," + + UsageEntry._USE_COUNT + "," + + "IFNULL(" + ClassificationEntry._DESCRIPTION + + "|| ' (' || " + UsageEntry._UNIT + "|| ')'" + + "," + UsageEntry._UNIT + ") AS " + UsageEntry._UNIT + + " FROM " + UnitUsageDBHelper.DB_USAGE_TABLE + + " LEFT JOIN " + UnitUsageDBHelper.DB_CLASSIFICATION_TABLE + " ON " + + UnitUsageDBHelper.DB_USAGE_TABLE + "." + UsageEntry._FACTOR_FPRINT + + "=" + + UnitUsageDBHelper.DB_CLASSIFICATION_TABLE + "." + ClassificationEntry._FACTOR_FPRINT + + " ORDER BY " + UsageEntry._USE_COUNT + " ASC, " + UnitUsageDBHelper.DB_USAGE_TABLE + "." + UsageEntry._UNIT + " DESC)"; + c = db.query( + tables, + projection, selection, selectionArgs, UsageEntry._FACTOR_FPRINT, UsageEntry._FACTOR_FPRINT + " NOTNULL", sortOrder); + // an extra watcher to keep list in sync with the units + //c.setNotificationUri(getContext().getContentResolver(), UsageEntry.CONTENT_URI); + } + break; + + case MATCHER_UNIT_USAGE_WITH_CLASSIFICATION: { + final SQLiteDatabase db = unitDbHelper.getReadableDatabase(); + final String tbPfxUnit = UnitUsageDBHelper.DB_USAGE_TABLE + "."; + final String tbPfxClass = UnitUsageDBHelper.DB_CLASSIFICATION_TABLE + "."; + + final ArrayList projection2 = new ArrayList<>(Arrays.asList(projection)); + final int idIdx = projection2.indexOf(UsageEntry._ID); + if (idIdx != -1) { + projection2.remove(idIdx); + projection2.add(idIdx, tbPfxUnit + UsageEntry._ID + " AS " + UsageEntry._ID); + } + + c = db.query(UnitUsageDBHelper.DB_USAGE_TABLE + + " LEFT JOIN " + UnitUsageDBHelper.DB_CLASSIFICATION_TABLE + + " ON (" + tbPfxUnit + UsageEntry._FACTOR_FPRINT + "=" + tbPfxClass + ClassificationEntry._FACTOR_FPRINT + ")", + projection2.toArray(new String[]{}), selection, selectionArgs, null, null, UsageEntry.SORT_DEFAULT); + } + break; + + case MATCHER_UNIT_USAGE_NO_UNIT_ALIAS: { + final SQLiteDatabase db = unitDbHelper.getWritableDatabase(); + final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + qb.setTables(UnitUsageDBHelper.DB_USAGE_TABLE); + qb.appendWhere(UsageEntry._IS_UNIT_ALIAS + "=0"); + + c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); + } + break; + + case MATCHER_CLASSIFICATION_DIR: { + final SQLiteDatabase db = unitDbHelper.getReadableDatabase(); + c = db.query(UnitUsageDBHelper.DB_CLASSIFICATION_TABLE, projection, selection, selectionArgs, null, null, sortOrder); + } + break; + + case MATCHER_CLASSIFICATION_ITEM: { + final SQLiteDatabase db = unitDbHelper.getReadableDatabase(); + final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + + qb.setTables(UnitUsageDBHelper.DB_CLASSIFICATION_TABLE); + id = ContentUris.parseId(uri); + qb.appendWhere(ClassificationEntry._ID + "=" + id); + c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder); + } + break; + + case MATCHER_CLASSIFICATION_ITEM_FPRINT: { + final SQLiteDatabase db = unitDbHelper.getReadableDatabase(); + c = db.query(UnitUsageDBHelper.DB_CLASSIFICATION_TABLE, projection, + addExtraWhere(selection, ClassificationEntry._FACTOR_FPRINT + "=?"), + addExtraWhereArgs(selectionArgs, uri.getLastPathSegment()), + null, null, sortOrder); + } + break; + + case MATCHER_UNIT_USAGE_ITEM_FPRINT: { + final SQLiteDatabase db = unitDbHelper.getReadableDatabase(); + c = db.query(UnitUsageDBHelper.DB_USAGE_TABLE, projection, + addExtraWhere(selection, UsageEntry._FACTOR_FPRINT + "=?"), + addExtraWhereArgs(selectionArgs, uri.getLastPathSegment()), + null, null, sortOrder); + } + break; + + case MATCHER_SEARCH_DIR: + case MATCHER_SEARCH_ITEM: { + final SQLiteDatabase db = unitDbHelper.getReadableDatabase(); + final String tbPfxUnit = UnitUsageDBHelper.DB_USAGE_TABLE + "."; + final String tbPfxClass = UnitUsageDBHelper.DB_CLASSIFICATION_TABLE + "."; + // the search manager is expecting specific column names, so we can map them here. + final String[] queryProjection = {tbPfxUnit + UsageEntry._ID + " AS " + UsageEntry._ID, + tbPfxUnit + UsageEntry._UNIT + " AS " + SearchManager.SUGGEST_COLUMN_TEXT_1, + tbPfxClass + ClassificationEntry._DESCRIPTION + " AS " + SearchManager.SUGGEST_COLUMN_TEXT_2, + "\"" + UsageEntry.CONTENT_URI + "\" AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA, + tbPfxUnit + UsageEntry._ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID + }; + final String query = uri.getLastPathSegment().toLowerCase(); + final String[] querySelectionArgs = {"%" + query + "%"}; + c = db.query(UnitUsageDBHelper.DB_USAGE_TABLE + + " LEFT JOIN " + UnitUsageDBHelper.DB_CLASSIFICATION_TABLE + + " ON (" + tbPfxUnit + UsageEntry._FACTOR_FPRINT + "=" + tbPfxClass + ClassificationEntry._FACTOR_FPRINT + ")", + queryProjection, UsageEntry._UNIT + " LIKE ? AND " + UsageEntry._IS_UNIT_ALIAS + "=0", + querySelectionArgs, null, null, UsageEntry.SORT_DEFAULT); + } + break; + + default: + throw new IllegalArgumentException("Cannot query " + uri); + } + + c.setNotificationUri(getContext().getContentResolver(), uri); + return c; + } + + @Override + public int update(@NonNull Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + + int numUpdated; + switch (uriMatcher.match(uri)) { + case MATCHER_HISTORY_ENTRY_DIR: { + final SQLiteDatabase db = dbHelper.getWritableDatabase(); + numUpdated = db.update(HISTORY_ENTRY_TABLE_NAME, values, selection, selectionArgs); + } + break; + + case MATCHER_HISTORY_ENTRY_ITEM: { + final SQLiteDatabase db = dbHelper.getWritableDatabase(); + + final long id = ContentUris.parseId(uri); + if (selection != null) { + selection = "(" + selection + ")" + " AND " + HistoryEntry._ID + "=" + id; + } else { + selection = HistoryEntry._ID + "=" + id; + } + numUpdated = db.update(HISTORY_ENTRY_TABLE_NAME, values, selection, selectionArgs); + } + break; + + case MATCHER_UNIT_USAGE_DIR: { + final SQLiteDatabase db = unitDbHelper.getWritableDatabase(); + numUpdated = db.update(UnitUsageDBHelper.DB_USAGE_TABLE, values, selection, selectionArgs); + } + break; + + case MATCHER_UNIT_USAGE_ITEM: { + final SQLiteDatabase db = unitDbHelper.getWritableDatabase(); + + final long id = ContentUris.parseId(uri); + if (selection != null) { + selection = "(" + selection + ")" + " AND " + UsageEntry._ID + "=" + id; + } else { + selection = UsageEntry._ID + "=" + id; + } + numUpdated = db.update(UnitUsageDBHelper.DB_USAGE_TABLE, values, selection, selectionArgs); + } + break; + + default: + throw new IllegalArgumentException("Cannot update " + uri); + } + getContext().getContentResolver().notifyChange(uri, null); + return numUpdated; + } + + @Override + public int delete(@NonNull Uri uri, String where, String[] whereArgs) { + + int numDeleted; + switch (uriMatcher.match(uri)) { + case MATCHER_HISTORY_ENTRY_DIR: { + final SQLiteDatabase db = dbHelper.getWritableDatabase(); + + numDeleted = db.delete(HISTORY_ENTRY_TABLE_NAME, where, whereArgs); + } + break; + + case MATCHER_HISTORY_ENTRY_ITEM: { + final SQLiteDatabase db = dbHelper.getWritableDatabase(); + final long id = ContentUris.parseId(uri); + if (where != null) { + where = "(" + where + ")" + " AND " + HistoryEntry._ID + "=" + id; + } else { + where = HistoryEntry._ID + "=" + id; + } + numDeleted = db.delete(HISTORY_ENTRY_TABLE_NAME, where, whereArgs); + } + break; + + case MATCHER_UNIT_USAGE_DIR: { + final SQLiteDatabase db = unitDbHelper.getWritableDatabase(); + + numDeleted = db.delete(UnitUsageDBHelper.DB_USAGE_TABLE, where, whereArgs); + } + break; + + case MATCHER_UNIT_USAGE_ITEM: { + final SQLiteDatabase db = unitDbHelper.getWritableDatabase(); + final long id = ContentUris.parseId(uri); + if (where != null) { + where = "(" + where + ")" + " AND " + UsageEntry._ID + "=" + id; + } else { + where = UsageEntry._ID + "=" + id; + } + numDeleted = db.delete(UnitUsageDBHelper.DB_USAGE_TABLE, where, whereArgs); + } + break; + + default: + throw new IllegalArgumentException("Cannot delete " + uri); + } + getContext().getContentResolver().notifyChange(uri, null); + return numDeleted; + } + + + /** + * Adds extra where clauses + * + * @param where + * @param extraWhere + * @return + */ + public static String addExtraWhere(String where, String... extraWhere) { + final String extraWhereJoined = "(" + join(Arrays.asList(extraWhere), ") AND (") + ")"; + return extraWhereJoined + (where != null && where.length() > 0 ? " AND (" + where + ")" : ""); + } + + /** + * Adds in extra arguments to a where query. You'll have to put in the appropriate + * + * @param whereArgs the original whereArgs passed in from the query. Can be null. + * @param extraArgs Extra arguments needed for the query. + * @return + */ + public static String[] addExtraWhereArgs(String[] whereArgs, String... extraArgs) { + final List whereArgs2 = new ArrayList<>(); + if (whereArgs != null) { + whereArgs2.addAll(Arrays.asList(whereArgs)); + } + whereArgs2.addAll(0, Arrays.asList(extraArgs)); + return whereArgs2.toArray(new String[]{}); + } + + + /** + * Join. Why is Collections missing this? + * + * @param list + * @param delim + * @return + * @see + * relevant Stackoverflow question + */ + public static String join(Collection list, String delim) { + + final StringBuilder sb = new StringBuilder(); + + String loopDelim = ""; + + for (final String s : list) { + + sb.append(loopDelim); + sb.append(s); + + loopDelim = delim; + } + + return sb.toString(); + } + +} diff --git a/app/src/main/java/de/buttercookie/units/UnitsMultiAutoCompleteTokenizer.java b/app/src/main/java/de/buttercookie/units/UnitsMultiAutoCompleteTokenizer.java new file mode 100644 index 00000000..bcbaae23 --- /dev/null +++ b/app/src/main/java/de/buttercookie/units/UnitsMultiAutoCompleteTokenizer.java @@ -0,0 +1,49 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.buttercookie.units; + +import static de.buttercookie.units.UnitUsageDBHelper.UNIT_REGEX_PATTERN; + +import android.widget.MultiAutoCompleteTextView; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class UnitsMultiAutoCompleteTokenizer implements MultiAutoCompleteTextView.Tokenizer { + final Pattern unitRegex = Pattern.compile(UNIT_REGEX_PATTERN + "$"); + final Pattern unitRegexEnd = Pattern.compile("^" + UNIT_REGEX_PATTERN); + + public CharSequence terminateToken(CharSequence text) { + return text + " "; + } + + public int findTokenStart(CharSequence text, int cursor) { + final Matcher m = unitRegex.matcher(text.subSequence(0, cursor)); + if (m.find()) { + return m.start(); + } + return cursor; + } + + public int findTokenEnd(CharSequence text, int cursor) { + final Matcher m = unitRegexEnd.matcher(text.subSequence(cursor, text.length() - 1)); + + if (m.find()) { + return m.end(); + } + return cursor; + } +} \ No newline at end of file diff --git a/app/src/main/java/de/buttercookie/units/UsageEntry.java b/app/src/main/java/de/buttercookie/units/UsageEntry.java new file mode 100644 index 00000000..920b9ea7 --- /dev/null +++ b/app/src/main/java/de/buttercookie/units/UsageEntry.java @@ -0,0 +1,45 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.buttercookie.units; + +import android.net.Uri; +import android.provider.BaseColumns; + +public class UsageEntry implements BaseColumns { + public static final String + _UNIT = "unit", + _USE_COUNT = "usecount", + _FACTOR_FPRINT = "factors", + _IS_UNIT_ALIAS = "isunitalias"; + + public static final String + PATH = "units", + PATH_CONFORM_TOP = PATH + "/by_conform", + PATH_WITH_CLASSIFICATION = PATH + "/with_classification", + PATH_NO_UNIT_ALIAS = PATH + "/no_unit_alias", + PATH_BY_FPRINT = "fprint", + SORT_DEFAULT = _USE_COUNT + " DESC"; + + public final static Uri + CONTENT_URI = Uri.parse("content://" + UnitsContentProvider.AUTHORITY + "/" + PATH), + CONTENT_URI_CONFORM_TOP = Uri.parse("content://" + UnitsContentProvider.AUTHORITY + "/" + PATH_CONFORM_TOP), + CONTENT_URI_WITH_CLASSIFICATION = Uri.parse("content://" + UnitsContentProvider.AUTHORITY + "/" + PATH_WITH_CLASSIFICATION), + CONTENT_URI_NO_UNIT_ALIAS = Uri.parse("content://" + UnitsContentProvider.AUTHORITY + "/" + PATH_NO_UNIT_ALIAS); + + public static Uri getEntriesMatchingFprint(String fprint) { + return Uri.withAppendedPath(CONTENT_URI, PATH_BY_FPRINT + "/" + fprint); + } +} diff --git a/app/src/main/java/de/buttercookie/units/ValueGui.java b/app/src/main/java/de/buttercookie/units/ValueGui.java new file mode 100644 index 00000000..89151bce --- /dev/null +++ b/app/src/main/java/de/buttercookie/units/ValueGui.java @@ -0,0 +1,254 @@ +/* + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.buttercookie.units; + +import static net.sourceforge.unitsinjava.UnitList.RoundMode.INTEGER; +import static net.sourceforge.unitsinjava.UnitList.RoundMode.SEPARATED_FRACTION; + +import android.content.Context; + +import net.sourceforge.unitsinjava.Env; +import net.sourceforge.unitsinjava.EvalError; +import net.sourceforge.unitsinjava.Factor; +import net.sourceforge.unitsinjava.Function; +import net.sourceforge.unitsinjava.Ignore; +import net.sourceforge.unitsinjava.UnitList; +import net.sourceforge.unitsinjava.Value; + +/** + * A wrapper of Value to allow for GUI interactions by getting + * rid of the input/output calls. + * + * @author steve + */ +public class ValueGui extends Value { + //===================================================================== + + /** + * Constructs a completely reduced Value from unit expression; + * throws exception on error. + *
+ * If the Value cannot be constructed because of incorrect syntax, + * unknown unit name, etc., throws an exception. + *
(Originally 'processunit'.) + * + * @param s a unit expression. + * @return Value represented by the expression, + * or null if the Value could not be constructed. + */ + //===================================================================== + public static Value fromString(final String s) throws EvalError { + final Value v = parse(s); + v.completereduce(); + return v; + } + + public static Value fromUnicodeString(final String s) throws EvalError { + // closeParens is run twice in order to allow for unicode characters mapping to functions. + final Value v = parse(closeParens(Units.unicodeToAscii(s))); + v.completereduce(); + return v; + } + + /** + * Close any open parentheses. + * + * @param s + * @return + */ + public static String closeParens(String s) { + final StringBuilder sb = new StringBuilder(s); + final int len = s.length(); + int openParen = 0; + int closedParen = 0; + for (int i = 0; i < len; i++) { + final char ch = s.charAt(i); + if (ch == '(') { + openParen++; + } else if (ch == ')') { + closedParen++; + } + } + if (openParen > closedParen) { + for (int i = 0; i < openParen - closedParen; i++) { + sb.append(')'); + } + } + return sb.toString(); + } + + public static Value getReciprocal(Value inval) { + final Value inv = new Value(); + inv.factor = 1 / inval.factor; + inv.numerator = inval.denominator; + inv.denominator = inval.numerator; + + return inv; + } + + public static String getFingerprint(Value val) { + final StringBuilder unitFprint = new StringBuilder(); + for (final Factor f : val.numerator.getFactors()) { + unitFprint.append(f.name); + unitFprint.append(','); + } + unitFprint.append(';'); + for (final Factor f : val.denominator.getFactors()) { + unitFprint.append(f.name); + unitFprint.append(','); + } + return unitFprint.toString(); + } + + public static String getFingerprint(UnitList ul) { + return getFingerprint(ul.value[0]); + } + + //===================================================================== + // convert to Value + //===================================================================== + + /** + * Shows result of conversion of unit expression to unit expression. + * + * @param fromValue 'from' expression converted to completely reduced Value. + * @param toValue 'to' expression converted to completely reduced Value. + * @throws ConversionException + */ + public static double convertNonInteractive + (Value fromValue, Value toValue) throws ConversionException { + //--------------------------------------------------------------- + // If 'toValue' and 'fromValue' are not compatible, + // we may be doing reciprocal conversion. + //--------------------------------------------------------------- + if (!fromValue.isCompatibleWith(toValue, Ignore.DIMLESS)) { + final Value invfrom = getReciprocal(fromValue); // inverse of fromValue, if needed + + //------------------------------------------------------------- + // If reciprocal conversion not wanted, or inverse of 'fromValue' + // is not compatible with 'toValue', we have conformability error. + //------------------------------------------------------------- + if (Env.strict || !toValue.isCompatibleWith(invfrom, Ignore.DIMLESS)) { + throw new ConversionException(); + } + + //------------------------------------------------------------- + // We arrive here to do a reciprocal conversion. + //------------------------------------------------------------- + throw new ReciprocalException(invfrom); + } + + return fromValue.factor / toValue.factor; + } + + //===================================================================== + // convert to Function + //===================================================================== + + /** + * Returns result of conversion of unit expression to function. + * + * @param fromValue 'from' expression converted to completely reduced Value. + * @param fun 'to' function. + */ + public static String convertNonInteractive(Value fromValue, Function fun) throws ConversionException { + try { + fun.applyInverseTo(fromValue); + fromValue.completereduce(); + } catch (final EvalError e) { + final ConversionException ce = new ConversionException(e.getMessage()); + ce.initCause(e); + throw ce; + } + return fun.name + "(" + fromValue.asString() + ")"; + } + + //===================================================================== + // convert a list of units + //===================================================================== + + /** + * Returns result of conversion of unit expression to a list of units. + * + * @param fromExpr The original user-entered 'from' expression + * @param fromValue 'from' expression converted to completely reduced Value. + * @param ul 'to' list of units. + */ + public static String convertNonInteractive(Context context, String fromExpr, Value fromValue, UnitList ul) throws ConversionException { + boolean result = ul.convert(fromExpr, fromValue); + if (!result) { + throw new ConversionException(); + } else { + StringBuilder sb = new StringBuilder(); + boolean printedSomething = false; + String sep = ""; + for (int i = 0; i < ul.n; i++) { + if (ul.result[i] != 0 || + // If everything's zero, always print the last partial result, even if that + // might be zero, too. + i == ul.n - 1 && !printedSomething && ul.roundMode != SEPARATED_FRACTION) { + sb.append(sep); + sb.append(UnitList.showUnit(ul.result[i], ul.unit[i])); + sep = " + "; + printedSomething = true; + } + } + if (ul.roundMode == SEPARATED_FRACTION && + (ul.rounded != 0 || !printedSomething)) { + sb.append(sep); + sb.append(UnitList.showUnit(ul.rounded, ul.unit[ul.n - 1])); + } else if (ul.roundMode == INTEGER && ul.roundAmount != 0) { + sb.append(" ("); + if (ul.roundAmount > 0) { + sb.append(context.getString(R.string.result_rounded_up, ul.unit[ul.n - 1])); + } else { + sb.append(context.getString(R.string.result_rounded_down, ul.unit[ul.n - 1])); + } + sb.append(")"); + } + return sb.toString(); + } + } + + public static class ConversionException extends Exception { + /** + * + */ + private static final long serialVersionUID = 834962768736410424L; + + public ConversionException(String msg) { + super(msg); + } + + public ConversionException() { + super(); + } + } + + public static class ReciprocalException extends ConversionException { + + /** + * + */ + private static final long serialVersionUID = 5809033194217476893L; + public final Value reciprocal; + + public ReciprocalException(Value reciprocal) { + super(); + this.reciprocal = reciprocal; + } + } +} diff --git a/app/src/main/java/net/sourceforge/unitsinjava/Alias.java b/app/src/main/java/net/sourceforge/unitsinjava/Alias.java new file mode 100644 index 00000000..28dbb52d --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/Alias.java @@ -0,0 +1,229 @@ +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2009, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.89.J01 +// 120201 Created for this version. +// 120317 Changed 'define' to replace an earlier definition +// instead of ignoring re-definition. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import java.util.Hashtable; + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class Alias +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * Alias for a UnitList. + */ + + public class Alias extends Entity +{ + //------------------------------------------------------------------- + // Unit list that the alias stands for. + //------------------------------------------------------------------- + final String unitList; + + //------------------------------------------------------------------- + // Table of Aliases. + //------------------------------------------------------------------- + public static Hashtable table = null; + + + //===================================================================== + // Constructor + //===================================================================== + /** + * Constructs an Alias object. + * + * @param name the alias. + * @param ulist unit list that the alias stands for. + * @param loc location where defined. + */ + Alias(final String name, final String ulist, final Location loc) + { + super(name,loc); + unitList = ulist.replace(" ","").replace("\t",""); + } + + + //===================================================================== + // define + //===================================================================== + /** + * Constructs an Alias object from a parsed '!unitlist' statement. + * Checks the name and, if correct, enters the object into Alias table. + * Writes diagnostics to Env.out. + * + * @param name the alias. + * @param ulist the unit list. + * @param loc location where defined. + */ + public static void define + ( final String name, final String ulist, final Location loc) + { + //--------------------------------------------------------------- + // Alias with incorrect name can never be accessed. + //--------------------------------------------------------------- + String diag = Entity.checkName(name); + + if (diag!=null) + { + Env.out.println + (loc.where() + ". Alias '" + name + + "' is ignored. It " + diag + "."); + return; + } + + //--------------------------------------------------------------- + // Install the alias in table. + //--------------------------------------------------------------- + Alias old = table.put(name, new Alias(name,ulist,loc )); + + //--------------------------------------------------------------- + // Write a message if an earlier definition was replaced. + //--------------------------------------------------------------- + if (old!=null) + { + Env.out.println + ("Unit list '" + name + "' defined in " + old.location.where() + + ", is redefined in " + loc.where() + "."); + } + } + + + //===================================================================== + // check + //===================================================================== + /** + * Checks definition of this Alias for correctness. + * Writes diagnostics to 'Env.out'. + * Used by 'check' in 'Tables'. + */ + void check() + { + //--------------------------------------------------------------- + // If requested, write check trace. + //--------------------------------------------------------------- + String where = location.where() + ". "; + if (Env.verbose==2) + Env.out.println(where + "Doing '" + name + "'."); + + //--------------------------------------------------------------- + // Check the unit list for correctness. + //--------------------------------------------------------------- + try + { UnitList ul = new UnitList(unitList); } + catch(EvalError e) + { + Env.out.println + (where + "Unit list '" + name + "'. " + e.getMessage()); + } + + //--------------------------------------------------------------- + // Alias must be different from function, unit, and prefix. + //--------------------------------------------------------------- + Function func = DefinedFunction.table.get(name); + if (func!=null) + Env.out.println + (where + "Unit list '" + name + + "' hides the function defined in " + + func.location.where() + "."); + + Unit unit = Unit.table.get(name); + if (unit!=null) + Env.out.println + (where + "Unit list '" + name + + "' hides the unit defined in " + + unit.location.where() + "."); + + Prefix pref = Prefix.table.get(name); + if (pref!=null) + Env.out.println + (where + "Unit list '" + name + + "' hides the prefix defined in " + + pref.location.where() + "."); + } + + + //===================================================================== + // conformsTo + //===================================================================== + /** + * Checks if unit list defined by this Alias conforms to given Value. + * Fails without a message if the unit list is invalid. + * Used by 'showConformable' in 'Tables'. + * + * @param v the Value to be checked against. + * @return true if this Alias conforms to v, false otherwise. + */ + boolean conformsTo(final Value v) + { + UnitList ul; + try { ul = new UnitList(unitList); } + catch(EvalError e) { return false; } + return ul.value[0].isCompatibleWith(v,Ignore.DIMLESS); + } + + + //===================================================================== + // showdef + //===================================================================== + /** + * If the argument is the name of a unit list, returns its definition. + * Otherwise returns null. + * + * @param name the name to be checked. + * @return the definition or null. + */ + public static String showdef(final String name) + { + Alias alias = Alias.table.get(name); + if (alias!=null) return "unit list, " + alias.unitList; + else return null; + } + + + //===================================================================== + // desc + //===================================================================== + /** + * Returns short description of this Alias. + * To be shown by 'showConformable' and 'showMatching' in 'Tables'. + * + * @return description. + */ + String desc() + { return ("= " + unitList); } +} diff --git a/src/net/sourceforge/unitsinjava/BuiltInFunction.java b/app/src/main/java/net/sourceforge/unitsinjava/BuiltInFunction.java similarity index 79% rename from src/net/sourceforge/unitsinjava/BuiltInFunction.java rename to app/src/main/java/net/sourceforge/unitsinjava/BuiltInFunction.java index d7b7e5ef..af25db54 100644 --- a/src/net/sourceforge/unitsinjava/BuiltInFunction.java +++ b/app/src/main/java/net/sourceforge/unitsinjava/BuiltInFunction.java @@ -1,244 +1,277 @@ -//========================================================================= -// -// Part of units package -- a Java version of GNU Units program. -// -// Units is a program for unit conversion originally written in C -// by Adrian Mariano (adrian@cam.cornell.edu.). -// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, -// 2005, 2006, 2007 by Free Software Foundation, Inc. -// -// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, -// 2009 by Roman R Redziejowski (roman.redz@tele2.se). -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -//------------------------------------------------------------------------- -// -// Change log -// -// 050203 Version 1.84.J05. Do not initialize table. -// 050315 Version 1.84.J07. Changed package name to "units". -// 091024 Version 1.87.J01 -// Used generics for 'table'. -// 091025 Replaced 'Parser.Exception' by 'EvalError'. -// 091027 Added 'sqrt' and 'cuberoot'. -// Used Enum. -// Added check for undefined result of math functions. -// 091031 Replaced 'addtolist' by 'isCompatibleWith'. -// -//========================================================================= - -package net.sourceforge.unitsinjava; - -import java.util.Hashtable; -import java.util.Vector; - - - -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -// -// class BuiltInFunction -// -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -/** - * A built-in function. - */ - - public class BuiltInFunction extends Function -{ - //------------------------------------------------------------------- - /** Table of built-in functions. */ - //------------------------------------------------------------------- - public static Hashtable table = null; - - //------------------------------------------------------------------- - // Argument and result types - //------------------------------------------------------------------- - private type funcType; - - private enum type - { - DIMLESS, // Argument and result are numbers - ANGLEIN, // Argument is a number or an angle, result a number. - ANGLEOUT, // Argument is a number, result an angle. - NOCHECK // The invoked procedure checks types. - } ; - - //------------------------------------------------------------------- - // Because Java does not have pointers, the procedure invoked - // to evaluate the function is identified by enumeration. - //------------------------------------------------------------------- - private proc procID; - - private enum proc {SIN,COS,TAN,LN,LOG,LOG2,EXP,ASIN,ACOS,ATAN,SQRT,CBRT}; - - //------------------------------------------------------------------- - // Radian (an angle is a unit reducible to radians) - //------------------------------------------------------------------- - - private static Unit radian; - - - //===================================================================== - // Construct object for built-in function 'name' - // of type 'funcType' with procedure ID 'procID'. - //===================================================================== - BuiltInFunction(String name, type funcType, proc procID) - { - super(name,new Location()); - this.funcType = funcType; - this.procID = procID; - } - - //===================================================================== - // Insert into the table a built-in function 'name' - // of type 'funcType' with procedure ID 'procID'. - //===================================================================== - private static void insert(String name, type funcType, proc procID) - { table.put(name, new BuiltInFunction(name,funcType,procID)); } - - - //===================================================================== - // Fill table of built-in functions. - //===================================================================== - public static void makeTable() - { - insert("sin", type.ANGLEIN, proc.SIN); - insert("cos", type.ANGLEIN, proc.COS); - insert("tan", type.ANGLEIN, proc.TAN); - insert("ln", type.DIMLESS, proc.LN); - insert("log", type.DIMLESS, proc.LOG); - insert("log2", type.DIMLESS, proc.LOG2); - insert("exp", type.DIMLESS, proc.EXP); - insert("acos", type.ANGLEOUT,proc.ACOS); - insert("atan", type.ANGLEOUT,proc.ATAN); - insert("asin", type.ANGLEOUT,proc.ASIN); - insert("sqrt", type.NOCHECK, proc.SQRT); - insert("cuberoot",type.NOCHECK, proc.CBRT); - - //--------------------------------------------------------------- - // Make sure radian is defined and save its object. - //--------------------------------------------------------------- - Unit r = Unit.table.get("radian"); - if (r!=null) radian = r; - else r = new Unit("radian",new Location(),"!"); - } - - - //===================================================================== - // Apply the function to Value 'v' (with result in 'v'). - //===================================================================== - void applyTo(Value v) - { - //--------------------------------------------------------------- - // Check argument type - //--------------------------------------------------------------- - switch (funcType) - { - case ANGLEIN: //-------- Must be a number or an angle - if (!v.isNumber()) - { - String s = v.asString(); - v.denominator.add(radian); - if (!v.isNumber()) - throw new EvalError("Argument " + s + " of " + - name + " is not a number or angle."); - } - break; - - case ANGLEOUT: //-------- Must be a number - case DIMLESS: - if (!v.isNumber()) - throw new EvalError("Argument " + v.asString() + " of " + - name + " is not a number."); - break; - - case NOCHECK: //-------- No checking - break; - - default: //-------- No other type exists - throw new Error("Program Error; funcType=" + funcType); - } - - //--------------------------------------------------------------- - // Apply the function - //--------------------------------------------------------------- - double arg = v.factor; - switch (procID) - { - case SIN: v.factor = Math.sin(arg); break; - case COS: v.factor = Math.cos(arg); break; - case TAN: v.factor = Math.tan(arg); break; - case LN: v.factor = Math.log(arg); break; - case LOG: v.factor = Math.log(arg)/Math.log(10); break; - case LOG2: v.factor = Math.log(arg)/Math.log(2); break; - case EXP: v.factor = Math.exp(arg); break; - case ASIN: v.factor = Math.asin(arg); break; - case ACOS: v.factor = Math.acos(arg); break; - case ATAN: v.factor = Math.atan(arg); break; - case SQRT: v.root(2); break; - case CBRT: v.root(3); break; - default: throw new Error("Program Error; procID=" + procID); - } - - //--------------------------------------------------------------- - // Check result - //--------------------------------------------------------------- - double result = v.factor; - boolean NaN = Double.isNaN(result) | Double.isInfinite(result); - - switch (funcType) - { - case ANGLEIN: //-------- Must be a number - case DIMLESS: - if (NaN) - throw new EvalError("The result of " + - name + " is undefined."); - break; - - case ANGLEOUT: //-------- Must be an angle - if (NaN) - throw new EvalError("The result of " + - name + " is undefined."); - v.numerator.add(radian); - break; - - case NOCHECK: //-------- No checking - break; - - default: //-------- No other type exists - throw new Error("Program Error; funcType=" + funcType); - } - } - - - //===================================================================== - // These methods, defined in Entity and Function classes, - // are never invoked for a BuiltInFunction. - //===================================================================== - public void applyInverseTo(Value v) - { throw new Error("Program Error"); } - - String showdef() - { throw new Error("Program Error"); } - - void check() - { throw new Error("Program Error"); } - - boolean isCompatibleWith(final Value v) - { throw new Error("Program Error"); } - - String desc() - { throw new Error("Program Error"); } -} +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2009, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J05. +// 050203 Do not initialize table. +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.87.J01 +// 091024 Used generics for 'table'. +// 091025 Replaced 'Parser.Exception' by 'EvalError'. +// 091027 Added 'sqrt' and 'cuberoot'. +// Used Enum. +// Added check for undefined result of math functions. +// 091031 Replaced 'addtolist' by 'isCompatibleWith'. +// +// Version 1.89.J01 +// 120208 Renamed one-argument method 'isCompatibleWith' +// to 'conformsTo' to avoid confusion with two-argument one +// defined in Product and Value. +// 120209 Clarified error messages about undefined result. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import java.util.Hashtable; +import java.util.Vector; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class BuiltInFunction +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * A built-in function. + */ + + public class BuiltInFunction extends Function +{ + //------------------------------------------------------------------- + /** Table of built-in functions. */ + //------------------------------------------------------------------- + public static Hashtable table = null; + + //------------------------------------------------------------------- + /** Argument and result type. */ + //------------------------------------------------------------------- + private type funcType; + + /** Codes for argument and result types. */ + private enum type + { + DIMLESS, // Argument and result are numbers + ANGLEIN, // Argument is a number or an angle, result a number. + ANGLEOUT, // Argument is a number, result an angle. + NOCHECK // The invoked procedure checks types. + } ; + + //------------------------------------------------------------------- + /** Procedure ID. + * Because Java does not have pointers, the procedure invoked + * to evaluate the function is identified by enumeration. */ + //------------------------------------------------------------------- + private proc procID; + + /** Procedure codes. */ + private enum proc {SIN,COS,TAN,LN,LOG,LOG2,EXP,ASIN,ACOS,ATAN,SQRT,CBRT}; + + //------------------------------------------------------------------- + /** Radian (an angle is a unit reducible to radians). */ + //------------------------------------------------------------------- + private static Unit radian; + + + //===================================================================== + // Constructor + //===================================================================== + /** + * Constructs object for a built-in function. + * + * @param name function name. + * @param funcType code for argument and result type. + * @param procID procedure code. + */ + BuiltInFunction(String name, type funcType, proc procID) + { + super(name,new Location()); + this.funcType = funcType; + this.procID = procID; + } + + //===================================================================== + // insert + //===================================================================== + /** + * Inserts into the table a built-in function. + * + * @param name function name. + * @param funcType code for argument and result type. + * @param procID procedure code. + */ + private static void insert(String name, type funcType, proc procID) + { table.put(name, new BuiltInFunction(name,funcType,procID)); } + + + //===================================================================== + // makeTable + //===================================================================== + /** + * Fills the table of built-in functions. + */ + public static void makeTable() + { + insert("sin", type.ANGLEIN, proc.SIN); + insert("cos", type.ANGLEIN, proc.COS); + insert("tan", type.ANGLEIN, proc.TAN); + insert("ln", type.DIMLESS, proc.LN); + insert("log", type.DIMLESS, proc.LOG); + insert("log2", type.DIMLESS, proc.LOG2); + insert("exp", type.DIMLESS, proc.EXP); + insert("acos", type.ANGLEOUT,proc.ACOS); + insert("atan", type.ANGLEOUT,proc.ATAN); + insert("asin", type.ANGLEOUT,proc.ASIN); + insert("sqrt", type.NOCHECK, proc.SQRT); + insert("cuberoot",type.NOCHECK, proc.CBRT); + + //--------------------------------------------------------------- + // Make sure radian is defined and save its object. + //--------------------------------------------------------------- + Unit r = Unit.table.get("radian"); + if (r!=null) radian = r; + else r = new Unit("radian",new Location(),"!"); + } + + + //===================================================================== + // applyTo + //===================================================================== + /** + * Applies this function to a given Value, + * and changes the Value to the result. + * + * @param v the argument and result. + */ + void applyTo(Value v) + { + //--------------------------------------------------------------- + // Check argument type + //--------------------------------------------------------------- + switch (funcType) + { + case ANGLEIN: //-------- Must be a number or an angle + if (!v.isNumber()) + { + String s = v.asString(); + v.denominator.add(radian); + if (!v.isNumber()) + throw new EvalError("Argument '" + s + "' of '" + + name + "' is not a number or angle."); + } + break; + + case ANGLEOUT: //-------- Must be a number + case DIMLESS: + if (!v.isNumber()) + throw new EvalError("Argument '" + v.asString() + "' of '" + + name + "' is not a number."); + break; + + case NOCHECK: //-------- No checking + break; + + default: //-------- No other type exists + throw new Error("Program Error; funcType=" + funcType); + } + + //--------------------------------------------------------------- + // Apply the function + //--------------------------------------------------------------- + double arg = v.factor; + switch (procID) + { + case SIN: v.factor = Math.sin(arg); break; + case COS: v.factor = Math.cos(arg); break; + case TAN: v.factor = Math.tan(arg); break; + case LN: v.factor = Math.log(arg); break; + case LOG: v.factor = Math.log(arg)/Math.log(10); break; + case LOG2: v.factor = Math.log(arg)/Math.log(2); break; + case EXP: v.factor = Math.exp(arg); break; + case ASIN: v.factor = Math.asin(arg); break; + case ACOS: v.factor = Math.acos(arg); break; + case ATAN: v.factor = Math.atan(arg); break; + case SQRT: v.root(2); break; + case CBRT: v.root(3); break; + default: throw new Error("Program Error; procID=" + procID); + } + + //--------------------------------------------------------------- + // Check result + //--------------------------------------------------------------- + double result = v.factor; + boolean NaN = Double.isNaN(result) | Double.isInfinite(result); + + switch (funcType) + { + case ANGLEIN: //-------- Must be a number + case DIMLESS: + if (NaN) + throw new EvalError("The result of " + + name + "(" + arg + ") is undefined."); + break; + + case ANGLEOUT: //-------- Must be an angle + if (NaN) + throw new EvalError("The result of " + + name + "(" + arg + ") is undefined."); + v.numerator.add(radian); + break; + + case NOCHECK: //-------- No checking + break; + + default: //-------- No other type exists + throw new Error("Program Error; funcType=" + funcType); + } + } + + + //===================================================================== + // These methods, defined in Entity and Function classes, + // are never invoked for a BuiltInFunction. + //===================================================================== + public void applyInverseTo(Value v) + { throw new Error("Program Error"); } + + String showdef() + { throw new Error("Program Error"); } + + void check() + { throw new Error("Program Error"); } + + boolean conformsTo(final Value v) + { throw new Error("Program Error"); } + + String desc() + { throw new Error("Program Error"); } +} diff --git a/src/net/sourceforge/unitsinjava/CommandArgs.java b/app/src/main/java/net/sourceforge/unitsinjava/CommandArgs.java similarity index 82% rename from src/net/sourceforge/unitsinjava/CommandArgs.java rename to app/src/main/java/net/sourceforge/unitsinjava/CommandArgs.java index 0fca3ab6..73de697e 100644 --- a/src/net/sourceforge/unitsinjava/CommandArgs.java +++ b/app/src/main/java/net/sourceforge/unitsinjava/CommandArgs.java @@ -1,329 +1,347 @@ -//========================================================================= -// -// Part of units package -- a Java version of GNU Units program. -// -// Units is a program for unit conversion originally written in C -// by Adrian Mariano (adrian@cam.cornell.edu.). -// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, -// 2005, 2006, 2007 by Free Software Foundation, Inc. -// -// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, -// 2009 by Roman R Redziejowski (roman.redz@tele2.se). -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -//------------------------------------------------------------------------- -// -// Change log -// -// 050203 Version 1.84.J05. User interface redesigned. -// Instead of cycling through options, you ask directly -// if a given option was specified and how many times. -// You can directly get a list of all values specified -// with that option. -// Attribute 'public' removed from all methods. -// 050315 Version 1.84.J07. Changed package name to "units". -// 091024 Version 1.87.J01. -// Used generics and modernized access to options. -// 091103 Corrected bug: exception on empty string as option argument. -// 091104 'String.isEmpty' not accepted by JDK1.5. Changed to length==0. -// -//========================================================================= - -package net.sourceforge.unitsinjava; - -import java.util.Vector; - - - -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -// -// Class CommandArgs -// -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -/** - * Object-oriented counterpart of C procedure 'getopt': - * an object of class CommandArgs represents parsed argument list - * of a command (the 'argv' parameter to 'main'). - *

- * The list is supposed to follow POSIX conventions, - * (IEEE Standard 1003.1, 2004 Edition, Chapter 12), namely: - *

    - *
  • The list consist of options and/or arguments. - *
  • The options precede the arguments. - *
  • An option is a hyphen followed by a single alphanumeric character, - * like this: -o. - *
  • An option may require a value, which may appear immediately after the - * option character, for example: -ovalue, - * or as the following item in the list, for example: -o value. - *
  • Options that do not require value can be grouped after a hyphen, so, - * for example, -rst is equivalent to -r -s -t. - *
  • Options can appear in any order; thus -rst - * is equivalent to -trs. - *
  • Options can appear multiple times. - *
  • The -- item is neither an option nor an argument. - * It terminates options. - * Anything that follows is an argument, even if it begins with a hyphen. - *
  • A hyphen alone is an argument. It terminates options. - *
- * A CommandArgs object is constructed by parsing the argument list - * according to instructions supplied to the constructor. - * The options and arguments can be then obtained by invoking - * methods on that object. - */ - - -public class CommandArgs -{ - //------------------------------------------------------------------- - // Option letters in order of appearance. - // Note that options with argument may have multiple occurrences. - //------------------------------------------------------------------- - private String letters; - - //------------------------------------------------------------------- - // Arguments specified with letters. - // Null for argument-less options. - //------------------------------------------------------------------- - private Vector optArgs = new Vector(); - - //------------------------------------------------------------------- - // Positional arguments. - //------------------------------------------------------------------- - private Vector args = new Vector(); - - //------------------------------------------------------------------- - // Error count. - //------------------------------------------------------------------- - private int errors = 0; - - //------------------------------------------------------------------- - /** Construct CommandArgs object from an argument list 'argv'. - *
- * @param argv Argument list, as passed to the program. - * @param options String consisting of option letters for options without argument. - * @param optionsWithArg String consisting of option letters for options with argument. - * @param minargs Minimum number of arguments. - * @param maxargs Maximum number of arguments. - */ - //------------------------------------------------------------------- - public CommandArgs - ( final String[] argv, - final String options, - final String optionsWithArg, - int minargs, - int maxargs) - { - int i = 0; - StringBuffer opts = new StringBuffer(); - - //--------------------------------------------------------------- - // Examine elements of argv as long as they specify options. - //--------------------------------------------------------------- - while(i=0) - { - //----------------------------------------------------------- - // Option with argument - //----------------------------------------------------------- - opts.append(c); - if (elem.length()>2) - { - // option's argument in the same element - optArgs.addElement(elem.substring(2,elem.length())); - i++; - } - - else - { - // option's argument in next element - i++; - if (i=0) - { - opts.append(c); - optArgs.addElement(null); - } - else - { - System.err.println("Unrecognized option -" + c + "."); - errors++; - break; - } - } - i++; - } - } - - letters = opts.toString(); - - //--------------------------------------------------------------- - // The remaining elements of argv are positional arguments. - //--------------------------------------------------------------- - while(imaxargs) - { - System.err.println("Too many arguments."); - errors++; - } - } - - //------------------------------------------------------------------- - // Access to options - //------------------------------------------------------------------- - /** - * Checks if a given option was specified. - * - * @param c Option letter. - * @return true if the option is specified, false otherwise.. - */ - public boolean opt(char c) - { return letters.indexOf(c)>=0; } - - /** - * Gets argument of a given option. - * Returns null if the option is not specified or does not have argument. - * If option was specified several times, returns the first occurrence. - * - * @param c Option letter. - * @return value of the i-th option. - */ - public String optArg(char c) - { - int i = letters.indexOf(c); - return i<0? null : optArgs.elementAt(i); - } - - /** - * Gets arguments of a given option. - * Returns a vector of arguments for an option specified repeatedly- - * Returns empty vector if the option is not specified or does not have argument. - * - * @param c Option letter. - * @return value of the i-th option. - */ - public Vector optArgs(char c) - { - Vector result = new Vector(); - for (int i=0;i0≤i<nOpts()). - * @return the i-th argument. - */ - public String arg(int i) - { return args.elementAt(i); } - - /** - * Gets the argument vector. - * - * @return Vector of arguments. - */ - public Vector args() - { return args; } - - //------------------------------------------------------------------- - // Error count - //------------------------------------------------------------------- - /** - * Gets number of errors detected when parsing the argument list. - * - * @return Number of errors. - */ - public int nErrors() - { return errors; } +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J05. User interface redesigned. +// 050203 Instead of cycling through options, you ask directly +// if a given option was specified and how many times. +// You can directly get a list of all values specified +// with that option. +// Attribute 'public' removed from all methods. +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.87.J01. +// 091024 Used generics and modernized access to options. +// 091103 Corrected bug: exception on empty string as option argument. +// 091104 'String.isEmpty' not accepted by JDK1.5. Changed to length==0. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import java.util.Vector; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// Class CommandArgs +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * Object-oriented counterpart of C procedure 'getopt'. + * An object of class CommandArgs represents parsed argument list + * of a command (the 'argv' parameter to 'main'). + *

+ * The list is supposed to follow POSIX conventions, + * (IEEE Standard 1003.1, 2004 Edition, Chapter 12), namely: + *

    + *
  • The list consist of options and/or arguments. + *
  • The options precede the arguments. + *
  • An option is a hyphen followed by a single alphanumeric character, + * like this: -o. + *
  • An option may require a value, which may appear immediately after the + * option character, for example: -ovalue, + * or as the following item in the list, for example: -o value. + *
  • Options that do not require value can be grouped after a hyphen, so, + * for example, -rst is equivalent to -r -s -t. + *
  • Options can appear in any order; thus -rst + * is equivalent to -trs. + *
  • Options can appear multiple times. + *
  • The -- item is neither an option nor an argument. + * It terminates options. + * Anything that follows is an argument, even if it begins with a hyphen. + *
  • A hyphen alone is an argument. It terminates options. + *
+ *

+ * A CommandArgs object is constructed by parsing the argument list + * according to instructions supplied to the constructor. + * The options and arguments can be then obtained by invoking + * methods on that object. + */ + + +public class CommandArgs +{ + //------------------------------------------------------------------- + /** Option letters in order of appearance. + * Note that options with argument may have multiple occurrences. */ + //------------------------------------------------------------------- + private String letters; + + //------------------------------------------------------------------- + /** Arguments specified with letters. + * Null for argument-less options. */ + //------------------------------------------------------------------- + private Vector optArgs = new Vector(); + + //------------------------------------------------------------------- + /** Positional arguments. */ + //------------------------------------------------------------------- + private Vector args = new Vector(); + + //------------------------------------------------------------------- + /** Error count. */ + //------------------------------------------------------------------- + private int errors = 0; + + //===================================================================== + // Constructor + //===================================================================== + /** + * Constructs CommandArgs object from an argument list 'argv'. + * + * @param argv Argument list, as passed to the program. + * @param options String consisting of option letters for options without argument. + * @param optionsWithArg String consisting of option letters for options with argument. + * @param minargs Minimum number of arguments. + * @param maxargs Maximum number of arguments. + */ + public CommandArgs + ( final String[] argv, + final String options, + final String optionsWithArg, + int minargs, + int maxargs) + { + int i = 0; + StringBuffer opts = new StringBuffer(); + + //--------------------------------------------------------------- + // Examine elements of argv as long as they specify options. + //--------------------------------------------------------------- + while(i=0) + { + //----------------------------------------------------------- + // Option with argument + //----------------------------------------------------------- + opts.append(c); + if (elem.length()>2) + { + // option's argument in the same element + optArgs.addElement(elem.substring(2,elem.length())); + i++; + } + + else + { + // option's argument in next element + i++; + if (i=0) + { + opts.append(c); + optArgs.addElement(null); + } + else + { + System.err.println("Unrecognized option -" + c + "."); + errors++; + break; + } + } + i++; + } + } + + letters = opts.toString(); + + //--------------------------------------------------------------- + // The remaining elements of argv are positional arguments. + //--------------------------------------------------------------- + while(imaxargs) + { + System.err.println("Too many arguments."); + errors++; + } + } + + //===================================================================== + // opt + //===================================================================== + /** + * Checks if a given option was specified. + * + * @param c Option letter. + * @return true if the option is specified, false otherwise.. + */ + public boolean opt(char c) + { return letters.indexOf(c)>=0; } + + //===================================================================== + // optArg + //===================================================================== + /** + * Gets argument of a given option. + * Returns null if the option is not specified or does not have argument. + * If option was specified several times, returns the first occurrence. + * + * @param c Option letter. + * @return value of the i-th option. + */ + public String optArg(char c) + { + int i = letters.indexOf(c); + return i<0? null : optArgs.elementAt(i); + } + + //===================================================================== + // optArgs + //===================================================================== + /** + * Gets arguments of a given option. + * Returns a vector of arguments for an option specified repeatedly- + * Returns empty vector if the option is not specified or does not have argument. + * + * @param c Option letter. + * @return value of the i-th option. + */ + public Vector optArgs(char c) + { + Vector result = new Vector(); + for (int i=0;i0≤i<nOpts()). + * @return the i-th argument. + */ + public String arg(int i) + { return args.elementAt(i); } + + //===================================================================== + // args + //===================================================================== + /** + * Gets the argument vector. + * + * @return Vector of arguments. + */ + public Vector args() + { return args; } + + //===================================================================== + // nErrors + //===================================================================== + /** + * Gets number of errors detected when parsing the argument list. + * + * @return Number of errors. + */ + public int nErrors() + { return errors; } } \ No newline at end of file diff --git a/app/src/main/java/net/sourceforge/unitsinjava/ComputedFunction.java b/app/src/main/java/net/sourceforge/unitsinjava/ComputedFunction.java new file mode 100644 index 00000000..e074bdbe --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/ComputedFunction.java @@ -0,0 +1,456 @@ +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2011, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.86.J01. +// 061229 Corrected test for 'verbose'. +// 070102 Suppress printing "\tDefinition :" for compact output. +// +// Version 1.87.J01. +// 091024 Used generics for 'list' in 'addtolist'. +// 091025 Replaced 'Parser.Exception' by 'EvalError'. +// 091031 Moved definition of Ignore to Factor. +// Replaced 'addtolist' by 'isCompatibleWith'. +// +// Version 1.88.J02 +// 110403 Removed optional "\tDefinition :" from 'showdef'. +// +// Version 1.89.J01 +// 120201 Adapted to use with File Parser: +// removed method 'accept' and added 'define'. +// 120208 Renamed one-argument method 'isCompatibleWith' +// to 'conformsTo' to avoid confusion with two-argument one +// defined in Product and Value. +// 120209 Definition of Ignore moved to separate file: +// replaced 'Factor.Ignore' by 'Ignore'. +// 120311 Suppress error messages from 'conformsTo'. +// 120313 Added 'location.where' to messages from 'check'. +// 120316 Corrected message about abandoned inverse check. +// 120317 Changed 'define' to replace an earlier definition +// instead of ignoring re-definition. +// 120318 In 'check': check name conflict using 'checkHiding'. +// 120402 In 'check': added checking of inverse parameter. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import java.util.Hashtable; +import java.util.Vector; + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class ComputedFunction +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * A function defined by an expression (a 'nonlinear unit'.) + */ + + public class ComputedFunction extends DefinedFunction +{ + private FuncDef forward; // Forward definition + private FuncDef inverse; // Inverse definition + + + //===================================================================== + // Constructor + //===================================================================== + /** + * Constructs ComputedFunction object. + * + * @param nam function name. + * @param loc location where defined. + * @param fdf forward function definition. + * @param fpar forward function parameter. + * @param fdim dimension of forward function parameter. + * @param idf inverse function definition. + * @param ipar inverse function parameter. + * @param idim dimension of inverse function parameter. + */ + ComputedFunction + ( String nam, Location loc, + String fpar, String fdf, String fdim, + String ipar, String idf, String idim) + { + super(nam,loc); + forward = new FuncDef(fpar,fdf,fdim); + inverse = new FuncDef(ipar,idf,idim); + } + + + //===================================================================== + // define + //===================================================================== + /** + * Builds FunctionTable entry from a parsed definition. + * The definition is parsed as follows: + *

+   *    name(param)  [fwddim;invdim] fwddef ; invdef
+   *  
+ * + * @param name function name. + * @param param parameter. + * @param fwddim dimension of forward function parameter. + * @param invdim dimension of inverse function parameter. + * @param fwddef forward function definition. + * @param invdef inverse function definition. + * @param loc location where defined. + */ + public static void define + ( final String name, final String param, + final String fwddim, final String invdim, + final String fwddef, final String invdef, + final Location loc) + { + //--------------------------------------------------------------- + // Function with incorrect name can never be accessed. + //--------------------------------------------------------------- + String diag = Entity.checkName(name); + + if (diag!=null) + { + Env.out.println + (loc.where() + ". Function '" + name + + "' is ignored. Its name " + diag + "."); + return; + } + + //--------------------------------------------------------------- + // Install the function in table. + //--------------------------------------------------------------- + Function old = table.put(name,new ComputedFunction + (name,loc, + param,fwddef,fwddim, + name,invdef,invdim)); + + //--------------------------------------------------------------- + // Write a message if an earlier definition is replaced. + //--------------------------------------------------------------- + if (old!=null) + { + Env.out.println + ("Function '" + name + "' defined in " + old.location.where() + + ", is redefined in " + loc.where() + "."); + } + } + + + //===================================================================== + // applyTo + //===================================================================== + /** + * Applies this function to a given Value, + * and changes the Value to the result. + * + * @param v the argument and result. + */ + void applyTo(Value v) + { forward.applyTo(v,""); } + + + //===================================================================== + // applyInverseTo + //===================================================================== + /** + * Applies the inverse of this function to a given Value, + * and changes the Value to the result. + * + * @param v the argument and result. + */ + public void applyInverseTo(Value v) + { inverse.applyTo(v,"~"); } + + //===================================================================== + // showdef + //===================================================================== + /** + * Returns definition of this function. + * (Originally 'showfuncdef'.) + * + * @return formatted definition of this function. + */ + String showdef() + { return name + "(" + forward.param + ") = " + forward.def; } + + + //===================================================================== + // check + //===================================================================== + /** + * Checks definition of this function for correctness. + * Writes diagnostics to 'Env.out'. + * Used by 'check' in 'Tables'. + */ + void check() + { + if (Env.verbose==2) + Env.out.println(location.where() + ". Doing function " + name); + + //--------------------------------------------------------------- + // Function name must be different from that of alias, unit, + // and prefix. Conflict with alias is checked by Alias. + // We check here for conflict with unit and prefix. + //--------------------------------------------------------------- + checkHiding(); + + //--------------------------------------------------------------- + // Parse dimension of inverse argument to check it. + // Notice that this is dimension of function's result. + // Null dimension means no check. + //--------------------------------------------------------------- + if (inverse.dimen!=null) + { + try + { Value.parse(inverse.dimen); } + catch (EvalError e) + { + Env.out.println + (location.where() + ". Dimension '" + inverse.dimen + + "' specified for argument of '~" + name + + "' is invalid. " + e.getMessage() + "."); + } + } + + //--------------------------------------------------------------- + // Parse dimension of forward argument to check it, + // and save its value for checking of inverse. + // Null dimension means no check. + //--------------------------------------------------------------- + Value v = new Value(); // Set 'v' to number 1 + + if (forward.dimen!=null) + { + try + { + v = Value.parse(forward.dimen); + v.completereduce(); + } + catch (EvalError e) + { + Env.out.println + (location.where() + ". Dimension '" + forward.dimen + + "' specified for argument of '" + name + + "' is invalid. " + e.getMessage() + "."); + return; + } + } + + //--------------------------------------------------------------- + // If inverse not defined: issue warning and return. + //--------------------------------------------------------------- + if (inverse.def==null) + { + Env.out.println + (location.where() + ". Warning: no inverse for function '" + + name + "'."); + return; + } + + //--------------------------------------------------------------- + // Forward argument is correct and inverse is defined. + // We proceed to check the inverse. + // The Value 'v' is now 1 with the dimension of forward argument. + // (Number 1 was assumed for null dimension.) + // Set 'v' to an arbitrarily chosen value + // where we are going to check the inverse. + //--------------------------------------------------------------- + v.factor *= 7; + + //--------------------------------------------------------------- + // Save 'v' for future comparison and apply the function to it. + // If the function cannot be evaluated for 'v', issue a warning + // and return. (That function cannot be evaluated does not mean + // it is faulty. The argument 7 may be out of accepted range.) + //--------------------------------------------------------------- + Value saved = new Value(v); + + try + { applyTo(v); } + catch (EvalError e) + { + Env.out.println + (location.where() + + ". Warning: inverse was not checked for '" + + name + "'."); + return; + } + + //--------------------------------------------------------------- + // 'v' contains now the evaluated function. Apply the function's + // inverse to it and issue a message if the result differs from + // the saved value by more than a factor of 10^-12. + // Issue an error message if the inverse cannot be evaluated. + //--------------------------------------------------------------- + try + { + applyInverseTo(v); + v.div(saved); + v.completereduce(); + double delta = v.factor-1; + if (!v.isNumber() || delta<-1e-12 || delta>1e-12) + Env.out.println + (location.where() + + ". Inverse is not the inverse for function '" + + name + "'."); + } + catch (EvalError e) + { + Env.out.println + (location.where() + ". Error in '~" + name + "(" + + inverse.param + ")' defined as '" + inverse.def + "'."); + } + } + + public Value getConformability() { + return inverse.dimen != null ? Value.fromString(inverse.dimen) : null; + } + + + //===================================================================== + // conformsTo + //===================================================================== + /** + * Checks if result of this function conforms to Value 'v'. + * Used by 'showConformable' in 'Tables'. + * + * @param v the Value to be checked against. + * @return true if this Entity conforms to v, false otherwise. + */ + boolean conformsTo(final Value v) + { + //--------------------------------------------------------------- + // Dimension of the result is the same as that + // of argument to the function's inverse. + // Treat null as "unknown". + //--------------------------------------------------------------- + if (inverse.dimen==null) return false; + try + { + Value thisvalue = Value.parse(inverse.dimen); + thisvalue.completereduce(); + return thisvalue.isCompatibleWith(v,Ignore.DIMLESS); + } + catch(EvalError e) + { return false; } + } + + + //===================================================================== + // desc + //===================================================================== + /** + * Returns short description of this function + * to be shown by 'showConformable' and 'showMatching' in 'Tables'. + * + * @return description. + */ + String desc() + { return ""; } + + + //HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + // + // Inner class FuncDef + // + //HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + /** + * Holds details of a forward or inverse definition of the function. + */ + private class FuncDef + { + String param; // Parameter + String def; // Definition + String dimen; // Dimension of parameter + + //=================================================================== + // Constructor + //=================================================================== + /** + * Constructs a FuncDef object. + * + * @param param parameter + * @param def function definition + * @param dimen dimension of the paremeter + */ + FuncDef(String param, String def, String dimen) + { + this.param = param; + this.def = def; + this.dimen = dimen; + } + + //=================================================================== + // applyTo + //=================================================================== + /** + * Applies the function defined by this object to a given Value, + * and changes the Value to the result. + * + * @param v the argument and result. + */ + void applyTo(Value v, String inv) + { + v.completereduce(); + if (dimen!=null) + { + Value dim; + try + { dim = Value.parse(dimen); } + catch (EvalError e) + { + throw new EvalError("Invalid argument dimension, " + + dimen + ", of function " + inv + name + + ". " + e.getMessage()); + } + dim.completereduce(); + if (!dim.isCompatibleWith(v,Ignore.NONE)) + throw new EvalError("Argument " + v.asString() + + " of function " + inv + name + + " is not conformable to " + + dim.asString() + "."); + } + + Value result; + try + { result = Value.parse(def,param,v); } + catch (EvalError e) + { + throw new EvalError("Invalid application of function '" + + inv + name + "'. " + e.getMessage()); + } + + v.copyFrom(result); + } + + } // end FuncDef +} diff --git a/src/net/sourceforge/unitsinjava/CurrentRule.java b/app/src/main/java/net/sourceforge/unitsinjava/CurrentRule.java similarity index 100% rename from src/net/sourceforge/unitsinjava/CurrentRule.java rename to app/src/main/java/net/sourceforge/unitsinjava/CurrentRule.java diff --git a/app/src/main/java/net/sourceforge/unitsinjava/DefinedFunction.java b/app/src/main/java/net/sourceforge/unitsinjava/DefinedFunction.java new file mode 100644 index 00000000..bc3ac3df --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/DefinedFunction.java @@ -0,0 +1,175 @@ +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2009, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J05. +// 050203 Do not initialize table. +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.87.J01. +// 091024 Used generics for 'table'. +// +// Version 1.89.J01. +// 120311 Added static method 'showdef'. +// 120312 Moved here from Value the method 'convert'. +// 120318 Added method 'checkHiding'. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import java.util.Hashtable; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class DefinedFunction +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * A defined function. + * Base class for ComputedFunction and TabularFunction. + */ + +public abstract class DefinedFunction extends Function +{ + //------------------------------------------------------------------- + /** Table of defined functions. */ + //------------------------------------------------------------------- + public static Hashtable table = null; + + public abstract Value getConformability(); + + //===================================================================== + // Constructor + //===================================================================== + /** + * Constructs DefinedFunction object defined in a units file. + * + * @param name function name. + * @param loc location in the file. + */ + DefinedFunction(String name, Location loc) + { super(name,loc); } + + + //===================================================================== + // checkHiding + //===================================================================== + /** + * Prints a message if name of this function is identical + * with the name of a unit or prefix. + * Used by method 'check' in the subclasses. + */ + void checkHiding() + { + Unit unit = Unit.table.get(name); + if (unit!=null) + Env.out.println + (location.where() + ". Function '" + name + + "' hides the unit defined in " + + unit.location.where() + "."); + + Prefix pref = Prefix.table.get(name); + if (pref!=null) + Env.out.println + (location.where() + ". Function '" + name + + "' hides the prefix defined in " + + pref.location.where() + "."); + } + + + //===================================================================== + // showdef + //===================================================================== + /** + * If the argument is the name of a defined function + * returns its definition followed by equal sign. + * Otherwise returns null. + * + * @param name the name to be checked. + * @return the definition or null. + */ + public static String showdef(final String name) + { + Function func = DefinedFunction.table.get(name); + if (func!=null) return func.showdef(); + else return null; + } + + + //===================================================================== + // convert + //===================================================================== + /** + * Converts a unit expression to this function. + * It shows the argument of the function that + * produces the value specified by the expression. + * The result is written to Env.out. + * Writes error message to Env.out if conversion fails. + * + * @param fromExpr the expression. + * @param fromValue the expression evaluated to completely reduced Value. + * @return true if conversion was successful, false otherwise. + */ + boolean convert(String fromExpr, Value fromValue) + { + //--------------------------------------------------------------- + // The result is obtained by applying inverse + // of this function to the given value. + //--------------------------------------------------------------- + try + { + applyInverseTo(fromValue); + fromValue.completereduce(); + } + catch(EvalError e) + { + Env.out.println(e.getMessage()); + return false; + } + + //--------------------------------------------------------------- + // Write out the result formatted as required. + //--------------------------------------------------------------- + if (Env.verbose==2) + Env.out.print("\t" + fromExpr + " = " + name + "("); + else + if (Env.verbose==1) Env.out.print("\t"); + Env.out.print(fromValue.asString()); + if (Env.verbose==2) + Env.out.print(")"); + Env.out.print("\n"); + return true; + } + + +} \ No newline at end of file diff --git a/src/net/sourceforge/unitsinjava/Entity.java b/app/src/main/java/net/sourceforge/unitsinjava/Entity.java similarity index 50% rename from src/net/sourceforge/unitsinjava/Entity.java rename to app/src/main/java/net/sourceforge/unitsinjava/Entity.java index 6a5c74c3..d6db17cd 100644 --- a/src/net/sourceforge/unitsinjava/Entity.java +++ b/app/src/main/java/net/sourceforge/unitsinjava/Entity.java @@ -1,101 +1,165 @@ -//========================================================================= -// -// Part of units package -- a Java version of GNU Units program. -// -// Units is a program for unit conversion originally written in C -// by Adrian Mariano (adrian@cam.cornell.edu.). -// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, -// 2005, 2006, 2007 by Free Software Foundation, Inc. -// -// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, -// 2009 by Roman R Redziejowski (roman.redz@tele2.se). -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -//------------------------------------------------------------------------- -// -// Change log -// -// 050315 Version 1.84.J07. Changed package name to "units". -// 091024 Version 1.87.J01. -// Redefined 'insertAlph' using generics. -// 091031 Replaced 'addtolist' by 'isCompatibleWith'. -// Implemented Comparable interface. -// 091101 Removed 'insertAlph'. -// -//========================================================================= - -package net.sourceforge.unitsinjava; - -import java.util.Vector; - - - -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -// -// class Entity -// -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -/** - * A unit, prefix, or function. - */ - -abstract class Entity implements Comparable -{ - /** Name of this Entity. */ - public String name; - - /** Where this Entity is defined. */ - Location location; - - //===================================================================== - /** Constructor. - * - * @param nam name. - * @param loc where defined. */ - //===================================================================== - Entity(final String nam, Location loc) - { - name = nam; - location = loc; - } - - - //===================================================================== - // Comparator. - //===================================================================== - public int compareTo(Entity e) - { return name.compareTo(e.name); } - - - //===================================================================== - // Check the definition.Used in 'checkunits'. - //===================================================================== - abstract void check(); - - - //===================================================================== - // Return true this Entity is compatible with Value 'v', - //===================================================================== - abstract boolean isCompatibleWith(final Value v); - - - //===================================================================== - // Return short description of the defined object - // to be shown by 'tryallunits'. - //===================================================================== - abstract String desc(); - -} - +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2009, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.87.J01. +// 091024 Redefined 'insertAlph' using generics. +// 091031 Replaced 'addtolist' by 'isCompatibleWith'. +// Implemented Comparable interface. +// 091101 Removed 'insertAlph'. +// +// Version 1.89.J01. +// 120127 Added 'alias' to the comment. +// Removed unused import. +// 120202 Added method 'checkName'. +// 120208 Renamed one-argument method 'isCompatibleWith' +// to 'conformsTo' to avoid confusion with two-argument one +// defined in Product and Value. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class Entity +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * A unit, prefix, function, or alias. + */ + +abstract class Entity implements Comparable +{ + /** Name of this Entity. */ + public String name; + + /** Where this Entity is defined. */ + Location location; + + //===================================================================== + // Constructor. + //===================================================================== + /** + * Constructs the Entity object. + * + * @param name name. + * @param location where defined. + */ + Entity(final String name, final Location location) + { + this.name = name; + this.location = location; + } + + + //===================================================================== + // compareTo + //===================================================================== + /** + * Implements comparator of the 'Comparable' interface. + * + * @param e the Entity to be compared with this Entity. + * @return <0 if this<e, 0 if this==e, >0 if this>e. + */ + public int compareTo(Entity e) + { return name.compareTo(e.name); } + + + //===================================================================== + // check + //===================================================================== + /** + * Checks definition of this Entity for correctness. + * Writes diagnostics to 'Env.out'. + * Used by 'check' in 'Tables'. + */ + abstract void check(); + + + //===================================================================== + // conformsTo + //===================================================================== + /** + * Checks if this Entity conforms to Value 'v'. + * Used by 'showConformable' in 'Tables'. + * + * @param v the Value to be checked against. + * @return true if this Entity conforms to v, false otherwise. + */ + abstract boolean conformsTo(final Value v); + + + //===================================================================== + // desc + //===================================================================== + /** + * Returns short description of this Entity + * to be shown by 'showConformable' and 'showMatching' in 'Tables'. + * + * @return description. + */ + abstract String desc(); + + + //===================================================================== + // checkName + //===================================================================== + /** + * Checks if 'name' is a correct Entity name. + * + * @param name name to be checked. + * @return diagnostic message, or null if 'name' is correct. + */ + public static String checkName(final String name) + { + if ("0123456789".indexOf(name.charAt(0))>=0) + return "begins with a digit"; + + if ("_.,~".indexOf(name.charAt(0))>=0) + return "begins with '" + name.charAt(0) + "'"; + + int lg = name.length(); + + if (".,_".indexOf(name.charAt(lg-1))>=0) + return "ends with '" + name.charAt(lg-1) + "'"; + + int i = Util.indexOf("+-*/|^();#",name,1); + if (i. -// -//------------------------------------------------------------------------- -// -// Change log -// -// 050203 Version 1.84.J05. -// Added constants, 'unitcheck' and 'filenames'. -// 050205 Added method 'getProperties'. -// 050207 Added 'propfile'. -// 050226 Version 1.84.J06. -// Expanded examples to help text moved from 'GUI' and 'convert'. -// 050315 Version 1.84.J07. -// Changed package name to "units". -// Removed 'Bug reports to.." from ABOUT. -// 050731 Version 1.85.J01. -// Changed version numbers and copyright. -// 061228 Version 1.86.J01. -// Changed version numbers and copyright. -// 061229 Changed 'verbose' to indicate compact / normal / verbose. -// Removed 'terse'. Added 'oneline'. -// 070103 Added method 'showAbout' and variable 'gui'. -// 091024 Version 1.87.J01. -// Used generics for 'filenames'. -// 091028 Changed version numbers and copyright years in 'ABOUT' -// Added warning about obsolete currency rates. -// 091103 Added method 'getPersonalUnits'. -// 101031 Version 1.87.J01. -// Changed version numbers and copyright years. -// Renamed 'ABOUT' to 'COPYRIGHT' and removed version info. -// Changed 'showAbout' to show version and invocation info -// before copyright. -// -//========================================================================= - -package net.sourceforge.unitsinjava; - -import java.util.Vector; -import java.util.Properties; - -import java.io.File; -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.FileNotFoundException; - - - -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -// -// Class Env -// -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -/** - * Contains static constants, variables, and methods common - * to different modes of invocation. - * Is never instantiated. - */ -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH - - public class Env -{ - //------------------------------------------------------------------- - // Constants - //------------------------------------------------------------------- - public static final String PROGNAME = "gnu.units"; // Used in error messages - public static final String VERSION = "1.88.J01"; // Program version - public static final String ORIGVER = "1.88"; // Original version - public static final String UNITSFILE = "units.dat"; // Default units file - public static final String FILEVER = "1.50 (14 February 2010)"; - public static final String PROPFILE = "units.opt"; // Properties file - public static final String DEFAULTLOCALE = "en_US"; // Default locale - public static final int MAXFILES = 25; // Max number of units files - public static final int MAXINCLUDE = 5; // Max depth of include files - - - public static final String COPYRIGHT = "" - + "This is an extended Java version of GNU Units " + ORIGVER + ", a program written in C\n" - + "by Adrian Mariano, copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003,\n" - + "2004, 2005, 2006, 2007, 2010 by Free Software Foundation, Inc.\n" - + "Java version copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010\n" - + "by Roman R Redziejowski.\n" - + "The program is free software; you can redistribute it and/or modify under\n" - + "the terms of the GNU General Public License as published by the Free Software\n" - + "Foundation; either version 3 of the License or (at your option) any later\n" - + "version. The program is distributed in the hope that it will be useful, but\n" - + "WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY\n" - + "or FITNESS FOR A PARTICULAR PURPOSE. For more details, see the GNU General\n" - + "Public License (http://www.gnu.org/licenses/)."; - - public static final String EXAMPLES = - " Examples of conversions:\n\n" - + " EXAMPLE 1. What is 6 feet 4 inches in meters?\n\n" - + " You have: 6 ft + 4 in\n" - + " You want: m\n" - + " 6 ft + 4 in = 1.9304 m\n" - + " 6 ft + 4 in = (1 / 0.51802737) m\n\n" - + " Answer: About 1.93 m (or 1/0.518 m).\n\n" - + " EXAMPLE 2. Thermometer shows 75 degrees Fahrenheit.\n" - + " What is the temperature in degrees Celsius?\n\n" - + " You have: tempF(75)\n" - + " You want: tempC\n" - + " tempF(75) = tempC(23.88889)\n\n" - + " Answer: About 24 C.\n\n" - + " EXAMPLE 3. A European car maker states fuel consumption of the newest model\n" - + " as 8 liters per 100 km. What it means in miles per gallon?\n\n" - + " You have: 8 liters / 100 km\n" - + " You want: miles per gallon\n" - + " reciprocal conversion\n" - + " 1 / (8 liters / 100 km) = 29.401823 miles per gallon\n" - + " 1 / (8 liters / 100 km) = (1 / 0.034011498) miles per gallon\n\n" - + " Answer: About 29.4 mpg. Notice the indication that 'miles per gallon'\n" - + " and 'liters per 100 km' are reciprocal dimensions.\n\n" - + " EXAMPLE 4. A flow of electrons in a vacuum tube has ben measured as 5 mA.\n" - + " How many electrons flow through the tube every second?\n" - + " (Hint: units data file defines the electron charge as 'e'.)\n\n" - + " You have: 5 mA\n" - + " You want: e/sec\n" - + " 5mA = 3.12075481E16 e/sec\n" - + " 5mA = (1 / 3.2043528E-17) e/sec\n\n" - + " Answer: About 31 200 000 000 000 000.\n\n" - + " EXAMPLE 5. What is the energy, in electronvolts, of a photon of yellow sodium light\n" - + " with wavelength of 5896 angstroms? (The energy is equal to Planck's constant times\n" - + " speed of light divided by the wavelength. The units data file defines the Planck's\n" - + " constant as 'h' and the speed of light as 'c'.)\n\n" - + " You have: h * (c/5896 angstroms)\n" - + " You want: e V\n" - + " h * (c/5896 angstroms) = 2.1028526 e V\n" - + " h * (c/5896 angstroms) = (1 / 0.4755445) e V\n\n" - + " Answer: About 2.103 eV.\n\n"; - - - //------------------------------------------------------------------- - // Current environment - //------------------------------------------------------------------- - public static Vector filenames; // Unit definition files - public static String locale; // Locale in effect - public static String propfile; // Property file used - - public static int verbose; // 0=compact, 1=normal, 2=verbose - public static boolean quiet; // Suppress prompting and statistics - public static boolean oneline; // Only one line of output - public static boolean strict; // Strict conversion - public static boolean unitcheck; // Unit checking - - public static FileAcc files; // File system - public static Writer out; // Standard output - public static Writer err; // Standard error - - //------------------------------------------------------------------- - // Access to file system - //------------------------------------------------------------------- - abstract public static class FileAcc - { - public abstract BufferedReader open(final String name); - } - - //------------------------------------------------------------------- - // Output writer - //------------------------------------------------------------------- - abstract public static class Writer - { - public abstract void print(final String s); - public abstract void println(final String s); - } - - - //===================================================================== - // getProperties - //===================================================================== - /** - * Obtains options from properties file (if present). - * The file must be in the same directory as JAR. - */ - public static void getProperties() - { - propfile = null; // Property file not found yet. - - //--------------------------------------------------------------- - // For a JAR-packaged program, 'java.class.path' returned - // by 'System.getProperty' is a complete path of the JAR file. - // To obtain full path of property file, we replace the JAR name - // by name of the property file. - //--------------------------------------------------------------- - String classPath = System.getProperty("java.class.path"); - String filSep = System.getProperty("file.separator"); - String propPath = classPath.substring(0,classPath.lastIndexOf(filSep)+1) + PROPFILE; - - //--------------------------------------------------------------- - // Try to open property file. Return if not found. - //--------------------------------------------------------------- - FileInputStream propFile; - try - { propFile = new FileInputStream(propPath); } - catch (FileNotFoundException e) - { return; } - - //--------------------------------------------------------------- - // Read properties from the file. - // Write message and return if file incorrect. - //--------------------------------------------------------------- - Properties props = new Properties(); - try - { - props.load(propFile); - propFile.close(); - } - catch (Exception e) - { - Env.err.println(PROGNAME + ": error reading properties from '" + propPath +"'.\n" + e); - return; - } - - propfile = propPath; // Property file found and read. - - //--------------------------------------------------------------- - // If LOCALE defined, store it as Env.locale. - //--------------------------------------------------------------- - String prop = props.getProperty("LOCALE"); - if (prop!=null) Env.locale = prop.trim(); - - //--------------------------------------------------------------- - // If UNITSFILE defined, it is a semicolon-separated list - // of file names. Convert it to a vector and store as Env.filenames. - //--------------------------------------------------------------- - prop = props.getProperty("UNITSFILE"); - if (prop!=null) - { - Env.filenames = new Vector(); - while(prop!=null) - { - String fileName; - int i = prop.indexOf(';'); - if (i>=0) - { - fileName = prop.substring(0,i).trim(); - prop = prop.substring(i+1,prop.length()); - } - else - { - fileName = prop.trim(); - prop = null; - } - - Env.filenames.add(fileName); - } - } - } - - - //===================================================================== - // getPersonalUnits - //===================================================================== - /** - * If 'user.home' directory contains file 'units.dat', - * add its name (full path) to 'filenames'. - */ - public static void getPersonalUnits() - { - String home = System.getProperty("user.home"); - File personal = new File(home + File.separator + "units.dat"); - if (personal.exists()) filenames.add(personal.getPath()); - } - - //===================================================================== - // showAbout - //===================================================================== - /** - * Write ABOUT text with information about current invocation. - */ - public static void showAbout() - { - Env.out.println("Version: " + VERSION + "."); - - if (Env.propfile!=null) - Env.out.println("Property list " + Env.propfile + "."); - - Env.out.print("Units database:"); - String sep = Env.filenames.size()==1? " ":"\n\t"; - for (int i=0;i. +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J05. +// 050203 Added constants, 'unitcheck' and 'filenames'. +// 050205 Added method 'getProperties'. +// 050207 Added 'propfile'. +// +// Version 1.84.J06. +// 050226 Expanded examples to help text moved from 'GUI' and 'convert'. +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// Removed 'Bug reports to..' from ABOUT. +// +// Version 1.85.J01. +// 050731 Changed version numbers and copyright. +// +// Version 1.86.J01. +// 061228 Changed version numbers and copyright. +// 061229 Changed 'verbose' to indicate compact / normal / verbose. +// Removed 'terse'. Added 'oneline'. +// 070103 Added method 'showAbout' and variable 'gui'. +// +// Version 1.87.J01. +// 091024 Used generics for 'filenames'. +// 091028 Changed version numbers and copyright years in 'ABOUT' +// Added warning about obsolete currency rates. +// 091103 Added method 'getPersonalUnits'. +// +// Version 1.88.J01. +// 101031 Changed version numbers and copyright years. +// Renamed 'ABOUT' to 'COPYRIGHT' and removed version info. +// Changed 'showAbout' to show version and invocation info +// before copyright. +// +// Version 1.88.J02. +// 110219 Changed version numbers and copyright years. +// 110404 Lines in COPYRIGHT made shorter to fit smaller window. +// +// Version 1.88.J03. +// 110623 Changed version number. +// Moved EXAMPLES text to 'convert'. +// +// Version 1.88.J04. +// 110814 Changed version number. +// +// Version 1.89.J01. +// 120123 Changed version number of Java Units, original Units, +// and 'units.dat' file. +// Changed return type of 'open' in 'FileAcc' to InputStream. +// 120126 Added variable for option '-r'. +// 120228 Removed the error writer 'Env.err'. +// Replaced use of 'Env.err' by 'Env.out'. +// 120311 Added method 'convert'. +// 120326 Removed 'gui' - never used. +// Added 'encoding', 'font', and their default values. +// Added 'guiFont'. +// Renamed DEFAULTLOCALE to LOCALE. +// Modified 'getProperties' to obtain ENCODING and GUIFONT. +// Moved FileAcc and 'files' to UnitsFile. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import java.util.Vector; +import java.util.Properties; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// Class Env +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * Contains static constants, variables, and methods common + * to different components and modes of invocation + * Is never instantiated. + */ + public class Env +{ + //------------------------------------------------------------------- + // Constants + //------------------------------------------------------------------- + public static final String PROGNAME = "gnu.units"; // Used in error messages + public static final String VERSION = "1.89.J01"; // Program version + public static final String ORIGVER = "1.89e"; // Original version + public static final String UNITSFILE = "units.dat"; // Default units file + public static final String FILEVER = "Version 1.53 (17 November 2011)"; + public static final String PROPFILE = "units.opt"; // Properties file + public static final String LOCALE = "en_US"; // Default locale + public static final int MAXFILES = 25; // Max number of units files + public static final int MAXINCLUDE = 5; // Max depth of include files + + + public static final String COPYRIGHT = "" + + "This is an extended Java version of GNU Units " + ORIGVER + ", a program\n" + + "written in C by Adrian Mariano, copyright (C) 1996, 1997, 1999,\n" + + "2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2010, 2011 by\n" + + "Free Software Foundation, Inc.\n" + + "Java version copyright (C) 2003, 2004, 2005, 2006, 2007, 2008,\n" + + "2009, 2010, 2011, 2012 by Roman R Redziejowski.\n" + + "The program is free software; you can redistribute it and/or\n" + + "modify under the terms of the GNU General Public License\n" + + "as published by the Free SoftwareFoundation; either version 3\n" + + "of the License or (at your option) any later version.\n" + + "The program is distributed in the hope that it will be useful,\n" + + "but WITHOUT ANY WARRANTY; without even the implied warranty\n" + + "of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" + + "For more details, see the GNU General Public License\n" + + "(http://www.gnu.org/licenses/)."; + + //------------------------------------------------------------------- + /** Property file if there was one, else null. + * Set by 'getProperties'. */ + //------------------------------------------------------------------- + public static String propfile = null; + + //===================================================================== + // Options. Set by UnitsWindow, convert, and applet. + // They are first set to default values for each environment; then: + // - UnitsWindow may override 'filenames', 'locale', 'encoding', + // and 'guifont' by values obtained from property file. + // - convert may override 'filenames', 'locale', 'encoding', + // and 'guifont' by values obtained from property file, + // then override all by values specified as command options. + // - applet may override 'location' and 'guifont' + // by values from parameters. + //===================================================================== + public static Vector filenames; // Unit definition files + public static String locale; // Locale in effect + public static String encoding; // Encoding name for System io + public static int verbose; // 0=compact, 1=normal, 2=verbose + public static boolean quiet; // Suppress prompting and statistics + public static boolean oneline; // Only one line of output + public static boolean strict; // Strict conversion + public static boolean unitcheck; // Unit checking + public static boolean round; // Round last element of unit list + + + + //HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + // + // Writer + // + //HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + /** + * Output writer. + * Different subclasses of Writer specify different ways + * to write standard output in different environments. + * An object of the proper subclass for current environment + * is instantiated and assigned to 'out'. + *
    + *
  • GUI defines subclass 'GUI.myOut' and plugs its instance into 'out'. + *
  • convert defines subclass 'GUI.myOut' and plugs its instance into 'out'. + *
+ */ + abstract public static class Writer + { + public abstract void print(final String s); + public abstract void println(final String s); + } + + //------------------------------------------------------------------- + // Current Writer + //------------------------------------------------------------------- + public static Writer out; + + + + //===================================================================== + // getProperties + //===================================================================== + /** + * Obtains options from properties file (if present). + * The file must be in the same directory as JAR. + */ + public static void getProperties() + { + propfile = null; // Property file not found yet. + + //--------------------------------------------------------------- + // For a JAR-packaged program, 'java.class.path' returned + // by 'System.getProperty' is a complete path of the JAR file. + // To obtain full path of property file, we replace the JAR name + // by name of the property file. + //--------------------------------------------------------------- + String classPath = System.getProperty("java.class.path"); + String filSep = System.getProperty("file.separator"); + String propPath = classPath.substring(0,classPath.lastIndexOf(filSep)+1) + PROPFILE; + + //--------------------------------------------------------------- + // Try to open property file. Return if not found. + //--------------------------------------------------------------- + FileInputStream propFile; + try + { propFile = new FileInputStream(propPath); } + catch (FileNotFoundException e) + { return; } + + //--------------------------------------------------------------- + // Read properties from the file. + // Write message and return if file incorrect. + //--------------------------------------------------------------- + Properties props = new Properties(); + try + { + props.load(propFile); + propFile.close(); + } + catch (Exception e) + { + Env.out.println(PROGNAME + ": error reading properties from '" + propPath +"'.\n" + e); + return; + } + + propfile = propPath; // Property file found and read. + + //--------------------------------------------------------------- + // If UNITSFILE defined, it is a semicolon-separated list + // of file names. Convert it to a vector and store as Env.filenames. + //--------------------------------------------------------------- + String prop = props.getProperty("UNITSFILE"); + if (prop!=null) + { + Env.filenames = new Vector(); + while(prop!=null) + { + String fileName; + int i = prop.indexOf(';'); + if (i>=0) + { + fileName = prop.substring(0,i).trim(); + prop = prop.substring(i+1,prop.length()); + } + else + { + fileName = prop.trim(); + prop = null; + } + + Env.filenames.add(fileName); + } + } + + //--------------------------------------------------------------- + // If LOCALE defined, store it as Env.locale. + //--------------------------------------------------------------- + prop = props.getProperty("LOCALE"); + if (prop!=null) Env.locale = prop.trim(); + + //--------------------------------------------------------------- + // If ENCODING defined, store it as Env.encoding. + //--------------------------------------------------------------- + prop = props.getProperty("ENCODING"); + if (prop!=null) Env.encoding = prop.trim(); + } + + + //===================================================================== + // getPersonalUnits + //===================================================================== + /** + * If 'user.home' directory contains file 'units.dat', + * add its name (full path) to 'filenames'. + */ + public static void getPersonalUnits() + { + String home = System.getProperty("user.home"); + File personal = new File(home + File.separator + "units.dat"); + if (personal.exists()) filenames.add(personal.getPath()); + } + + //===================================================================== + // showAbout + //===================================================================== + /** + * Write ABOUT text with information about current invocation. + */ + public static void showAbout() + { + Env.out.println("Version: " + VERSION + "."); + + if (Env.propfile!=null) + Env.out.println("Property list " + Env.propfile + "."); + + Env.out.print("Units database:"); + String sep = Env.filenames.size()==1? " ":"\n\t"; + for (int i=0;itrue if conversion was successful, + * false otherwise. + */ + public static boolean convert + (final String fromExpr, final Value fromValue, final String toString) + { + //--------------------------------------------------------------- + // If 'toString' is a unit list or name of a unit list, + // show conversion to unit list. + //--------------------------------------------------------------- + String uList = UnitList.isUnitList(toString); + + if (uList!=null) + { + UnitList ul = null; + try + { ul = new UnitList(uList); } + catch(EvalError ee) + { + Env.out.println("Invalid unit list. " + ee.getMessage()); + return false; + } + + boolean ok = ul.convert(fromExpr,fromValue); + return ok; + } + + //--------------------------------------------------------------- + // If 'toString' is a function name without argument, + // show conversion to that function and return. + //--------------------------------------------------------------- + DefinedFunction func = DefinedFunction.table.get(toString); + if (func!=null) + { + boolean ok = func.convert(fromExpr,fromValue); + return ok; + } + + //--------------------------------------------------------------- + // Evaluate 'toString' to Value 'toValue'. + // A failed evaluation prints error message and returns null. + //--------------------------------------------------------------- + Value toValue = Value.fromString(toString); + if (toValue==null) + return false; + + //--------------------------------------------------------------- + // Evaluation successful, show conversion. + //--------------------------------------------------------------- + boolean ok = Value.convert(fromExpr,fromValue,toString,toValue); + return ok; + } + +} diff --git a/src/net/sourceforge/unitsinjava/EvalError.java b/app/src/main/java/net/sourceforge/unitsinjava/EvalError.java similarity index 89% rename from src/net/sourceforge/unitsinjava/EvalError.java rename to app/src/main/java/net/sourceforge/unitsinjava/EvalError.java index 1ba3c88e..64b4585d 100644 --- a/src/net/sourceforge/unitsinjava/EvalError.java +++ b/app/src/main/java/net/sourceforge/unitsinjava/EvalError.java @@ -1,59 +1,60 @@ -//========================================================================= -// -// Part of units package -- a Java version of GNU Units program. -// -// Units is a program for unit conversion originally written in C -// by Adrian Mariano (adrian@cam.cornell.edu.). -// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, -// 2005, 2006, 2007 by Free Software Foundation, Inc. -// -// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, -// 2009 by Roman R Redziejowski (roman.redz@tele2.se). -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -//------------------------------------------------------------------------- -// -// Change log -// -// 091025 Created for Version 1.87.J01 to replace Parser.Error. -// -//========================================================================= - -package net.sourceforge.unitsinjava; - - - -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -// -// class EvalError -// -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -/** - * Exception thrown by evaluation of unit expression. - */ - - public class EvalError extends Error -{ - //------------------------------------------------------------------- - // Constructor - //------------------------------------------------------------------- - EvalError(final String s) - { super(s); } - - //------------------------------------------------------------------- - // Serial version UID. Unused: defined to eliminate compiler warning - //------------------------------------------------------------------- - public static final long serialVersionUID = 4711L; -} +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.87.J01 +// 091025 Created to replace Parser.Error. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class EvalError +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * Exception thrown by evaluation of unit expression. + */ + + public class EvalError extends Error +{ + //------------------------------------------------------------------- + // Constructor + //------------------------------------------------------------------- + EvalError(final String s) + { super(s); } + + //------------------------------------------------------------------- + // Serial version UID. Unused: defined to eliminate compiler warning + //------------------------------------------------------------------- + public static final long serialVersionUID = 4711L; +} diff --git a/app/src/main/java/net/sourceforge/unitsinjava/Factor.java b/app/src/main/java/net/sourceforge/unitsinjava/Factor.java new file mode 100644 index 00000000..6ccdebaa --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/Factor.java @@ -0,0 +1,302 @@ +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2009, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2011, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J07 +// 050315 Changed package name to 'units'. +// +// Version 1.87.J01 +// 091031 Moved here definition of Ignore. +// +// Version 1.88.J02 +// 110403 'showdef' modified to return String instead of printing. +// +// Version 1.89.J01 +// 120202 Use a method from 'Double' instead of 'Util.strtod' +// to decide 'isNumber' attribute in the constructor. +// 120209 Moved definition of Ignore back to separate file as enum. +// (No longer needs to be a set of constants in a class.) +// 120303 Added check for a valid name to 'split'. +// Substantial rewrite of 'showdef'. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class Factor +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * An entity that can be a factor in a Product: + * a unit or a prefix. + */ + +abstract public class Factor extends Entity +{ + //------------------------------------------------------------------- + /** Definition string. */ + //------------------------------------------------------------------- + public String def; + + //------------------------------------------------------------------- + /** Is this a primitive unit? */ + //------------------------------------------------------------------- + boolean isPrimitive = false; + + //------------------------------------------------------------------- + /** Is this a dimensionless primitive unit? */ + //------------------------------------------------------------------- + boolean isDimless = false; + + //------------------------------------------------------------------- + /** Is the definition a number? */ + //------------------------------------------------------------------- + boolean isNumber = false; + + //------------------------------------------------------------------- + /** Ignore in comparisons? */ + //------------------------------------------------------------------- + boolean ignoredIf(Ignore what) + { + if (what==Ignore.PRIMITIVE && isPrimitive) return true; + if (what==Ignore.DIMLESS && isDimless) return true; + return false; + } + + + //===================================================================== + // Constructor + //===================================================================== + /** + * Constructs a Factor object. + * + * @param name name of the Factor. + * @param loc location where defined. + * @param def definition. + */ + Factor(final String name, Location loc, final String def) + { + super(name,loc); + this.def = def; + + if (def.equals("!")) + isPrimitive = true; + + if (def.equals("!dimensionless")) + { + isPrimitive = true; + isDimless = true; + } + + Double d; + try + { d = Double.valueOf(def); } + catch(NumberFormatException e) + { return; } + + if (d.isInfinite() || d.isNaN()) + return; + + isNumber = true; + } + + //===================================================================== + // split + //===================================================================== + /** + * Finds out if given string is the name of a unit or prefix, + * or is a prefixed unit name. The unit name given as 'name' + * or its part may be in plural. + * (Originally part of 'lookupunit'.) + * + * @param name string to be investigated. + * @return two-element array where first element is the Prefix + * (or null if none) and second is the Unit (or null if none). + *
+ * Null if the name is not recognized. + */ + public static Factor[] split(final String name) + { + //--------------------------------------------------------------- + // Return null if 'name' is not a valid name. + //--------------------------------------------------------------- + if (Entity.checkName(name)!=null) return null; + + //--------------------------------------------------------------- + // If 'name' is a unit name, possibly in plural form, + // return its Unit object. + //--------------------------------------------------------------- + Unit u = Unit.find(name); + if (u!=null) + return new Factor[]{null,u}; + + //--------------------------------------------------------------- + // The 'name' is not a unit name. + // See if it is a prefix or prefixed unit name. + //--------------------------------------------------------------- + Prefix p = Prefix.find(name); + + //--------------------------------------------------------------- + // Return null if not a prefix or prefixed unit name. + //--------------------------------------------------------------- + if (p==null) + return null; + + //--------------------------------------------------------------- + // Get the prefix string. + // If it is all of 'name', return its Prefix object. + //--------------------------------------------------------------- + String prefix = p.name; + if (name.equals(prefix)) + return new Factor[]{p,null}; + + //--------------------------------------------------------------- + // The 'name' has a known prefix 'prefix'. + // Split 'name' into the prefix and 'rest'. + // If 'rest' (or its singular form) is a unit name, + // return the Prefix and Unit objects. + //--------------------------------------------------------------- + String rest = name.substring(prefix.length(),name.length()); + u = Unit.find(rest); + if (u!=null) + return new Factor[]{p,u}; + + //--------------------------------------------------------------- + // Return null if 'rest' is not a unit name. + //--------------------------------------------------------------- + return null; + } + + + //===================================================================== + // showdef + //===================================================================== + /** + * If given string is the name of a unit or prefix, or is a prefixed + * unit name, return its definition. Otherwise return null. + * (Modified part of 'showdefinition'.) + * + * @param name string to be investigated. + * @return definition of 'name' or null. + */ + public static String showdef(final String name) + { + StringBuilder sb = new StringBuilder(); + + String def = name; + + //--------------------------------------------------------------- + // This loop produces possibly in 'sb' a chain of definitions + // preceded by equal sign. It ends either by 'return' that + // delivers the definition, or by 'break' that proceeds to add + // the reduced form of the Value represented by 'name'. + // These exits are commented below. + //--------------------------------------------------------------- + while(true) + { + //------------------------------------------------------------- + // Split 'def' into prefix and unit - if possible. + //------------------------------------------------------------- + Factor[] pu = split(def); + + //------------------------------------------------------------- + // If 'def' is not a prefix, unit, or prefix-unit combination, + // proceed to append the reduced form of 'name'. + //------------------------------------------------------------- + if (pu==null) break; + + Factor pref = pu[0]; + Factor unit = pu[1]; + + //------------------------------------------------------------- + // If 'def' is a stand-alone prefix defined as a number, + // append this definition to 'sb' and return result. + // If it is not defined as a number, append the definition + // to 'sb', and repeat the process for that definition. + //------------------------------------------------------------- + if (unit==null) + { + def = pref.def; + if (pref.isNumber) + return name + sb.toString() + " = " + def; + + sb.append(" = " + def); + continue; + } + + //------------------------------------------------------------- + // If 'def' is a unit defined as a number, append + // this definition to 'sb' and return result. + // If 'def' is a primitive unit return either a text stating + // that, or the definition chain accumulated in 'sb'. + // If 'def' is any other unit, append its definition to 'sb', + // and repeat the process for that definition. + //------------------------------------------------------------- + if (pref==null) + { + def = unit.def; + if (unit.isNumber) + return name + sb.toString() + " = " + def; + else if (unit.isPrimitive) + { + if (sb.length()==0) return "'" + name + "' is a primitive unit"; + else return name + sb.toString(); + } + + sb.append(" = " + def); + continue; + } + + //------------------------------------------------------------- + // If 'def' is a prefix-unit combination, append combined + // definition to 'sb', and proceed to append the reduced + // form of 'name'. + //------------------------------------------------------------- + sb.append(" = " + pref.def + " " + + (unit.isPrimitive || unit.isNumber? unit.name : unit.def)); + break; + } + + //--------------------------------------------------------------- + // Append and return the reduced form of name - or return null + // if 'name' cannot be evaluated. + //--------------------------------------------------------------- + Value v; + try + { v = Value.parse(name); } + catch(EvalError e) + {return null; } + + v.completereduce(); + return name + sb.toString() + " = " + v.asString(); + } +} diff --git a/app/src/main/java/net/sourceforge/unitsinjava/FileGrammar.peg b/app/src/main/java/net/sourceforge/unitsinjava/FileGrammar.peg new file mode 100644 index 00000000..bfabc903 --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/FileGrammar.peg @@ -0,0 +1,106 @@ +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2009, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2011, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.89.J01. +// 120201 Created for this version. +// +//========================================================================= + + +line = "!" command + / space definition ; + +command = "locale " argument EOL {locale} + / "locale" EOL {badloc} + / "endlocale" EOL {endlocale} + / "include " argument EOL {include} + / "include" EOL {badincl} + / "unitlist " space name space argument EOL {unitlist} + / "unitlist" space EOL {badlist} + / "utf8" EOL + / "endutf8" EOL + / skip EOL {badcomm} + ; + + // The last alternative above ensures that 'command' + // never fails, so 'line' will never backtrack + // to process the line starting with '!' as a definition. + // (A 'name' is allowed to start with '!'.) + + +definition = name [ \t] def EOL {unitdef} + / name "(" param ")" space "[" dim ";" dim "]" def (";" def)? EOL {funcdef1} + / name "(" param ")" space "[" skip EOL {baddim} + / name "(" param ")" def (";" def)? EOL {funcdef2} + / name "(" skip EOL {badfunc} + / name "[" tabunit "]" space pair+ EOL {tabdef} + / name "[" skip EOL {badtab} + / name EOL {badunit} + / EOL + ; + + // Note that 'name' can be any string up to a blank, + // '(', '[', or end of line; all these alternatives + // are exhausted above. + // The alternatives with actions 'baddim', 'badfunc', + // and 'badtab' prevent backtracking after the recognized + // portion excludes further success. + // To avoid rescanning of the initial postion, we use + // memoizing version of the parser that caches one result. + + +argument = _* ; + +def = ^[;]* ; + +name = ^[[( \t]+ ; + +param = ^[)]+ ; + +tabunit = ^[\]]+ ; + +dim = ^[\];]* ; + +pair = number number ("," space)? {pair}; + +number = sign? mantissa exponent? space {number} ; + +mantissa = "." digits / digits ("." digits?)? ; + +exponent = [Ee] sign? digits ; + +sign = [+-] ; + +digits = [0-9]+ ; + +space = [ \t]* ; + +skip = _* ; + +EOL = !_ ; diff --git a/app/src/main/java/net/sourceforge/unitsinjava/FileParser.java b/app/src/main/java/net/sourceforge/unitsinjava/FileParser.java new file mode 100644 index 00000000..513de33a --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/FileParser.java @@ -0,0 +1,672 @@ +//========================================================================= +// +// This file was generated by Mouse 1.5 at 2012-04-06 19:20:20 GMT +// from grammar 'D:\Units\ units\FileGrammar.peg'. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import net.sourceforge.unitsinjava.Source; + +public class FileParser extends net.sourceforge.unitsinjava.ParserMemo +{ + final FileSemantics sem; + + //======================================================================= + // + // Initialization + // + //======================================================================= + //------------------------------------------------------------------- + // Constructor + //------------------------------------------------------------------- + public FileParser() + { + sem = new FileSemantics(); + sem.rule = this; + super.sem = sem; + caches = cacheList; + } + + //------------------------------------------------------------------- + // Run the parser + //------------------------------------------------------------------- + public boolean parse(Source src) + { + super.init(src); + sem.init(); + if (line()) return true; + return failure(); + } + + //------------------------------------------------------------------- + // Get semantics + //------------------------------------------------------------------- + public FileSemantics semantics() + { return sem; } + + //======================================================================= + // + // Parsing procedures + // + //======================================================================= + //===================================================================== + // line = "!" command / space definition ; + //===================================================================== + private boolean line() + { + if (saved(line)) return reuse(); + if (line_0()) return accept(); + if (line_1()) return accept(); + return reject(); + } + + //------------------------------------------------------------------- + // line_0 = "!" command + //------------------------------------------------------------------- + private boolean line_0() + { + if (savedInner(line_0)) return reuseInner(); + if (!next('!')) return rejectInner(); + if (!command()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // line_1 = space definition + //------------------------------------------------------------------- + private boolean line_1() + { + if (savedInner(line_1)) return reuseInner(); + space(); + if (!definition()) return rejectInner(); + return acceptInner(); + } + + //===================================================================== + // command = "locale " argument EOL {locale} / "locale" EOL {badloc} / + // "endlocale" EOL {endlocale} / "include " argument EOL {include} / + // "include" EOL {badincl} / "unitlist " space name space argument + // EOL {unitlist} / "unitlist" space EOL {badlist} / "utf8" EOL / + // "endutf8" EOL / skip EOL {badcomm} ; + //===================================================================== + private boolean command() + { + if (saved(command)) return reuse(); + if (command_0()) + { sem.locale(); return accept(); } + if (command_1()) + { sem.badloc(); return accept(); } + if (command_2()) + { sem.endlocale(); return accept(); } + if (command_3()) + { sem.include(); return accept(); } + if (command_4()) + { sem.badincl(); return accept(); } + if (command_5()) + { sem.unitlist(); return accept(); } + if (command_6()) + { sem.badlist(); return accept(); } + if (command_7()) return accept(); + if (command_8()) return accept(); + if (command_9()) + { sem.badcomm(); return accept(); } + return reject(); + } + + //------------------------------------------------------------------- + // command_0 = "locale " argument EOL + //------------------------------------------------------------------- + private boolean command_0() + { + if (savedInner(command_0)) return reuseInner(); + if (!next("locale ")) return rejectInner(); + argument(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // command_1 = "locale" EOL + //------------------------------------------------------------------- + private boolean command_1() + { + if (savedInner(command_1)) return reuseInner(); + if (!next("locale")) return rejectInner(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // command_2 = "endlocale" EOL + //------------------------------------------------------------------- + private boolean command_2() + { + if (savedInner(command_2)) return reuseInner(); + if (!next("endlocale")) return rejectInner(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // command_3 = "include " argument EOL + //------------------------------------------------------------------- + private boolean command_3() + { + if (savedInner(command_3)) return reuseInner(); + if (!next("include ")) return rejectInner(); + argument(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // command_4 = "include" EOL + //------------------------------------------------------------------- + private boolean command_4() + { + if (savedInner(command_4)) return reuseInner(); + if (!next("include")) return rejectInner(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // command_5 = "unitlist " space name space argument EOL + //------------------------------------------------------------------- + private boolean command_5() + { + if (savedInner(command_5)) return reuseInner(); + if (!next("unitlist ")) return rejectInner(); + space(); + if (!name()) return rejectInner(); + space(); + argument(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // command_6 = "unitlist" space EOL + //------------------------------------------------------------------- + private boolean command_6() + { + if (savedInner(command_6)) return reuseInner(); + if (!next("unitlist")) return rejectInner(); + space(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // command_7 = "utf8" EOL + //------------------------------------------------------------------- + private boolean command_7() + { + if (savedInner(command_7)) return reuseInner(); + if (!next("utf8")) return rejectInner(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // command_8 = "endutf8" EOL + //------------------------------------------------------------------- + private boolean command_8() + { + if (savedInner(command_8)) return reuseInner(); + if (!next("endutf8")) return rejectInner(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // command_9 = skip EOL + //------------------------------------------------------------------- + private boolean command_9() + { + if (savedInner(command_9)) return reuseInner(); + skip(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //===================================================================== + // definition = name [ \t] def EOL {unitdef} / name "(" param ")" + // space "[" dim ";" dim "]" def (";" def)? EOL {funcdef1} / name + // "(" param ")" space "[" skip EOL {baddim} / name "(" param ")" + // def (";" def)? EOL {funcdef2} / name "(" skip EOL {badfunc} / + // name "[" tabunit "]" space pair+ EOL {tabdef} / name "[" skip EOL + // {badtab} / name EOL {badunit} / EOL ; + //===================================================================== + private boolean definition() + { + if (saved(definition)) return reuse(); + if (definition_0()) + { sem.unitdef(); return accept(); } + if (definition_1()) + { sem.funcdef1(); return accept(); } + if (definition_2()) + { sem.baddim(); return accept(); } + if (definition_3()) + { sem.funcdef2(); return accept(); } + if (definition_4()) + { sem.badfunc(); return accept(); } + if (definition_5()) + { sem.tabdef(); return accept(); } + if (definition_6()) + { sem.badtab(); return accept(); } + if (definition_7()) + { sem.badunit(); return accept(); } + if (EOL()) return accept(); + return reject(); + } + + //------------------------------------------------------------------- + // definition_0 = name [ \t] def EOL + //------------------------------------------------------------------- + private boolean definition_0() + { + if (savedInner(definition_0)) return reuseInner(); + if (!name()) return rejectInner(); + if (!nextIn(" \t")) return rejectInner(); + def(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // definition_1 = name "(" param ")" space "[" dim ";" dim "]" def + // (";" def)? EOL + //------------------------------------------------------------------- + private boolean definition_1() + { + if (savedInner(definition_1)) return reuseInner(); + if (!name()) return rejectInner(); + if (!next('(')) return rejectInner(); + if (!param()) return rejectInner(); + if (!next(')')) return rejectInner(); + space(); + if (!next('[')) return rejectInner(); + dim(); + if (!next(';')) return rejectInner(); + dim(); + if (!next(']')) return rejectInner(); + def(); + definition_8(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // definition_2 = name "(" param ")" space "[" skip EOL + //------------------------------------------------------------------- + private boolean definition_2() + { + if (savedInner(definition_2)) return reuseInner(); + if (!name()) return rejectInner(); + if (!next('(')) return rejectInner(); + if (!param()) return rejectInner(); + if (!next(')')) return rejectInner(); + space(); + if (!next('[')) return rejectInner(); + skip(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // definition_3 = name "(" param ")" def (";" def)? EOL + //------------------------------------------------------------------- + private boolean definition_3() + { + if (savedInner(definition_3)) return reuseInner(); + if (!name()) return rejectInner(); + if (!next('(')) return rejectInner(); + if (!param()) return rejectInner(); + if (!next(')')) return rejectInner(); + def(); + definition_8(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // definition_4 = name "(" skip EOL + //------------------------------------------------------------------- + private boolean definition_4() + { + if (savedInner(definition_4)) return reuseInner(); + if (!name()) return rejectInner(); + if (!next('(')) return rejectInner(); + skip(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // definition_5 = name "[" tabunit "]" space pair+ EOL + //------------------------------------------------------------------- + private boolean definition_5() + { + if (savedInner(definition_5)) return reuseInner(); + if (!name()) return rejectInner(); + if (!next('[')) return rejectInner(); + if (!tabunit()) return rejectInner(); + if (!next(']')) return rejectInner(); + space(); + if (!pair()) return rejectInner(); + while (pair()); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // definition_6 = name "[" skip EOL + //------------------------------------------------------------------- + private boolean definition_6() + { + if (savedInner(definition_6)) return reuseInner(); + if (!name()) return rejectInner(); + if (!next('[')) return rejectInner(); + skip(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // definition_7 = name EOL + //------------------------------------------------------------------- + private boolean definition_7() + { + if (savedInner(definition_7)) return reuseInner(); + if (!name()) return rejectInner(); + if (!EOL()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // definition_8 = ";" def + //------------------------------------------------------------------- + private boolean definition_8() + { + if (savedInner(definition_8)) return reuseInner(); + if (!next(';')) return rejectInner(); + def(); + return acceptInner(); + } + + //===================================================================== + // argument = _* ; + //===================================================================== + private boolean argument() + { + if (saved(argument)) return reuse(); + while (next()); + return accept(); + } + + //===================================================================== + // def = ^[;]* ; + //===================================================================== + private boolean def() + { + if (saved(def)) return reuse(); + while (nextNot(';')); + return accept(); + } + + //===================================================================== + // name = ^[[( \t]+ ; + //===================================================================== + private boolean name() + { + if (saved(name)) return reuse(); + if (!nextNotIn("[( \t")) return reject(); + while (nextNotIn("[( \t")); + return accept(); + } + + //===================================================================== + // param = ^[)]+ ; + //===================================================================== + private boolean param() + { + if (saved(param)) return reuse(); + if (!nextNot(')')) return reject(); + while (nextNot(')')); + return accept(); + } + + //===================================================================== + // tabunit = ^[]]+ ; + //===================================================================== + private boolean tabunit() + { + if (saved(tabunit)) return reuse(); + if (!nextNot(']')) return reject(); + while (nextNot(']')); + return accept(); + } + + //===================================================================== + // dim = ^[];]* ; + //===================================================================== + private boolean dim() + { + if (saved(dim)) return reuse(); + while (nextNotIn("];")); + return accept(); + } + + //===================================================================== + // pair = number number ("," space)? {pair} ; + //===================================================================== + private boolean pair() + { + if (saved(pair)) return reuse(); + if (!number()) return reject(); + if (!number()) return reject(); + pair_0(); + sem.pair(); + return accept(); + } + + //------------------------------------------------------------------- + // pair_0 = "," space + //------------------------------------------------------------------- + private boolean pair_0() + { + if (savedInner(pair_0)) return reuseInner(); + if (!next(',')) return rejectInner(); + space(); + return acceptInner(); + } + + //===================================================================== + // number = sign? mantissa exponent? space {number} ; + //===================================================================== + private boolean number() + { + if (saved(number)) return reuse(); + sign(); + if (!mantissa()) return reject(); + exponent(); + space(); + sem.number(); + return accept(); + } + + //===================================================================== + // mantissa = "." digits / digits ("." digits?)? ; + //===================================================================== + private boolean mantissa() + { + if (saved(mantissa)) return reuse(); + if (mantissa_0()) return accept(); + if (mantissa_1()) return accept(); + return reject(); + } + + //------------------------------------------------------------------- + // mantissa_0 = "." digits + //------------------------------------------------------------------- + private boolean mantissa_0() + { + if (savedInner(mantissa_0)) return reuseInner(); + if (!next('.')) return rejectInner(); + if (!digits()) return rejectInner(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // mantissa_1 = digits ("." digits?)? + //------------------------------------------------------------------- + private boolean mantissa_1() + { + if (savedInner(mantissa_1)) return reuseInner(); + if (!digits()) return rejectInner(); + mantissa_2(); + return acceptInner(); + } + + //------------------------------------------------------------------- + // mantissa_2 = "." digits? + //------------------------------------------------------------------- + private boolean mantissa_2() + { + if (savedInner(mantissa_2)) return reuseInner(); + if (!next('.')) return rejectInner(); + digits(); + return acceptInner(); + } + + //===================================================================== + // exponent = [Ee] sign? digits ; + //===================================================================== + private boolean exponent() + { + if (saved(exponent)) return reuse(); + if (!nextIn("Ee")) return reject(); + sign(); + if (!digits()) return reject(); + return accept(); + } + + //===================================================================== + // sign = [+-] ; + //===================================================================== + private boolean sign() + { + if (saved(sign)) return reuse(); + if (!nextIn("+-")) return reject(); + return accept(); + } + + //===================================================================== + // digits = [0-9]+ ; + //===================================================================== + private boolean digits() + { + if (saved(digits)) return reuse(); + if (!nextIn('0','9')) return reject(); + while (nextIn('0','9')); + return accept(); + } + + //===================================================================== + // space = [ \t]* ; + //===================================================================== + private boolean space() + { + if (saved(space)) return reuse(); + while (nextIn(" \t")); + return accept(); + } + + //===================================================================== + // skip = _* ; + //===================================================================== + private boolean skip() + { + if (saved(skip)) return reuse(); + while (next()); + return accept(); + } + + //===================================================================== + // EOL = !_ ; + //===================================================================== + private boolean EOL() + { + if (saved(EOL)) return reuse(); + if (!aheadNot()) return reject(); + return accept(); + } + + //======================================================================= + // + // Cache objects + // + //======================================================================= + + final Cache line = new Cache("line","line"); + final Cache command = new Cache("command","command"); + final Cache definition = new Cache("definition","definition"); + final Cache argument = new Cache("argument","argument"); + final Cache def = new Cache("def","def"); + final Cache name = new Cache("name","name"); + final Cache param = new Cache("param","param"); + final Cache tabunit = new Cache("tabunit","tabunit"); + final Cache dim = new Cache("dim","dim"); + final Cache pair = new Cache("pair","pair"); + final Cache number = new Cache("number","number"); + final Cache mantissa = new Cache("mantissa","mantissa"); + final Cache exponent = new Cache("exponent","exponent"); + final Cache sign = new Cache("sign","sign"); + final Cache digits = new Cache("digits","digits"); + final Cache space = new Cache("space","space"); + final Cache skip = new Cache("skip","skip"); + final Cache EOL = new Cache("EOL","EOL"); + + final Cache line_0 = new Cache("line_0"); // "!" command + final Cache line_1 = new Cache("line_1"); // space definition + final Cache command_0 = new Cache("command_0"); // "locale " argument EOL + final Cache command_1 = new Cache("command_1"); // "locale" EOL + final Cache command_2 = new Cache("command_2"); // "endlocale" EOL + final Cache command_3 = new Cache("command_3"); // "include " argument EOL + final Cache command_4 = new Cache("command_4"); // "include" EOL + final Cache command_5 = new Cache("command_5"); // "unitlist " space name space argument EOL + final Cache command_6 = new Cache("command_6"); // "unitlist" space EOL + final Cache command_7 = new Cache("command_7"); // "utf8" EOL + final Cache command_8 = new Cache("command_8"); // "endutf8" EOL + final Cache command_9 = new Cache("command_9"); // skip EOL + final Cache definition_0 = new Cache("definition_0"); // name [ \t] def EOL + final Cache definition_1 = new Cache("definition_1"); // name "(" param ")" space "[" dim ";" dim "]" def (";" def)? EOL + final Cache definition_2 = new Cache("definition_2"); // name "(" param ")" space "[" skip EOL + final Cache definition_3 = new Cache("definition_3"); // name "(" param ")" def (";" def)? EOL + final Cache definition_4 = new Cache("definition_4"); // name "(" skip EOL + final Cache definition_5 = new Cache("definition_5"); // name "[" tabunit "]" space pair+ EOL + final Cache definition_6 = new Cache("definition_6"); // name "[" skip EOL + final Cache definition_7 = new Cache("definition_7"); // name EOL + final Cache definition_8 = new Cache("definition_8"); // ";" def + final Cache pair_0 = new Cache("pair_0"); // "," space + final Cache mantissa_0 = new Cache("mantissa_0"); // "." digits + final Cache mantissa_1 = new Cache("mantissa_1"); // digits ("." digits?)? + final Cache mantissa_2 = new Cache("mantissa_2"); // "." digits? + + //------------------------------------------------------------------- + // List of Cache objects + //------------------------------------------------------------------- + + Cache[] cacheList = + { + line,command,definition,argument,def,name,param,tabunit,dim,pair, + number,mantissa,exponent,sign,digits,space,skip,EOL,line_0,line_1, + command_0,command_1,command_2,command_3,command_4,command_5, + command_6,command_7,command_8,command_9,definition_0,definition_1, + definition_2,definition_3,definition_4,definition_5,definition_6, + definition_7,definition_8,pair_0,mantissa_0,mantissa_1,mantissa_2 + }; +} diff --git a/app/src/main/java/net/sourceforge/unitsinjava/FileSemantics.java b/app/src/main/java/net/sourceforge/unitsinjava/FileSemantics.java new file mode 100644 index 00000000..08c0b409 --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/FileSemantics.java @@ -0,0 +1,422 @@ +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2009, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2011, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.89.J01. +// 120201 Created for this version. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class FileSemantics +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * Contains semantic procedures for FileParser. + */ + public class FileSemantics extends net.sourceforge.unitsinjava.SemanticsBase +{ + //------------------------------------------------------------------- + // Location of line being parsed. + //------------------------------------------------------------------- + Location loc; + + //------------------------------------------------------------------- + // Include depth. + //------------------------------------------------------------------- + int depth; + + //------------------------------------------------------------------- + // Indicator: if true we are currently reading data + // for the wrong locale so we should skip it. + //------------------------------------------------------------------- + boolean wronglocale = false; + + //------------------------------------------------------------------- + // Indicator: if true we are currently reading data + // for some locale (right or wrong). + //------------------------------------------------------------------- + boolean inlocale = false; + + + //===================================================================== + // command = "locale " argument EOL + // 0 1 2 + //===================================================================== + void locale() + { + String argument = rhs(1).text().trim(); + + if (inlocale) + { + Env.out.println(loc.where() + ". Nested locales are not allowed."); + return; + } + + if (argument.length() == 0) + { + Env.out.println(loc.where() + ". No locale specified."); + return; + } + + inlocale = true; + if (!argument.equals(Env.locale)) + { + wronglocale = true; + return; + } + + } + + //===================================================================== + // command = "locale" EOL + // 0 1 + //===================================================================== + void badloc() + { + Env.out.println(loc.where() + ". No locale specified."); + return; + } + + //===================================================================== + // command = "endlocale" EOL + //===================================================================== + void endlocale() + { + if (!inlocale) + { + Env.out.println(loc.where() + ". Unmatched !endlocale."); + return; + } + + inlocale = false; + wronglocale = false; + } + + //===================================================================== + // command = "include " argument EOL + // 0 1 2 + //===================================================================== + void include() + { + if (wronglocale) return; + + String argument = rhs(1).text().trim(); + + if (argument.length() == 0) + { + Env.out.println(loc.where() + ". No file name specified."); + return; + } + + if (depth>=Env.MAXINCLUDE) + { + Env.out.println(loc.where() + ". Max include depth of " + + Env.MAXINCLUDE + " exceeded."); + return; + } + + UnitsFile infile = new UnitsFile(argument); + boolean ok = infile.readunits(depth+1); + if (ok) Env.filenames.add(infile.name); + } + + //===================================================================== + // command = "include" EOL + // 0 1 + //===================================================================== + void badincl() + { + Env.out.println(loc.where() + ". No file name specified."); + return; + } + + //===================================================================== + // command = "unitlist " space name space argument EOL + // 0 1 2 3 4 5 + //===================================================================== + void unitlist() + { + if (wronglocale) return; + + String name = rhs(2).text(); + String argument = rhs(4).text().trim(); + + if (argument.length() == 0) + { + Env.out.println + (loc.where() + ". No unit list specified for '" + name + "'."); + return; + } + + Alias.define(name,argument,loc); + } + + //===================================================================== + // command = "unitlist" space EOL + // 0 1 2 + //===================================================================== + void badlist() + { + Env.out.println + (loc.where() + ". No name specified with '!unitlist'."); + } + + //===================================================================== + // command = skip EOL + // 0 1 + //===================================================================== + void badcomm() + { + Env.out.println(loc.where() + ". Unrecognized command '!" + + rhsText(0,1) + "'."); + } + + //===================================================================== + // definition = name "(" param ")" space "[" dim ";" dim "]" def (";" def)? EOL + // 0 1 2 3 4 5 6 7 8 9 10 11 12 11,13 + //===================================================================== + void funcdef1() + { + if (wronglocale) return; + + String name = rhs(0).text(); + String param = rhs(2).text(); + String fwddim = rhs(6).text().trim(); + String invdim = rhs(8).text().trim(); + String fwddef = rhs(10).text().trim(); + String invdef = null; + if (rhsSize()>12) invdef = rhs(12).text().trim(); + + if (fwddef.length() == 0) + { + Env.out.println + (loc.where() + ". Function '" + name + + "' is ignored. Definition missing."); + return; + } + + if (fwddim.length() == 0) fwddim = null; + if (invdim.length() == 0) invdim = null; + + ComputedFunction.define + (name,param,fwddim,invdim,fwddef,invdef,loc); + } + + //===================================================================== + // definition = name "(" param ")" def (";" def)? EOL + // 0 1 2 3 4 5 6 5,7 + //===================================================================== + void funcdef2() + { + if (wronglocale) return; + + String name = rhs(0).text(); + String param = rhs(2).text(); + String fwddef = rhs(4).text().trim(); + String invdef = null; + if (rhsSize()>6) invdef = rhs(6).text().trim(); + + if (fwddef.length() == 0) + { + Env.out.println + (loc.where() + ". Function '" + name + + "' is ignored. Definition missing."); + return; + } + + ComputedFunction.define + (name,param,null,null,fwddef,invdef,loc); + } + + //===================================================================== + // definition = name "(" param ")" space "[" skip EOL + // 0 1 2 3 4 5 6 7 + //===================================================================== + void baddim() + { + if (wronglocale) return; + + String name = rhs(0).text(); + + Env.out.println + (loc.where() + ". Function '" + name + + "' is ignored. Argument dimensions not found after '['."); + } + + //===================================================================== + // definition = name "(" skip EOL + // 0 1 2 3 + //===================================================================== + void badfunc() + { + if (wronglocale) return; + + String name = rhs(0).text(); + + Env.out.println + (loc.where() + ". Function '" + name + + "' is ignored. Parameter not found after '('."); + } + + //===================================================================== + // definition = name "[" resUnit "]" space pair+ EOL + // 0 1 2 3 4 5,.. 6,.. + //===================================================================== + void tabdef() + { + if (wronglocale) return; + + String name = rhs(0).text(); + String resUnit = rhs(2).text().trim(); + int npairs = rhsSize()-6; + + if (npairs==1) + { + Env.out.println + (loc.where() + ". Function '" + name + + "' is ignored. Only one point is defined."); + return; + } + + double x[] = new double[npairs]; + double y[] = new double[npairs]; + + for (int i=0;i. -// -//------------------------------------------------------------------------- -// -// Change log -// -// 050315 Version 1.84.J07. Changed package name to "units". -// 091025 Version 1.87.J01. Replaced 'Parser.Exception' by 'EvalError'. -// -//========================================================================= - -package net.sourceforge.unitsinjava; - - - -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -// -// class Function -// -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -/** - * A function (built-in, computed, or tabular). - */ - -public abstract class Function extends Entity -{ - //===================================================================== - // Construct object for function 'nam' defined at 'loc'. - //===================================================================== - Function(String nam,Location loc) - { super(nam,loc); } - - - //===================================================================== - // Apply the function to Value 'v' (with result in 'v'). - //===================================================================== - abstract void applyTo(Value v); - - - //===================================================================== - // Apply inverse of the function to Value 'v' (with result in 'v'). - //===================================================================== - public abstract void applyInverseTo(Value v); - - - //===================================================================== - // Return definition of the function. - // (Originally 'showfuncdef'.) - //===================================================================== - abstract String showdef(); -} +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.87.J01. +// 091025 Replaced 'Parser.Exception' by 'EvalError'. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class Function +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * A function. Base class for BuiltInFunction and DefinedFunction. + */ + +public abstract class Function extends Entity +{ + //===================================================================== + // Constructor + //===================================================================== + /** + * Constructs a Function object. + * + * @param name function name. + * @param loc location where defined. + */ + Function(String name,Location loc) + { super(name,loc); } + + + //===================================================================== + // applyTo + //===================================================================== + /** + * Applies this function to a given Value, + * and changes the Value to the result. + * + * @param v the argument and result. + */ + abstract void applyTo(Value v); + + + //===================================================================== + // applyInverseTo + //===================================================================== + /** + * Applies the inverse of this function to a given Value, + * and changes the Value to the result. + * + * @param v the argument and result. + */ + public abstract void applyInverseTo(Value v); + + //===================================================================== + // showdef + //===================================================================== + /** + * Returns definition of this function. + * (Originally 'showfuncdef'.) + * + * @return formatted definition of this function. + */ + abstract String showdef(); +} \ No newline at end of file diff --git a/src/net/sourceforge/unitsinjava/grammar.peg b/app/src/main/java/net/sourceforge/unitsinjava/Grammar.peg similarity index 59% rename from src/net/sourceforge/unitsinjava/grammar.peg rename to app/src/main/java/net/sourceforge/unitsinjava/Grammar.peg index 74b8337c..2dbd5928 100644 --- a/src/net/sourceforge/unitsinjava/grammar.peg +++ b/app/src/main/java/net/sourceforge/unitsinjava/Grammar.peg @@ -5,10 +5,10 @@ // Units is a program for unit conversion originally written in C // by Adrian Mariano (adrian@cam.cornell.edu.). // Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, -// 2005, 2006, 2007, 2010 by Free Software Foundation, Inc. +// 2005, 2006, 2007, 2010, 2011 by Free Software Foundation, Inc. // // Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, -// 2009, 2010 by Roman R Redziejowski (roman.redz@tele2.se). +// 2009, 2010, 2011, 2012 by Roman R Redziejowski (www.romanredz.se). // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -27,14 +27,39 @@ // // Change log // -// 091024 Created for version 1.87.J01. -// 101101 Version 1.88.J01: Removed superfluous '?' after 'space'. +// Version 1.87.J01. +// 091024 Created. +// +// Version 1.88.J01. +// 101101 Removed superfluous '?' after 'space'. +// +// Version 1.88.J02. +// 110317 Added !"." after 'number' to reject as invalid the strings +// such as '1..2' and '1.2.3' that would otherwise be treated +// as two numbers multiplied by juxtaposition. +// +// Version 1.89.J01 +// 111013 Changed !"." after 'number' to '!point', with point = [.,] +// expecting local number formats in the future. +// Added ',' as the forbidden start character of 'word'. +// Redefined 'namechar' using ^[s]. +// 120130 Renamed from 'grammar' to 'Grammar'. +// 120202 Updated syntax of 'word' according to Section 9.2 of GNU units +// manual edition 1.89e, adding decimal comma and tilde +// to forbidden start characters. // //========================================================================= + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// Parsing Expression Grammar of unit expressions +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + unitexpr = space expr? EOT {unitexpr} ~{error}; -expr = term ((PLUS / MINUS) term)* {expr} +expr = term ((PLUS / MINUS) term)* {expr} / (SLASH / PER) product {inverse} ; term = product ((STAR / SLASH / PER) product)* {term} ; @@ -45,17 +70,17 @@ factor = unary ((HAT / STARSTAR) unary)* {factor}; unary = (PLUS / MINUS)? primary {unary} ; -primary = numexpr {makeNumUnit} +primary = numexpr {makeNumUnit} / LPAR expr RPAR {pass2} / unitname {pass} / bfunc LPAR expr RPAR {evalBfunc} / opttilde dfunc LPAR expr RPAR {evalUfunc} ; - + numexpr = number (BAR number)* {numexpr}; -number = mantissa exponent? space {number} ; +number = mantissa exponent? !point space {number} ; -mantissa = "." digits / digits ("." digits?)? ; +mantissa = "." digits / digits ("." digits?)? ; exponent = [Ee] sign? digits ; @@ -65,9 +90,11 @@ digits = digit+ ; digit = [0-9] ; -word = ![.0123456789~]namechar+ ; +word = ![0123456789_.,~] namechar+ ; + +namechar = ^[\t\n +-*/|^();#] ; -namechar = ![\t\n^ +-*/|()]_ ; +point = [.,] <'.' or ','> ; opttilde = TILDE? <~> ; @@ -85,7 +112,7 @@ RPAR = ")" space <)> ; SLASH = "/" space ; STARSTAR = "**" space <**> ; STAR = "*" !"*" space <*> ; -PER = "per" &[\t\n^ +-*/|()] space <'per'> ; +PER = "per" !namechar space <'per'> ; space = [ \t]* {space} ; diff --git a/src/net/sourceforge/unitsinjava/DefinedFunction.java b/app/src/main/java/net/sourceforge/unitsinjava/Ignore.java similarity index 57% rename from src/net/sourceforge/unitsinjava/DefinedFunction.java rename to app/src/main/java/net/sourceforge/unitsinjava/Ignore.java index 08a072bf..fde2f294 100644 --- a/src/net/sourceforge/unitsinjava/DefinedFunction.java +++ b/app/src/main/java/net/sourceforge/unitsinjava/Ignore.java @@ -1,64 +1,50 @@ -//========================================================================= -// -// Part of units package -- a Java version of GNU Units program. -// -// Units is a program for unit conversion originally written in C -// by Adrian Mariano (adrian@cam.cornell.edu.). -// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, -// 2005, 2006, 2007 by Free Software Foundation, Inc. -// -// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, -// 2009 by Roman R Redziejowski (roman.redz@tele2.se). -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -//------------------------------------------------------------------------- -// -// Change log -// -// 050203 Version 1.84.J05. Do not initialize table. -// 050315 Version 1.84.J07. Changed package name to "units". -// 091024 Version 1.87.J01. Used generics for 'table'. -// -//========================================================================= - -package net.sourceforge.unitsinjava; - -import java.util.Hashtable; - - - -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -// -// class DefinedFunction -// -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -/** - * A defined function (computed or tabular). - */ - -public abstract class DefinedFunction extends Function -{ - //------------------------------------------------------------------- - /** Table of defined functions. */ - //------------------------------------------------------------------- - public static Hashtable table = null; - -public abstract Value getConformability(); - //===================================================================== - // Construct object for function 'nam' appearing at 'loc'. - //===================================================================== - DefinedFunction(String nam, Location loc) - { super(nam,loc); } -} +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2009, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2011, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.89.J01 +// 120209 Created. +// Moved from Factor, as enum can be a separate class definition. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// enum Ignore +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * Definition of keywords used in methods to checking Products + * and Values for compatibility. + */ + +public enum Ignore {NONE, PRIMITIVE, DIMLESS}; \ No newline at end of file diff --git a/src/net/sourceforge/unitsinjava/Location.java b/app/src/main/java/net/sourceforge/unitsinjava/Location.java similarity index 60% rename from src/net/sourceforge/unitsinjava/Location.java rename to app/src/main/java/net/sourceforge/unitsinjava/Location.java index cbc92c97..d8666db3 100644 --- a/src/net/sourceforge/unitsinjava/Location.java +++ b/app/src/main/java/net/sourceforge/unitsinjava/Location.java @@ -1,87 +1,103 @@ -//========================================================================= -// -// Part of units package -- a Java version of GNU Units program. -// -// Units is a program for unit conversion originally written in C -// by Adrian Mariano (adrian@cam.cornell.edu.). -// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, -// 2005, 2006, 2007 by Free Software Foundation, Inc. -// -// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, -// 2009 by Roman R Redziejowski (roman.redz@tele2.se). -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -//------------------------------------------------------------------------- -// -// Change log -// -// 050315 Version 1.84.J07. Changed package name to "units". -// -//========================================================================= - -package net.sourceforge.unitsinjava; - - - -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -// -// class Location -// -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -/** - * Identifies location of a piece of text in a file. - */ - public class Location -{ - final File file; // File - final int lineNum; // Line number - final int beginChar; // Starting character index - final int endChar; // Ending character index - - -//======================================================================= -// Constructor -//======================================================================= -/** - * Constructs dummy Location for built-in entity. - */ -Location() - { - file = null; - lineNum = -1; - beginChar = -1; - endChar = -1; - } - - -//======================================================================= -// Constructor -//======================================================================= -/** - * Constructs Location object. - * - * @param fil file name. - * @param line line number. - * @param begin starting character index. - * @param end ending character index. - */ -Location(final File fil, int line, int begin, int end) - { - file = fil; - lineNum = line; - beginChar = begin; - endChar = end; - } -} +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2009, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.89.J01. +// 120121 Renamed 'File' to 'UnitsFile'. +// 120129 Added method 'where'. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class Location +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * Identifies location of a piece of text in a units file. + * Note that line numbers start with 1. + */ + public class Location +{ + final UnitsFile file; // UnitsFile object for the file. + final int lineNum; // Number of the first line containing the text. + final int beginChar; // Index of first character in String-mapped file. + final int endChar; // Index of last character in String-mapped file. + + +//======================================================================= +// Constructor +//======================================================================= +/** + * Constructs dummy Location object. + */ +Location() + { + file = null; + lineNum = -1; + beginChar = -1; + endChar = -1; + } + + +//======================================================================= +// Constructor +//======================================================================= +/** + * Constructs Location object. + * + * @param file UnitsFile object for the file. + * @param line Number of the first line containing the text. + * @param begin Index of first character in String-mapped file. + * @param end Index of last character in String-mapped file. + */ +Location(final UnitsFile file, int line, int begin, int end) + { + this.file = file; + lineNum = line; + beginChar = begin; + endChar = end; + } + + +//======================================================================= +// where +//======================================================================= +/** + * @return String that describes the location. + */ + String where() + { return "'" + file.name + "', line " + lineNum ; } +} diff --git a/src/net/sourceforge/unitsinjava/Parser.java b/app/src/main/java/net/sourceforge/unitsinjava/Parser.java similarity index 80% rename from src/net/sourceforge/unitsinjava/Parser.java rename to app/src/main/java/net/sourceforge/unitsinjava/Parser.java index 6dddaf2a..d2dc248d 100644 --- a/src/net/sourceforge/unitsinjava/Parser.java +++ b/app/src/main/java/net/sourceforge/unitsinjava/Parser.java @@ -1,7 +1,7 @@ //========================================================================= // -// This file was generated by Mouse 1.3 at 2010-11-02 16:19:27 GMT\nfrom -// grammar 'D:\Units\ units\grammar.peg'. +// This file was generated by Mouse 1.5 at 2012-04-06 19:20:20 GMT +// from grammar 'D:\Units\ units\Grammar.peg'. // //========================================================================= @@ -53,17 +53,19 @@ public Semantics semantics() //===================================================================== // unitexpr = space expr? EOT {unitexpr} ~{error} ; //===================================================================== - boolean unitexpr() + private boolean unitexpr() { begin("unitexpr"); - if (unitexpr_0()) {sem.unitexpr(); return accept();} else sem.error(); + if (unitexpr_0()) + { sem.unitexpr(); return accept(); } + else sem.error(); return reject(); } //------------------------------------------------------------------- // unitexpr_0 = space expr? EOT //------------------------------------------------------------------- - boolean unitexpr_0() + private boolean unitexpr_0() { begin(""); space(); @@ -76,18 +78,20 @@ boolean unitexpr_0() // expr = term ((PLUS / MINUS) term)* {expr} / (SLASH / PER) product // {inverse} ; //===================================================================== - boolean expr() + private boolean expr() { begin("expr"); - if (expr_0()) {sem.expr(); return accept();} - if (expr_1()) {sem.inverse(); return accept();} + if (expr_0()) + { sem.expr(); return accept(); } + if (expr_1()) + { sem.inverse(); return accept(); } return reject(); } //------------------------------------------------------------------- // expr_0 = term ((PLUS / MINUS) term)* //------------------------------------------------------------------- - boolean expr_0() + private boolean expr_0() { begin(""); if (!term()) return rejectInner(); @@ -98,7 +102,7 @@ boolean expr_0() //------------------------------------------------------------------- // expr_1 = (SLASH / PER) product //------------------------------------------------------------------- - boolean expr_1() + private boolean expr_1() { begin(""); if (!SLASH() @@ -111,7 +115,7 @@ boolean expr_1() //------------------------------------------------------------------- // expr_2 = (PLUS / MINUS) term //------------------------------------------------------------------- - boolean expr_2() + private boolean expr_2() { begin(""); if (!PLUS() @@ -124,7 +128,7 @@ boolean expr_2() //===================================================================== // term = product ((STAR / SLASH / PER) product)* {term} ; //===================================================================== - boolean term() + private boolean term() { begin("term"); if (!product()) return reject(); @@ -136,7 +140,7 @@ boolean term() //------------------------------------------------------------------- // term_0 = (STAR / SLASH / PER) product //------------------------------------------------------------------- - boolean term_0() + private boolean term_0() { begin(""); if (!STAR() @@ -150,7 +154,7 @@ boolean term_0() //===================================================================== // product = factor (![+-] factor)* {product} ; //===================================================================== - boolean product() + private boolean product() { begin("product"); if (!factor()) return reject(); @@ -162,7 +166,7 @@ boolean product() //------------------------------------------------------------------- // product_0 = ![+-] factor //------------------------------------------------------------------- - boolean product_0() + private boolean product_0() { begin(""); if (!aheadNotIn("+-")) return rejectInner(); @@ -173,7 +177,7 @@ boolean product_0() //===================================================================== // factor = unary ((HAT / STARSTAR) unary)* {factor} ; //===================================================================== - boolean factor() + private boolean factor() { begin("factor"); if (!unary()) return reject(); @@ -185,7 +189,7 @@ boolean factor() //------------------------------------------------------------------- // factor_0 = (HAT / STARSTAR) unary //------------------------------------------------------------------- - boolean factor_0() + private boolean factor_0() { begin(""); if (!HAT() @@ -198,7 +202,7 @@ boolean factor_0() //===================================================================== // unary = (PLUS / MINUS)? primary {unary} ; //===================================================================== - boolean unary() + private boolean unary() { begin("unary"); unary_0(); @@ -210,7 +214,7 @@ boolean unary() //------------------------------------------------------------------- // unary_0 = PLUS / MINUS //------------------------------------------------------------------- - boolean unary_0() + private boolean unary_0() { begin(""); if (PLUS()) return acceptInner(); @@ -223,21 +227,26 @@ boolean unary_0() // {pass} / bfunc LPAR expr RPAR {evalBfunc} / opttilde dfunc LPAR // expr RPAR {evalUfunc} ; //===================================================================== - boolean primary() + private boolean primary() { begin("primary"); - if (numexpr()) {sem.makeNumUnit(); return accept();} - if (primary_0()) {sem.pass2(); return accept();} - if (unitname()) {sem.pass(); return accept();} - if (primary_1()) {sem.evalBfunc(); return accept();} - if (primary_2()) {sem.evalUfunc(); return accept();} + if (numexpr()) + { sem.makeNumUnit(); return accept(); } + if (primary_0()) + { sem.pass2(); return accept(); } + if (unitname()) + { sem.pass(); return accept(); } + if (primary_1()) + { sem.evalBfunc(); return accept(); } + if (primary_2()) + { sem.evalUfunc(); return accept(); } return reject(); } //------------------------------------------------------------------- // primary_0 = LPAR expr RPAR //------------------------------------------------------------------- - boolean primary_0() + private boolean primary_0() { begin(""); if (!LPAR()) return rejectInner(); @@ -249,7 +258,7 @@ boolean primary_0() //------------------------------------------------------------------- // primary_1 = bfunc LPAR expr RPAR //------------------------------------------------------------------- - boolean primary_1() + private boolean primary_1() { begin(""); if (!bfunc()) return rejectInner(); @@ -262,7 +271,7 @@ boolean primary_1() //------------------------------------------------------------------- // primary_2 = opttilde dfunc LPAR expr RPAR //------------------------------------------------------------------- - boolean primary_2() + private boolean primary_2() { begin(""); opttilde(); @@ -276,7 +285,7 @@ boolean primary_2() //===================================================================== // numexpr = number (BAR number)* {numexpr} ; //===================================================================== - boolean numexpr() + private boolean numexpr() { begin("numexpr"); if (!number()) return reject(); @@ -288,7 +297,7 @@ boolean numexpr() //------------------------------------------------------------------- // numexpr_0 = BAR number //------------------------------------------------------------------- - boolean numexpr_0() + private boolean numexpr_0() { begin(""); if (!BAR()) return rejectInner(); @@ -297,22 +306,33 @@ boolean numexpr_0() } //===================================================================== - // number = mantissa exponent? space {number} ; + // number = mantissa exponent? !point space {number} ; //===================================================================== - boolean number() + private boolean number() { begin("number"); if (!mantissa()) return reject(); exponent(); + if (!number_0()) return reject(); space(); sem.number(); return accept(); } + //------------------------------------------------------------------- + // number_0 = !point + //------------------------------------------------------------------- + private boolean number_0() + { + begin("","not '.' or ','"); + if (point()) return rejectNot(); + return acceptNot(); + } + //===================================================================== // mantissa = "." digits / digits ("." digits?)? ; //===================================================================== - boolean mantissa() + private boolean mantissa() { begin("mantissa"); if (mantissa_0()) return accept(); @@ -323,7 +343,7 @@ boolean mantissa() //------------------------------------------------------------------- // mantissa_0 = "." digits //------------------------------------------------------------------- - boolean mantissa_0() + private boolean mantissa_0() { begin(""); if (!next('.')) return rejectInner(); @@ -334,7 +354,7 @@ boolean mantissa_0() //------------------------------------------------------------------- // mantissa_1 = digits ("." digits?)? //------------------------------------------------------------------- - boolean mantissa_1() + private boolean mantissa_1() { begin(""); if (!digits()) return rejectInner(); @@ -345,7 +365,7 @@ boolean mantissa_1() //------------------------------------------------------------------- // mantissa_2 = "." digits? //------------------------------------------------------------------- - boolean mantissa_2() + private boolean mantissa_2() { begin(""); if (!next('.')) return rejectInner(); @@ -356,7 +376,7 @@ boolean mantissa_2() //===================================================================== // exponent = [Ee] sign? digits ; //===================================================================== - boolean exponent() + private boolean exponent() { begin("exponent"); if (!nextIn("Ee")) return reject(); @@ -366,9 +386,9 @@ boolean exponent() } //===================================================================== - // sign (sign) = [+-] ; + // sign = [+-] ; //===================================================================== - boolean sign() + private boolean sign() { begin("sign","sign"); if (!nextIn("+-")) return reject(); @@ -378,7 +398,7 @@ boolean sign() //===================================================================== // digits = digit+ ; //===================================================================== - boolean digits() + private boolean digits() { begin("digits"); if (!digit()) return reject(); @@ -389,7 +409,7 @@ boolean digits() //===================================================================== // digit = [0-9] ; //===================================================================== - boolean digit() + private boolean digit() { begin("digit"); if (!nextIn('0','9')) return reject(); @@ -397,32 +417,41 @@ boolean digit() } //===================================================================== - // word = ![.0123456789~] namechar+ ; + // word = ![0123456789_.,~] namechar+ ; //===================================================================== - boolean word() + private boolean word() { begin("word"); - if (!aheadNotIn(".0123456789~")) return reject(); + if (!aheadNotIn("0123456789_.,~")) return reject(); if (!namechar()) return reject(); while (namechar()); return accept(); } //===================================================================== - // namechar (more name) = ![\t\n^ +-*/|()] _ ; + // namechar = ^[\t\n +-*/|^();#] ; //===================================================================== - boolean namechar() + private boolean namechar() { begin("namechar","more name"); - if (!aheadNotIn(" \n^ +-*/|()")) return reject(); - if (!next()) return reject(); + if (!nextNotIn("\t\n +-*/|^();#")) return reject(); + return accept(); + } + + //===================================================================== + // point = [.,] <'.' or ','> ; + //===================================================================== + private boolean point() + { + begin("point","'.' or ','"); + if (!nextIn(".,")) return reject(); return accept(); } //===================================================================== - // opttilde (~) = TILDE? ; + // opttilde = TILDE? <~> ; //===================================================================== - boolean opttilde() + private boolean opttilde() { begin("opttilde","~"); TILDE(); @@ -430,45 +459,48 @@ boolean opttilde() } //===================================================================== - // unitname (unit name) = word space {&unitname} ; + // unitname = word space {&unitname} ; //===================================================================== - boolean unitname() + private boolean unitname() { begin("unitname","unit name"); if (!word()) return reject(); space(); if (sem.unitname()) return accept(); + boolReject(); return reject(); } //===================================================================== - // bfunc (function name) = word space {&bfunc} ; + // bfunc = word space {&bfunc} ; //===================================================================== - boolean bfunc() + private boolean bfunc() { begin("bfunc","function name"); if (!word()) return reject(); space(); if (sem.bfunc()) return accept(); + boolReject(); return reject(); } //===================================================================== - // dfunc (function name) = word space {&ufunc} ; + // dfunc = word space {&ufunc} ; //===================================================================== - boolean dfunc() + private boolean dfunc() { begin("dfunc","function name"); if (!word()) return reject(); space(); if (sem.ufunc()) return accept(); + boolReject(); return reject(); } //===================================================================== - // BAR (|) = "|" space ; + // BAR = "|" space <|> ; //===================================================================== - boolean BAR() + private boolean BAR() { begin("BAR","|"); if (!next('|')) return reject(); @@ -477,9 +509,9 @@ boolean BAR() } //===================================================================== - // HAT (^) = "^" space ; + // HAT = "^" space <^> ; //===================================================================== - boolean HAT() + private boolean HAT() { begin("HAT","^"); if (!next('^')) return reject(); @@ -488,9 +520,9 @@ boolean HAT() } //===================================================================== - // TILDE (~) = "~" space ; + // TILDE = "~" space <~> ; //===================================================================== - boolean TILDE() + private boolean TILDE() { begin("TILDE","~"); if (!next('~')) return reject(); @@ -499,9 +531,9 @@ boolean TILDE() } //===================================================================== - // LPAR (() = "(" space ; + // LPAR = "(" space <(> ; //===================================================================== - boolean LPAR() + private boolean LPAR() { begin("LPAR","("); if (!next('(')) return reject(); @@ -510,9 +542,9 @@ boolean LPAR() } //===================================================================== - // MINUS (-) = "-" space ; + // MINUS = "-" space <-> ; //===================================================================== - boolean MINUS() + private boolean MINUS() { begin("MINUS","-"); if (!next('-')) return reject(); @@ -521,9 +553,9 @@ boolean MINUS() } //===================================================================== - // PLUS (+) = "+" space ; + // PLUS = "+" space <+> ; //===================================================================== - boolean PLUS() + private boolean PLUS() { begin("PLUS","+"); if (!next('+')) return reject(); @@ -532,9 +564,9 @@ boolean PLUS() } //===================================================================== - // RPAR ()) = ")" space ; + // RPAR = ")" space <)> ; //===================================================================== - boolean RPAR() + private boolean RPAR() { begin("RPAR",")"); if (!next(')')) return reject(); @@ -543,9 +575,9 @@ boolean RPAR() } //===================================================================== - // SLASH (/) = "/" space ; + // SLASH = "/" space ; //===================================================================== - boolean SLASH() + private boolean SLASH() { begin("SLASH","/"); if (!next('/')) return reject(); @@ -554,9 +586,9 @@ boolean SLASH() } //===================================================================== - // STARSTAR (**) = "**" space ; + // STARSTAR = "**" space <**> ; //===================================================================== - boolean STARSTAR() + private boolean STARSTAR() { begin("STARSTAR","**"); if (!next("**")) return reject(); @@ -565,9 +597,9 @@ boolean STARSTAR() } //===================================================================== - // STAR (*) = "*" !"*" space ; + // STAR = "*" !"*" space <*> ; //===================================================================== - boolean STAR() + private boolean STAR() { begin("STAR","*"); if (!next('*')) return reject(); @@ -577,32 +609,42 @@ boolean STAR() } //===================================================================== - // PER ('per') = "per" &[\t\n^ +-*/|()] space ; + // PER = "per" !namechar space <'per'> ; //===================================================================== - boolean PER() + private boolean PER() { begin("PER","'per'"); if (!next("per")) return reject(); - if (!aheadIn(" \n^ +-*/|()")) return reject(); + if (!PER_0()) return reject(); space(); return accept(); } + //------------------------------------------------------------------- + // PER_0 = !namechar + //------------------------------------------------------------------- + private boolean PER_0() + { + begin("","not more name"); + if (namechar()) return rejectNot(); + return acceptNot(); + } + //===================================================================== // space = [ \t]* {space} ; //===================================================================== - boolean space() + private boolean space() { begin("space"); - while (nextIn(" ")); + while (nextIn(" \t")); sem.space(); return accept(); } //===================================================================== - // EOT (end of input) = !_ ; + // EOT = !_ ; //===================================================================== - boolean EOT() + private boolean EOT() { begin("EOT","end of input"); if (!aheadNot()) return reject(); diff --git a/src/net/sourceforge/unitsinjava/ParserBase.java b/app/src/main/java/net/sourceforge/unitsinjava/ParserBase.java similarity index 72% rename from src/net/sourceforge/unitsinjava/ParserBase.java rename to app/src/main/java/net/sourceforge/unitsinjava/ParserBase.java index 98640927..65d5e501 100644 --- a/src/net/sourceforge/unitsinjava/ParserBase.java +++ b/app/src/main/java/net/sourceforge/unitsinjava/ParserBase.java @@ -2,7 +2,8 @@ // // Part of PEG parser generator Mouse. // -// Copyright (C) 2009, 2010 by Roman R. Redziejowski (www.romanredz.se). +// Copyright (C) 2009, 2010, 2011, 2012 +// by Roman R. Redziejowski (www.romanredz.se). // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,6 +28,20 @@ // 100429 Bug fix in errMerge(Phrase): assignment to errText replaced // by clear + addAll (assignment produced alias resulting in // explosion of errText in memo version). +// 101105 Changed errMerge(msg,pos) to errAdd(who). +// 101105 Commented error handling. +// 101129 Added 'boolReject'. +// 101203 Convert result of 'listErr' to printable. +// Version 1.4 +// 110918 Changed 'listErr' to separate 'not' texts as 'not expected'. +// 111004 Added methods to implement ^[s]. +// 111004 Implemented method 'where' of Phrase. +// Version 1.5 +// 111027 Revised methods for ^[s] and ^[c]. +// 111104 Implemented methods 'rule' and 'isTerm' of Phrase. +// Version 1.5.1 +// 120102 (Steve Owens) Ensure failure() method does not emit blank +// line when error info is absent. // //========================================================================= @@ -113,8 +128,8 @@ public void setTrace(String trace) //------------------------------------------------------------------- protected boolean failure() { - String message = current.errMsg(); - System.out.println(message.replace("\n","\\n").replace("\t","\\t").replace("\r","\\r")); + if (current.errPos>=0) + System.out.println(current.errMsg()); return false; } @@ -142,6 +157,12 @@ protected void begin(final String name,final String diag) //------------------------------------------------------------------- // Accept Rule + // Note: 'upgrade error info' is applied when rule such as + // R = A/B/C/... or R = (A/B/C/...)* consumed empty string after one + // or more of A,B,C failed without advancing cursor. + // In case of a later failure, it gives message 'expected R', + // which is not strictly correct because R succeeded, but is + // more comprehensible than 'expected A or B or C or ...'. //------------------------------------------------------------------- protected boolean accept() { @@ -156,20 +177,6 @@ protected boolean accept() return true; } - //------------------------------------------------------------------- - // Accept Rule by true return from boolean action - //------------------------------------------------------------------- - protected boolean acceptBoolean() - { - Phrase p = pop(); // Pop p from compile stack - p.rhs = null; // Remove right-hand side of p - p.errClear(); - p.success = true; // Indicate p successful - current.end = pos; // Update end of parent - current.rhs.add(p); // Attach p to rhs of parent - return true; - } - //------------------------------------------------------------------- // Accept Inner //------------------------------------------------------------------- @@ -185,6 +192,7 @@ protected boolean acceptInner() //------------------------------------------------------------------- // Accept And-predicate (argument was accepted) + // Note: we ignore all failures encountered in processing the argument. //------------------------------------------------------------------- protected boolean acceptAnd() { @@ -199,6 +207,7 @@ protected boolean acceptAnd() //------------------------------------------------------------------- // Accept Not-predicate (argument was rejected) + // Note: we ignore all failures encountered in processing the argument. //------------------------------------------------------------------- protected boolean acceptNot() { @@ -213,6 +222,10 @@ protected boolean acceptNot() //------------------------------------------------------------------- // Reject Rule + // Note: 'upgrade error info' is applied when rule such as + // R = A/B/C/... failed after one or more of A,B,C failed without + // advancing cursor. In case of a later failure, it gives message + // 'expected R', instead of 'expected A or B or C or ...'. //------------------------------------------------------------------- protected boolean reject() { @@ -228,19 +241,17 @@ protected boolean reject() } //------------------------------------------------------------------- - // Reject Rule by false return from boolean action + // Simulate failure after boolean action returned false. + // Note: the action was called after the Rule accepted some text. + // We ignore all failures encountered in the process + // and report failure at the start of the text. //------------------------------------------------------------------- - protected boolean rejectBoolean() + protected boolean boolReject() { - Phrase p = pop(); // Pop p from compile stack - p.end = p.start; // Reset end of p - p.rhs = null; // Remove right-hand side of p - System.out.println(p.diag + " " + source.where(p.start)); - System.out.println(current.errTxt + " " + current.errPos); - p.errSet(p.diag,p.start); - p.success = false; // Indicate p failed - current.errMerge(p); // Merge error info with parent - pos = p.start; // Backtrack to start of p + pos = current.start; // Backtrack to start + current.end = pos; // Reset end + current.rhs.clear(); // Clear right-hand side + current.errSet(current.diag,pos);// Register failure return false; } @@ -260,6 +271,8 @@ protected boolean rejectInner() //------------------------------------------------------------------- // Reject And-predicate (argument was rejected) + // Note: we ignore all failures encountered in processing the argument, + // and register failure at the point of call of the predicate. //------------------------------------------------------------------- protected boolean rejectAnd() { @@ -273,6 +286,8 @@ protected boolean rejectAnd() //------------------------------------------------------------------- // Reject Not-predicate (argument was accepted) + // Note: we ignore all failures encountered in processing the argument, + // and register failure at the point of call of the predicate. //------------------------------------------------------------------- protected boolean rejectNot() { @@ -297,7 +312,16 @@ protected boolean next(char ch) } //------------------------------------------------------------------- - // Execute expression &'c' + // Execute expression ^'c' + //------------------------------------------------------------------- + protected boolean nextNot(char ch) + { + if (pos errTxt = new Vector(); + Vector errTxt = new Vector(); //=================================================================== @@ -544,11 +594,23 @@ public char charAt(int i) public boolean isEmpty() { return start==end; } - //----------------------------------------------------------------- - // Is this s? - //----------------------------------------------------------------- - public boolean isA(String s) - { return name.equals(s); } + //------------------------------------------------------------------- + // Get name of rule that created this Phrase. + //------------------------------------------------------------------- + public String rule() + { return name; } + + //------------------------------------------------------------------- + // Was this Phrase created by rule 'rule'? + //------------------------------------------------------------------- + public boolean isA(String rule) + { return name.equals(rule); } + + //------------------------------------------------------------------- + // Was this Phrase created by a terminal? + //------------------------------------------------------------------- + public boolean isTerm() + { return name.length() == 0; } //----------------------------------------------------------------- // Get error message @@ -556,11 +618,11 @@ public boolean isA(String s) public String errMsg() { if (errPos<0) return ""; - return source.where(errPos) + ": expected " + listErr(); + return source.where(errPos) + ":" + listErr(); } //----------------------------------------------------------------- - // Clear error message + // Clear error information //----------------------------------------------------------------- public void errClear() { @@ -568,6 +630,12 @@ public void errClear() errPos = -1; } + //----------------------------------------------------------------- + // Describe position of i-th character of the Phrase in source text. + //----------------------------------------------------------------- + public String where(int i) + { return source.where(start+i); } + //=================================================================== // @@ -575,33 +643,36 @@ public void errClear() // //=================================================================== - void errSet(final String msg, int where) + //----------------------------------------------------------------- + // Set fresh info ('who' failed 'where'), discarding any previous. + //----------------------------------------------------------------- + void errSet(final String who, int where) { errTxt.clear(); - errTxt.add(msg); + errTxt.add(who); errPos = where; } - void errMerge(final String msg, int newPos) + //----------------------------------------------------------------- + // Add info about 'who' failing at current position. + //----------------------------------------------------------------- + void errAdd(final String who) { - if (errPosnewPos) return; // If new position older: forget - if (errPospos) return; // If current position older: forget + if (errPos done = new Vector(); for (String s: errTxt) { if (done.contains(s)) continue; - sb.append(sp + s); done.add(s); - sp = " or "; + if (s.startsWith("not ")) + toPrint(" or " + s.substring(4),two); + else + toPrint(" or " + s,one); + } + + if (one.length()>0) + { + if (two.length()==0) + return " expected " + one.toString().substring(4); + else + return " expected " + one.toString().substring(4) + + "; not expected " + two.toString().substring(4); + } + else + return " not expected " + two.toString().substring(4); + } + + //----------------------------------------------------------------- + // Convert string to printable and append to StringBuilder. + //----------------------------------------------------------------- + private void toPrint(final String s, StringBuilder sb) + { + for (int i=0;i256) + { + String u = "000" + Integer.toHexString(c); + sb.append("\\u" + u.substring(u.length()-4,u.length())); + } + else sb.append(c); + continue; + } } - return sb.toString(); } } diff --git a/app/src/main/java/net/sourceforge/unitsinjava/ParserMemo.java b/app/src/main/java/net/sourceforge/unitsinjava/ParserMemo.java new file mode 100644 index 00000000..50d05e14 --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/ParserMemo.java @@ -0,0 +1,224 @@ +//========================================================================= +// +// Part of PEG parser generator Mouse. +// +// Copyright (C) 2009, 2010 +// by Roman R. Redziejowski (www.romanredz.se). +// +// 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. +// +//------------------------------------------------------------------------- +// +// Change log +// 090721 Created for Mouse 1.1. +// Version 1.3 +// 100504 Added c.diag to arguments of begin in saved and savedInner. +// 100504 In Cache(String) set diag to name instead of null. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import net.sourceforge.unitsinjava.Source; + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// ParserMemo +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + + +public class ParserMemo extends ParserBase +{ + //------------------------------------------------------------------- + // Cache size. + //------------------------------------------------------------------- + int cacheSize = 1; + + //------------------------------------------------------------------- + // Phrase to reuse. + //------------------------------------------------------------------- + Phrase reuse; + + //------------------------------------------------------------------- + // List of Cache objects for initialization. + //------------------------------------------------------------------- + protected Cache[] caches; + + //------------------------------------------------------------------- + // Constructor + //------------------------------------------------------------------- + protected ParserMemo() + {} + + //------------------------------------------------------------------- + // Initialize + //------------------------------------------------------------------- + public void init(Source src) + { + super.init(src); + for (Cache c: caches) // Reset Cache objects + c.reset(); + } + + //------------------------------------------------------------------- + // Set cache size. + //------------------------------------------------------------------- + public void setMemo(int m) + { + if (m<1 | m>9) throw new Error("m=" + m + " outside range 1-9"); + cacheSize = m; + } + + + //===================================================================== + // + // Methods called from parsing procedures + // + //===================================================================== + //------------------------------------------------------------------- + // If saved result found, use it, otherwise begin new procedure. + // Version for Rule. + //------------------------------------------------------------------- + protected boolean saved(Cache c) + { + reuse = c.find(); + if (reuse!=null) // If found Phrase to reuse.. + return true; // .. return + + begin(c.name,c.diag); // Otherwise push new Phrase + c.save(current); // .. and cache it + return false; + } + + //------------------------------------------------------------------- + // If saved result found, use it, otherwise begin new procedure. + // Version for Inner. + //------------------------------------------------------------------- + protected boolean savedInner(Cache c) + { + reuse = c.find(); + if (reuse!=null) // If found Phrase to reuse.. + return true; // .. return + + begin("",c.diag); // Otherwise push new Phrase + c.save(current); // .. and cache it + return false; + } + + //------------------------------------------------------------------- + // Reuse Rule + //------------------------------------------------------------------- + protected boolean reuse() + { + if (reuse.success) + { + pos = reuse.end; // Update position + current.end = pos; // Update end of current + current.rhs.add(reuse); // Attach p to rhs of current + current.errMerge(reuse); // Merge error info with current + return true; + } + else + { + current.errMerge(reuse); // Merge error info with current + return false; + } + } + + //------------------------------------------------------------------- + // Reuse Inner + //------------------------------------------------------------------- + protected boolean reuseInner() + { + if (reuse.success) + { + pos = reuse.end; // Update position + current.end = pos; // Update end of current + current.rhs.addAll(reuse.rhs); // Add rhs to rhs of current + current.errMerge(reuse); // Merge error info with current + return true; + } + else + { + current.errMerge(reuse); // Merge error info with current + return false; + } + } + + //------------------------------------------------------------------- + // Reuse predicate + //------------------------------------------------------------------- + protected boolean reusePred() + { + if (reuse.success) + return true; + else + { + current.errMerge(reuse); // Merge error info with current + return false; + } + } + + + //HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + // + // Cache + // + //HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + + protected class Cache + { + public final String name; + public final String diag; + + Phrase[] cache; + int last; + + public Cache(final String name) + { + this.name = name; + this.diag = name; + } + + public Cache(final String name, final String diag) + { + this.name = name; + this.diag = diag; + } + + void reset() + { + cache = new Phrase[cacheSize]; + last = 0; + } + + void save(Phrase p) + { + last = (last+1)%cacheSize; + cache[last] = p; + } + + Phrase find() + { + for (Phrase p: cache) + if (p!=null && p.start==pos) return p; + return null; + } + } + +} + + + diff --git a/src/net/sourceforge/unitsinjava/Phrase.java b/app/src/main/java/net/sourceforge/unitsinjava/Phrase.java similarity index 74% rename from src/net/sourceforge/unitsinjava/Phrase.java rename to app/src/main/java/net/sourceforge/unitsinjava/Phrase.java index efc22d66..37a93143 100644 --- a/src/net/sourceforge/unitsinjava/Phrase.java +++ b/app/src/main/java/net/sourceforge/unitsinjava/Phrase.java @@ -2,7 +2,7 @@ // // Part of PEG parser generator Mouse. // -// Copyright (C) 2009 by Roman R. Redziejowski (www.romanredz.se). +// Copyright (C) 2009, 2011 by Roman R. Redziejowski (www.romanredz.se). // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,6 +21,10 @@ // Change log // 090701 License changed by the author to Apache v.2. // 090717 Removed unused import of java.util.Vector. +// Version 1.4 +// 111004 Added method 'where'. +// Version 1.5 +// 111104 Added methods 'rule' and 'isTerm'. // //========================================================================= @@ -61,9 +65,19 @@ public interface Phrase boolean isEmpty(); //------------------------------------------------------------------- - // Is this s? + // Get name of rule that created this Phrase. //------------------------------------------------------------------- - boolean isA(String s); + String rule(); + + //------------------------------------------------------------------- + // Was this Phrase created by rule 'name'? + //------------------------------------------------------------------- + boolean isA(String name); + + //------------------------------------------------------------------- + // Was this Phrase created by a terminal? + //------------------------------------------------------------------- + boolean isTerm(); //------------------------------------------------------------------- // Get error message @@ -75,4 +89,9 @@ public interface Phrase //------------------------------------------------------------------- void errClear(); + //------------------------------------------------------------------- + // Describe position of i-th character of the Phrase in source text. + //------------------------------------------------------------------- + String where(int i); + } diff --git a/app/src/main/java/net/sourceforge/unitsinjava/Prefix.java b/app/src/main/java/net/sourceforge/unitsinjava/Prefix.java new file mode 100644 index 00000000..0f490e5f --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/Prefix.java @@ -0,0 +1,246 @@ +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// 050203 Do not initialize table. +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.86.J01. +// 061229 Corrected test for 'verbose'. +// +// Version 1.87.J01. +// 091024 Used generics for 'table'. +// 091031 Moved definition of Ignore to Factor. +// Replaced 'addtolist' by 'isCompatibleWith'. +// +// Version 1.89.J01 +// 120201 Adapted to use with File Parser: +// removed method 'accept' and added 'define'. +// 120208 Renamed one-argument method 'isCompatibleWith' +// to 'conformsTo' to avoid confusion with two-argument one +// defined in Product and Value. +// 120209 Definition of Ignore moved to separate file: +// replaced 'Factor.Ignore' by 'Ignore'. +// 120313 Added 'location.where' to messages from 'check'. +// 120316 Added methods 'desc' and 'conformsTo'. +// 120317 Changed 'define' to replace an earlier definition +// instead of ignoring re-definition. +// 120405 Used method 'startsWith' in 'find'. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import java.util.Hashtable; +import java.util.Enumeration; + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class Prefix +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * A prefix. + */ + + public class Prefix extends Factor +{ + //------------------------------------------------------------------- + // Table of Prefixes + //------------------------------------------------------------------- + public static Hashtable table = null; + + //------------------------------------------------------------------- + // Uned in conformability checks + //------------------------------------------------------------------- + public static Value one = new Value(); + + + //===================================================================== + // Constructor + //===================================================================== + /** + * Constructs a Prefix object. + * + * @param name the prefix. + * @param loc location where defined. + * @param def definition. + */ + Prefix(final String name, Location loc, final String def) + { super(name,loc,def); } + + + //===================================================================== + // define + //===================================================================== + /** + * Builds PrefixTable entry from a parsed definition. + * The definition is parsed as follows: + *
+   *    name-  definition
+   *  
+ * + * @param name the prefix. + * @param def definition. + * @param loc location where defined. + */ + public static void define(final String name, final String def, final Location loc) + { + //--------------------------------------------------------------- + // Get name without suffix '-'. + //--------------------------------------------------------------- + String prefname = name.substring(0,name.length()-1); + + //--------------------------------------------------------------- + // Prefix with incorrect syntax can never be accessed. + //--------------------------------------------------------------- + String diag = Entity.checkName(prefname); + + if (diag!=null) + { + Env.out.println + (loc.where() + ". Prefix '" + prefname + + "' is ignored. It " + diag + "."); + return; + } + + //--------------------------------------------------------------- + // Install the prefix in table. + //--------------------------------------------------------------- + Prefix old = table.put(prefname, new Prefix(prefname,loc,def)); + + //--------------------------------------------------------------- + // Write a message if an earlier definition was replaced. + //--------------------------------------------------------------- + if (old!=null) + { + Env.out.println + ("Prefix '" + name + "' defined in " + old.location.where() + + ", is redefined in " + loc.where() + "."); + } + } + + + //===================================================================== + // check + //===================================================================== + /** + * Checks definition of this Prefix for correctness. + * Writes diagnostics to 'Env.out'. + * Used by 'check' in 'Tables'. + */ + void check() + { + if (Env.verbose==2) + Env.out.println(location.where() + ". Doing '" + name + "'"); + + //--------------------------------------------------------------- + // check for bad '/' character in prefix + //--------------------------------------------------------------- + int plevel = 0; + for (int i=0;i enu=Prefix.table.elements();enu.hasMoreElements();) + { + Prefix p = enu.nextElement(); + int plg = p.name.length(); + if (plg>maxlg && name.startsWith(p.name)) + { + maxp = p; + maxlg = plg; + } + } + return maxp; + } + + + //===================================================================== + // conformsTo + //===================================================================== + /** Checks if this Prefix conforms to Value 'v'. + * Used by 'showConformable' in 'Tables'. + * + * @param v the Value to be checked against. + * @return true if this Prefix conforms to v, false otherwise. + */ + boolean conformsTo(final Value v) + { return one.isCompatibleWith(v,Ignore.DIMLESS); } + + + //===================================================================== + // desc + //===================================================================== + /** + * Returns short description of this Prefix. + * To be shown by 'showConformable' and 'showMatching' in 'Tables'. + * + * @return description. + */ + String desc() + { return (" " + def); } +} diff --git a/src/net/sourceforge/unitsinjava/Product.java b/app/src/main/java/net/sourceforge/unitsinjava/Product.java similarity index 64% rename from src/net/sourceforge/unitsinjava/Product.java rename to app/src/main/java/net/sourceforge/unitsinjava/Product.java index 7f2ce73e..6ebb374c 100644 --- a/src/net/sourceforge/unitsinjava/Product.java +++ b/app/src/main/java/net/sourceforge/unitsinjava/Product.java @@ -1,220 +1,292 @@ -//========================================================================= -// -// Part of units package -- a Java version of GNU Units program. -// -// Units is a program for unit conversion originally written in C -// by Adrian Mariano (adrian@cam.cornell.edu.). -// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, -// 2005, 2006, 2007 by Free Software Foundation, Inc. -// -// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, -// 2009 by Roman R Redziejowski (roman.redz@tele2.se). -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -//------------------------------------------------------------------------- -// -// Change log -// -// 050315 Version 1.84.J07. Changed package name to "units". -// 091024 Version 1.87.J01. -// Used generics for 'factors'. -// Used modified 'insertAlph'. -// 091025 Replaced 'Parser.Exception' by 'EvalError'. -// 091031 Moved definition of Ignore to Factor. -// 091101 'insertAlph' replaced by simple loop. -// -//========================================================================= - -package net.sourceforge.unitsinjava; - -import java.util.Vector; - - - -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -// -// class Product -// -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -/** - * A product of units and/or prefixes. - *
- * It may be empty, representing dimensionless number 1. - */ - - public class Product -{ - //------------------------------------------------------------------- - // The factors in a Product are represented as element of this Vector. - // The factors are Factor objects (units or prefixes). - // They are sorted in increasing alphabetic order of their names. - // Duplicates are allowed (mean a power>1 of the unit). - //------------------------------------------------------------------- - private Vector factors = new Vector(); - - //===================================================================== - // Default constructor. - //===================================================================== - Product() - { factors = new Vector(); } - - - //===================================================================== - // Copy constructor. - //===================================================================== - Product(final Product p) - { - for (Factor e: p.factors) - factors.add(e); - } - - - //===================================================================== - // Add Factor 'f' to the Product. - // Return the result. - // (Originally 'addsubunit'). - //===================================================================== - Product add (final Factor f) - { - for (int i=0;i getFactors(){ - return factors; - } - //===================================================================== - // Return true if this Product and Product "p" have the same factors, - // other than those marked dimensionless. - // (Originally 'compareproducts'.) - //===================================================================== - boolean isCompatibleWith(final Product p, Factor.Ignore ignore) - { - int i = 0; - int j = 0; - - while(true) - { - while(i0 && f==factor(i-1)) - counter++; - - // If s is first or distinct from preceding: - else - { - if (counter>1) - sb.append("^" + counter); - sb.append(" " + f.name); - counter = 1; - } - } - - if (counter>1) - sb.append("^" + counter); - - return sb.toString(); - } - - - //===================================================================== - // Return n-th root of the Product, or null if not n-th root. - // (Originally 'subunitroot'). - //===================================================================== - Product root(int n) - { - Product p = new Product(); - for (int i=0;i. +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.87.J01. +// 091024 Used generics for 'factors'. +// Used modified 'insertAlph'. +// 091025 Replaced 'Parser.Exception' by 'EvalError'. +// 091031 Moved definition of Ignore to Factor. +// 091101 'insertAlph' replaced by simple loop. +// +// Version 1.88.J02. +// 110314 Bug fix in 'root': did not accept product of n-th powers +// because of an extra increment of 'i'. +// +// Version 1.89.J01. +// 120209 Definition of Ignore moved to separate file: +// replaced 'Factor.Ignore' by 'Ignore'. +// 120209 Method 'isCompatibleWith' renamed to 'hasSameFactorsAs' +// to avoid confusion with method defined in Value. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import java.util.Vector; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class Product +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * A product of units and/or prefixes. + *
+ * It may be empty, representing dimensionless number 1. + */ + + public class Product +{ + //------------------------------------------------------------------- + /** The factors in a Product are represented as element of this Vector. + * The factors are Factor objects (units or prefixes). + * They are sorted in increasing ALPHABETIC ORDER of their names. + * Duplicates are allowed (mean a power>1 of the unit). */ + //------------------------------------------------------------------- + private Vector factors = new Vector(); + + //===================================================================== + // Default constructor. + //===================================================================== + /** + * Constructs empty Product. + */ + Product() + {} + + + //===================================================================== + // Copy constructor. + //===================================================================== + /** + * Constructs a copy of given Product. + * + * @param p Product to copy. + */ + Product(final Product p) + { + for (Factor f: p.factors) + factors.add(f); + } + + + //===================================================================== + // add Factor + //===================================================================== + /** + * Adds given Factor to this Product. + * (Originally 'addsubunit'). + * + * @param f Factor to be added. + * @return the modified Product. + */ + Product add (final Factor f) + { + for (int i=0;i getFactors() { + return factors; + } + + //===================================================================== + // hasSameFactors + //===================================================================== + /** + * Compares this Products with another Product. + * (Originally 'compareproducts'.) + * + * @param p Product to compare with. + * @param ignore indicates which Factors should be ignored. + * @return true if this Product and Product 'p' have the same + * factors, except those to be ignored. + * Otherwise false. + */ + boolean hasSameFactorsAs(final Product p, Ignore ignore) + { + int i = 0; + int j = 0; + + while(true) + { + while(i0 && f==factor(i-1)) + counter++; + + //------------------------------------------------------------- + // If s is first or distinct from preceding: + //------------------------------------------------------------- + else + { + if (counter>1) + sb.append("^" + counter); + sb.append(" " + f.name); + counter = 1; + } + } + + if (counter>1) + sb.append("^" + counter); + + return sb.toString(); + } + + + //===================================================================== + // root + //===================================================================== + /** + * Computes a root of this Product. + * (Originally 'subunitroot'). + * + * @param n positive integer. + * @return n-th root of this Product, or null if this Product + * is not an n-th power. + */ + Product root(int n) + { + Product p = new Product(); + int i = 0; + while (i1) - word = word.substring(0,word.length()-1); + // Extract concatenated exponent if present - + // - only if 'word' does not have subscript. + int exp = 1; + if (!Unit.hasSubscript(word)) + { + exp = 2 + "23456789".indexOf(word.charAt(word.length()-1)); + if (exp>1) + word = word.substring(0,word.length()-1); + } Value v = Value.fromName(word); diff --git a/src/net/sourceforge/unitsinjava/SemanticsBase.java b/app/src/main/java/net/sourceforge/unitsinjava/SemanticsBase.java similarity index 100% rename from src/net/sourceforge/unitsinjava/SemanticsBase.java rename to app/src/main/java/net/sourceforge/unitsinjava/SemanticsBase.java diff --git a/app/src/main/java/net/sourceforge/unitsinjava/Source.java b/app/src/main/java/net/sourceforge/unitsinjava/Source.java new file mode 100644 index 00000000..bf8dd925 --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/Source.java @@ -0,0 +1,73 @@ +//========================================================================= +// +// Part of PEG parser generator Mouse. +// +// Copyright (C) 2009, 2010 by Roman R. Redziejowski (www.romanredz.se). +// +// 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. +// +//------------------------------------------------------------------------- +// +// Change log +// 090701 License changed by the author to Apache v.2. +// 090810 Package name changed. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// Interface to source text wrapper. +// The generated parser accesses its input through a wrapper that +// presents the input as a sequence of characters. These characters +// can be individually accessed by specifying their position in the +// sequence. The positions are numbered starting with 0. +// For diagnostic purposes, the wrapper has the method 'where' that +// describes a given position in terms compatible with the input +// medium, for example, as line and column number for a file. +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + +public interface Source +{ + //------------------------------------------------------------------- + // Is the wrapper correctly initialized? + // The wrapper's constructor may encounter errors that result + // in the object not being properly initialized. + // The method returns 'false' if this is the case. + //------------------------------------------------------------------- + boolean created(); + + //------------------------------------------------------------------- + // Returns position of the last character plus 1 + // (= length of the sequence). + //------------------------------------------------------------------- + int end(); + + //------------------------------------------------------------------- + // Returns character at position p. + //------------------------------------------------------------------- + char at(int p); + + //------------------------------------------------------------------- + // Returns characters at positions p through q-1. + //------------------------------------------------------------------- + String at(int p, int q); + + //------------------------------------------------------------------- + // Describes position p in user's terms. + //------------------------------------------------------------------- + String where(int p); +} diff --git a/app/src/main/java/net/sourceforge/unitsinjava/SourceString.java b/app/src/main/java/net/sourceforge/unitsinjava/SourceString.java new file mode 100644 index 00000000..98e677b0 --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/SourceString.java @@ -0,0 +1,101 @@ +//========================================================================= +// +// Part of PEG parser generator Mouse. +// +// Copyright (C) 2009, 2010 by Roman R. Redziejowski (www.romanredz.se). +// +// 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. +// +//------------------------------------------------------------------------- +// +// Change log +// 090701 License changed by the author to Apache v.2. +// 090810 Renamed from 'SourceString' and package name changed. +// Version 1.2 +// 091105 Modified where() to insert three dots. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// Wrapper for parser input in the form of a string. +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + +public class SourceString implements Source +{ + //===================================================================== + // + // Data. + // + //===================================================================== + //------------------------------------------------------------------- + // The String. + // Note: it is the string given to the constructor, not a copy. + //------------------------------------------------------------------- + final String text; + + //===================================================================== + // + // Constructor. Wraps the string 's'. + // + //===================================================================== + public SourceString(final String s) + { text = s; } + + + //===================================================================== + // + // Interface methods. + // + //===================================================================== + //------------------------------------------------------------------- + // Is the wrapper correctly initialized? + //------------------------------------------------------------------- + public boolean created() + { return true; } + + //------------------------------------------------------------------- + // Returns end position. + //------------------------------------------------------------------- + public int end() + { return text.length(); } + + //------------------------------------------------------------------- + // Returns character at position p. + //------------------------------------------------------------------- + public char at(int p) + { return text.charAt(p); } + + //------------------------------------------------------------------- + // Returns characters at positions p through q-1. + //------------------------------------------------------------------- + public String at(int p, int q) + { return text.substring(p,q); } + + //------------------------------------------------------------------- + // Describes position p in terms of preceding text. + //------------------------------------------------------------------- + public String where(int p) + { + if (p>15) + return "After '... " + text.substring(p-15,p) + "'"; + else if (p>0) + return "After '" + text.substring(0,p) + "'"; + else + return "At start"; + } +} diff --git a/src/net/sourceforge/unitsinjava/Tables.java b/app/src/main/java/net/sourceforge/unitsinjava/Tables.java similarity index 50% rename from src/net/sourceforge/unitsinjava/Tables.java rename to app/src/main/java/net/sourceforge/unitsinjava/Tables.java index 4bf7e9e7..6e577664 100644 --- a/src/net/sourceforge/unitsinjava/Tables.java +++ b/app/src/main/java/net/sourceforge/unitsinjava/Tables.java @@ -1,181 +1,258 @@ -//========================================================================= -// -// Part of units package -- a Java version of GNU Units program. -// -// Units is a program for unit conversion originally written in C -// by Adrian Mariano (adrian@cam.cornell.edu.). -// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, -// 2005, 2006, 2007 by Free Software Foundation, Inc. -// -// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, -// 2009 by Roman R Redziejowski (roman.redz@tele2.se). -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -//------------------------------------------------------------------------- -// -// Change log -// -// 050203 Version 1.84.J05. -// Check number of files in 'build'. -// Initialize tables in 'build'. -// Added method 'clean'. -// Added title parameter to calls for Browser. -// 050315 Version 1.84.J07. Changed package name to "units". -// 061231 Version 1.86.J01. -// Removed printing of statistics from 'build'. -// Added method 'stat' to obtain statistics. -// 091024 Version 1.87.J01. -// Used generics for 'Unit.table', 'Prefix.table', -// BuiltInFunction.table', 'DefinedFunction.table', and -// 'list' in 'showConformable'. -// 091031 Replaced 'addtolist' by 'isCompatibleWith' -// and 'insertAlph' by 'Collections.sort'. -// 091101 Implemented 'search' ('showMatching'). -// -//========================================================================= - -package net.sourceforge.unitsinjava; - -import java.util.Collections; -import java.util.Enumeration; -import java.util.Hashtable; -import java.util.Vector; - - -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -// -// Class Tables -// -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -/** - * Contains static methods for maintenance of tables. - */ - - public class Tables -{ - //===================================================================== - // build - //===================================================================== - /** - * Build tables from given definition files. - * - * @return true if success, false otherwise. - */ - public static boolean build() - { - //--------------------------------------------------------------- - // Check number of files. - //--------------------------------------------------------------- - if (Env.filenames.size()>=Env.MAXFILES) - { - Env.out.println - ("At most " + Env.MAXFILES + " file names are allowed."); - return false; - } - - //--------------------------------------------------------------- - // Initialize all tables. - //--------------------------------------------------------------- - if (Unit.table!=null || Prefix.table!=null || - BuiltInFunction.table!=null || DefinedFunction.table!=null) - Env.err.println("Multiple invocations. Warning for interference."); - - Unit.table = new Hashtable(); - Prefix.table = new Hashtable(); - BuiltInFunction.table = new Hashtable(); - DefinedFunction.table = new Hashtable(); - - //--------------------------------------------------------------- - // Read unit definitions. - //--------------------------------------------------------------- - for (int i=0; i - * Cycles through all units and prefixes and attempts - * to reduce each one to 1. - * Prints a message for all units which do not reduce to 1. - */ - public static void check() - { - //--------------------------------------------------------------- - // Check all functions for valid definition and correct inverse. - //--------------------------------------------------------------- - for(Enumeration e=DefinedFunction.table.elements();e.hasMoreElements();) - ((Function)e.nextElement()).check(); - - //--------------------------------------------------------------- - // Now check all units for validity - //--------------------------------------------------------------- - for (Enumeration e=Unit.table.elements();e.hasMoreElements();) - ((Unit)e.nextElement()).check(); - - - //--------------------------------------------------------------- - // Check prefixes - //--------------------------------------------------------------- - for (Enumeration e=Prefix.table.elements();e.hasMoreElements();) - ((Prefix)e.nextElement()).check(); - } - - - } +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2009, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J05. +// 050203 Check number of files in 'build'. +// Initialize tables in 'build'. +// Added method 'clean'. +// Added title parameter to calls for Browser. +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.86.J01. +// 061231 Removed printing of statistics from 'build'. +// Added method 'stat' to obtain statistics. +// +// Version 1.87.J01. +// 091024 Used generics for 'Unit.table', 'Prefix.table', +// BuiltInFunction.table', 'DefinedFunction.table', and +// 'list' in 'showConformable'. +// 091031 Replaced 'addtolist' by 'isCompatibleWith' +// and 'insertAlph' by 'Collections.sort'. +// 091101 Implemented 'search' ('showMatching'). +// +// Version 1.89.J01. +// 120121 Renamed 'File' to 'UnitsFile'. +// Used generics for the three Enumerations in 'check'. +// 120123 Prepared for include files being added to 'Env.filenames'; +// see under 'Read unit definitions' in 'build'. +// 120201 Instantiate Alias table. +// 120208 Renamed one-argument method 'isCompatibleWith' +// to 'conformsTo' to avoid confusion with two-argument one +// defined in Product and Value. +// 120225 Added Alias table in 'showConformable', +// 'showMatching', 'check', 'stat', and 'clean'. +// 120228 Replaced use of 'Env.err' by 'Env.out'. +// 120303 Added method 'showdef' to show a complete +// definition of a unit name, function name, or alias. +// 120309 Added Alias table in 'showSource'. +// 120316 Added Prefix table in 'showMatching' and 'showConformable'. +// 120318 Removed check for multiple invocations. +// It was superfluous: each invocation has own static variables. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import java.util.Collections; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// Class Tables +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * Contains static methods for maintenance of tables and extracting + * information from tables. Is never instantiated. + */ + + public class Tables +{ + //===================================================================== + // build + //===================================================================== + /** + * Build tables from given definition files. + * + * @return true if success, false otherwise. + */ + public static boolean build() + { + //--------------------------------------------------------------- + // Check number of files. + //--------------------------------------------------------------- + if (Env.filenames.size()>=Env.MAXFILES) + { + Env.out.println + ("At most " + Env.MAXFILES + " unit definition files are allowed."); + return false; + } + + //--------------------------------------------------------------- + // Initialize all tables. + //--------------------------------------------------------------- + Unit.table = new Hashtable(); + Prefix.table = new Hashtable(); + BuiltInFunction.table = new Hashtable(); + DefinedFunction.table = new Hashtable(); + Alias.table = new Hashtable(); + + //--------------------------------------------------------------- + // Read unit definitions. + // Note that 'Env.filenames' may be extended with include files + // by 'file.readunits'; we do not want to process them. + //--------------------------------------------------------------- + int nfiles = Env.filenames.size(); + for (int i=0; i + * Cycles through all functions, units, prefixes, and aliases + * applying their 'check' methods to check for correctnes. + * Prints messages about any error found. + */ + public static void check() + { + //--------------------------------------------------------------- + // Check aliases. + //--------------------------------------------------------------- + for (Enumeration e=Alias.table.elements();e.hasMoreElements();) + (e.nextElement()).check(); + + //--------------------------------------------------------------- + // Check functions. + //--------------------------------------------------------------- + for(Enumeration e=DefinedFunction.table.elements();e.hasMoreElements();) + (e.nextElement()).check(); + + //--------------------------------------------------------------- + // Check prefixes. + //--------------------------------------------------------------- + for (Enumeration e=Prefix.table.elements();e.hasMoreElements();) + (e.nextElement()).check(); + + //--------------------------------------------------------------- + // Check units. + //--------------------------------------------------------------- + for (Enumeration e=Unit.table.elements();e.hasMoreElements();) + (e.nextElement()).check(); + } + + + //===================================================================== + // showdef + //===================================================================== + /** + * If the argument is the name of a function or unit list, + * returns its definition. Otherwise returns null. + * If 'showUnit' is true, returns definition also if the argument + * is the name of a unit or prefix. + * + * @param name the name. + * @param showUnit if true, show unit or prefix definition. + * @return definition or null. + */ + public static String showdef(final String name, boolean showUnit) + { + //--------------------------------------------------------------- + // Check first if 'name' is valid as an Entity name. + //--------------------------------------------------------------- + if (Entity.checkName(name)!=null) return null; + + //--------------------------------------------------------------- + // If 'name' is an alias, return its definition. + //--------------------------------------------------------------- + String def = Alias.showdef(name); + if (def!=null) return def; + + //--------------------------------------------------------------- + // If 'name' is the name of a function, return its definition. + //--------------------------------------------------------------- + def = DefinedFunction.showdef(name); + if (def!=null) return def; + + //--------------------------------------------------------------- + // Return if no check for unit or prefix. + //--------------------------------------------------------------- + if (!showUnit) return null; + + //--------------------------------------------------------------- + // If 'name' is the name of a unit, prefix, or combination + // of prefix and unit, return its definition. + //--------------------------------------------------------------- + def = Factor.showdef(name); + if (def!=null) return def; + + return null; + } + + } diff --git a/app/src/main/java/net/sourceforge/unitsinjava/TabularFunction.java b/app/src/main/java/net/sourceforge/unitsinjava/TabularFunction.java new file mode 100644 index 00000000..c6a50382 --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/TabularFunction.java @@ -0,0 +1,421 @@ +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.86.J01. +// 061229 Corrected test for 'verbose'. +// 070102 Suppress printing "\tDefinition :" and tabs for compact output. +// +// Version 1.87.J01. +// 091024 Used modified 'insertAlph'. +// Used generics for 'x'.'y'. +// 091025 Replaced 'Parser.Exception' by 'EvalError'. +// 091031 Moved definition of Ignore to Factor. +// Replaced 'addtolist' by 'isCompatibleWith'. +// +// Version 1.88.J02 +// 110403 In 'showdef': removed optional "\tDefinition :"; +// replaced StringBuffer by StringBuilder; +// removed unused variable 'nc'. +// +// Version 1.89.J01 +// 120201 Adapted to use with File Parser: +// removed method 'accept' and added 'define'. +// 120208 Renamed one-argument method 'isCompatibleWith' +// to 'conformsTo' to avoid confusion with two-argument one +// defined in Product and Value. +// 120209 Definition of Ignore moved to separate file: +// replaced 'Factor.Ignore' by 'Ignore'. +// 120311 Suppress error messages from 'conformsTo'. +// 120313 Added 'location.where' to messages from 'check'. +// 120317 Changed 'define' to replace an earlier definition +// instead of ignoring re-definition. +// 120318 In 'check': check name conflict using 'checkHiding'. +// 120404 In 'check': added checking of result unit. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import java.util.Hashtable; +import java.util.Vector; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class TabularFunction +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * A function defined by a table (a 'piecewise linear unit'.) + */ + + public class TabularFunction extends DefinedFunction +{ + //------------------------------------------------------------------- + // The table + //------------------------------------------------------------------- + private final double[] x; // Argument values + private final double[] y; // Corresponding values in units 'resUnit'. + + //------------------------------------------------------------------- + // Result unit + //------------------------------------------------------------------- + private final String resUnit; + + + //===================================================================== + // Constructor + //===================================================================== + /** + * Constructs TabularFunction object. + * + * @param name function name. + * @param loc location where defined. + * @param resUnit unit for y-values. + * @param x argument values. + * @param y corresponding values in units 'resUnit'. + */ + TabularFunction + ( final String name, final Location loc, + final String resUnit, final double[] x, final double[] y) + { + super(name,loc); + this.resUnit = resUnit; + this.x = x; + this.y = y; + } + + + //===================================================================== + // define + //===================================================================== + /** + * Builds FunctionTable entry from a parsed definition. + * The definition is parsed as follows: + *
+   *    name[resUnit]  x1 y1, x2 y2, ... , xn yn
+   *  
+ * + * @param name function name. + * @param resUnit unit for y-values. + * @param x argument values. + * @param y corresponding values in units 'resUnit'. + * @param loc location where defined. + */ + public static void define + ( final String name, final String resUnit, + final double[] x, final double[] y, final Location loc) + { + //--------------------------------------------------------------- + // Function with incorrect name can never be accessed. + //--------------------------------------------------------------- + String diag = Entity.checkName(name); + + if (diag!=null) + { + Env.out.println + (loc.where() + ". Function '" + name + + "' is ignored. Its name " + diag + "."); + return; + } + + //--------------------------------------------------------------- + // Install the function in table. + //--------------------------------------------------------------- + Function old = table.put(name, new TabularFunction(name,loc,resUnit,x,y)); + + //--------------------------------------------------------------- + // Write a message if an earlier definition replaced. + //--------------------------------------------------------------- + if (old!=null) + { + Env.out.println + ("Function '" + name + "' defined in " + old.location.where() + + ", is redefined in " + loc.where() + "."); + } + } + + + //===================================================================== + // applyTo + //===================================================================== + /** + * Applies this function to a given Value, + * and changes the Value to the result. + * + * @param v the argument and result. + */ + void applyTo(Value v) + { + //--------------------------------------------------------------- + // Parse resUnit to obtain a Value, 'dim'. + //--------------------------------------------------------------- + Value dim = null; + try + { dim = Value.parse(resUnit); } + + catch (EvalError e) + { + throw new EvalError("Invalid result unit, " + resUnit + + ", of function " + name + ". " + e.getMessage()); + } + + //--------------------------------------------------------------- + // The argument must be a number. + //--------------------------------------------------------------- + if (!v.isNumber()) + throw new EvalError("Argument " + v.asString() + " of '" + + name + "' is not a number."); + + //--------------------------------------------------------------- + // Find y-value corresponding to argument 'x'. + //--------------------------------------------------------------- + double result = interpolate(v.factor,x,y,v,""); + + //--------------------------------------------------------------- + // Return result in result units. + //--------------------------------------------------------------- + dim.factor *= result; + v.copyFrom(dim); + } + + + //===================================================================== + // applyInverseTo + //===================================================================== + /** + * Applies the inverse of this function to a given Value, + * and changes the Value to the result. + * + * @param v the argument and result. + */ + public void applyInverseTo(Value v) + { + //--------------------------------------------------------------- + // Parse resUnit to obtain a Value, 'dim'. + //--------------------------------------------------------------- + Value dim = null; + try + { dim = Value.parse(resUnit); } + + catch (EvalError e) + { + throw new EvalError("Invalid result unit, '" + resUnit + + "', of '" + name + "'. " + e.getMessage()); + } + + //--------------------------------------------------------------- + // Express argument as n*resUnit. + //--------------------------------------------------------------- + Value n = new Value(v); + n.div(dim); + + //--------------------------------------------------------------- + // 'n' must be a number. + //--------------------------------------------------------------- + if (!n.isNumber()) + throw new EvalError("Argument " + v.asString() + + " of '~" + name + "' is not conformable to '" + + resUnit + "'."); + + //--------------------------------------------------------------- + // Find x-value corresponding to y-value 'n'. + //--------------------------------------------------------------- + double result = interpolate(n.factor,y,x,v,"~"); + + //--------------------------------------------------------------- + // Return result as Value. + //--------------------------------------------------------------- + v.copyFrom(new Value()); + v.factor = result; + } + + //===================================================================== + // showdef + //===================================================================== + /** + * Returns definition of this function. + * (Originally 'showfuncdef'.) + * + * @return formatted definition of this function. + */ + String showdef() + { + String pref = ("0123456789.".indexOf(resUnit.charAt(0))>=0)? " * " : " "; + + StringBuilder sb = new StringBuilder("Interpolated table with points:"); + + for(int i=0;i0? "\n\t\t " : "\n ") + name + + "(" + x[i]+ ") = " + y[i] + pref + resUnit); + return sb.toString(); + } + + + //===================================================================== + // check + //===================================================================== + /** + * Checks definition of this function for correctness. + * Writes diagnostics to 'Env.out'. + * Used by 'check' in 'Tables'. + */ + void check() + { + if (Env.verbose==2) + Env.out.println(location.where() + ". Doing function " + name); + + //--------------------------------------------------------------- + // Function name must be different from that of alias, unit, + // and prefix. Conflict with alias is checked by Alias. + // We check here for conflict with unit and prefix. + //--------------------------------------------------------------- + checkHiding(); + + //--------------------------------------------------------------- + // Check result unit + //--------------------------------------------------------------- + try + { Value.parse(resUnit); } + + catch (EvalError e) + { + Env.out.println + (location.where() + ". Invalid result unit, '" + resUnit + + "', of '" + name + "'. " + e.getMessage()); + } + + //--------------------------------------------------------------- + // Check that at least two points are defined. + //--------------------------------------------------------------- + if (x.length<=1) + { + Env.out.println + (location.where() + ". Table '" + name + + "' has only one data point."); + return; + } + + //--------------------------------------------------------------- + // Check for monotonicity which is needed for unique inverses + //--------------------------------------------------------------- + int direction = signum(y[1]-y[0]); + for(int i=2;i"; } + + + //===================================================================== + // interpolate + //===================================================================== + /** + * Finds by interpolation in tables an output value corresponding + * to a given input value. Thhrow EvalError exception if input value + * is outside the inputs table. + * + * @param inval numeric input value. + * @param in table of input values. + * @param out table of output values. + * @param v input value with units, for diagnostics. + * @param inv empty string or '~', for disagnostics. + */ + private double interpolate(double inval, double[] in, double[] out, Value v, String inv) + { + for(int i=0;i=inval && inval>=in[i+1])) + return out[i] + (inval-in[i])*(out[i+1]-out[i])/(in[i+1]-in[i]); + + throw new EvalError("Argument " + v.asString() + + " is outside the domain of '" + inv + name + "'."); + } + + + //===================================================================== + // signum + //===================================================================== + /** + * Finds signum of a given number. + * + * @param d a number. + * @return -1, 0, or +1 if, respectively, d<0, d==0, or d>0. + */ + private static int signum(double d) + { return d==0? 0 : (d>0? 1 : -1); } +} diff --git a/app/src/main/java/net/sourceforge/unitsinjava/Unit.java b/app/src/main/java/net/sourceforge/unitsinjava/Unit.java new file mode 100644 index 00000000..c869f7fc --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/Unit.java @@ -0,0 +1,315 @@ +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J05. +// 050203 Do not initialize table. +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.86.J01. +// 061229 Corrected test for 'verbose'. +// +// Version 1.87.J01. +// 091024 Used modified 'insertAlph'. +// 091031 Moved definition of Ignore to Factor. +// Replaced 'addtolist' by 'isCompatibleWith'. +// +// Version 1.89.J01 +// 120201 Adapted to use with File Parser: +// removed method 'accept' and added 'define'. +// 120202 Added method 'hasSubscript' and used it to check the name. +// 120208 Renamed one-argument method 'isCompatibleWith' +// to 'conformsTo' to avoid confusion with two-argument one +// defined in Product and Value. +// 120209 Definition of Ignore moved to separate file: +// replaced 'Factor.Ignore' by 'Ignore'. +// 120311 Suppress error messages from 'conformsTo'. +// 120313 Added 'location.where' to messages from 'check'. +// 120317 Changed 'define' to replace an earlier definition +// instead of ignoring re-definition. +// 120318 In 'check': removed check for name conflict. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import java.util.Hashtable; +import java.util.Vector; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class Unit +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * A unit. + */ + + public class Unit extends Factor +{ + //------------------------------------------------------------------- + /** Table of Units */ + //------------------------------------------------------------------- + public static Hashtable table = null; + + + //===================================================================== + // Constructor + //===================================================================== + /** + * Constructs a Unit object. + * + * @param name unit name. + * @param loc location where defined. + * @param def definition. + */ + Unit(final String name, final Location loc, final String def) + { super(name,loc,def); } + + + //===================================================================== + // define + //===================================================================== + /** + * Builds UnitTable entry from a parsed definition. + * The definition is parsed as follows: + *
+   *    name definition
+   *  
+ * + * @param name unit name. + * @param def definition. + * @param loc location where defined. + */ + public static void define + (final String name, final String def, final Location loc) + { + //--------------------------------------------------------------- + // Units with incorrect syntax can never be accessed. + //--------------------------------------------------------------- + String diag = Entity.checkName(name); + + if (diag!=null) + { + Env.out.println + (loc.where() + ". Unit '" + name + + "' is ignored. Its name " + diag + "."); + return; + } + + //--------------------------------------------------------------- + // Units that end in [2-9] without '_' can never be accessed. + //--------------------------------------------------------------- + if (!hasSubscript(name) + && "23456789".indexOf(name.charAt(name.length()-1))>=0) + { + Env.out.println + (loc.where() + ". Unit '" + name + + "' is ignored. Its name ends with a digit 2-9 without '_'."); + return; + } + + //--------------------------------------------------------------- + // Install the unit in table. + //--------------------------------------------------------------- + Unit old = table.put(name, new Unit(name,loc,def)); + + //--------------------------------------------------------------- + // Write a message if an earlier definition was replaced. + //--------------------------------------------------------------- + if (old!=null) + { + Env.out.println + ("Unit '" + name + "' defined in " + old.location.where() + + ", is redefined in " + loc.where() + "."); + } + } + + + //===================================================================== + // check + //===================================================================== + /** + * Checks definition of this unit for correctness. + * Writes diagnostics to 'Env.out'. + * Used by 'check' in 'Tables'. + */ + public static Value one = new Value(); + + void check() + { + if (Env.verbose==2) + Env.out.println(location.where() + ". Doing '" + name + "'"); + + //--------------------------------------------------------------- + // check if can be reduced + //--------------------------------------------------------------- + Value v = null; + try + { + v = Value.parse(name); + v.completereduce(); + } + catch (EvalError e) + { + Env.out.println(location.where() + ". " + e.getMessage()); + return; + } + + if (!v.isCompatibleWith(one,Ignore.PRIMITIVE)) + Env.out.println + (location.where() + ". Unit '" + name + "' defined as '" + + def + "' is irreducible."); + } + + + //===================================================================== + // conformsTo + //===================================================================== + /** + * Checks if this unit conforms to Value 'v'. + * Used by 'showConformable' in 'Tables'. + * + * @param v the Value to be checked against. + * @return true if this unitconforms to v, false otherwise. + */ + boolean conformsTo(final Value v) + { + try + { + Value thisvalue = Value.parse(def); + thisvalue.completereduce(); + return thisvalue.isCompatibleWith(v,Ignore.DIMLESS); + } + catch(EvalError e) + { return false; } + } + + + //===================================================================== + // desc + //===================================================================== + /** + * Returns short description of this unit + * to be shown by 'showConformable' and 'showMatching' in 'Tables'. + * + * @return description. + */ + String desc() + { return (isPrimitive? "" : "= " + def); } + + + //===================================================================== + // find + //===================================================================== + /** + * Finds out if given string is the name of a known unit, + * possibly in plural, and returns the Unit object if so. + * (Originally part of 'lookupunit'.) + * + * @param name the string to be investigated. + * @return the Unit object identified by 'name', + * or null if none found. + */ + public static Unit find(final String name) + { + //--------------------------------------------------------------- + // If 'name' appears as unit name in table, + // return object from the table. + //--------------------------------------------------------------- + if (Unit.table.containsKey(name)) + return Unit.table.get(name); + + //--------------------------------------------------------------- + // Plural rules for English: + // add -s + // after x, sh, ch, ss add -es + // -y becomes -ies except after a vowel when you just add -s. + //--------------------------------------------------------------- + int ulg = name.length(); + if (ulg>2 && name.charAt(ulg-1)=='s') + { + //------------------------------------------------------------- + // Try removing 's'. + //------------------------------------------------------------- + String temp = name.substring(0,ulg-1); + if (Unit.table.containsKey(temp)) + return Unit.table.get(temp); + + //------------------------------------------------------------- + // Removing the suffix 's' did not help. It could still be + // a plural form ending on 'es'. Try this. + //------------------------------------------------------------- + if (ulg>3 && name.charAt(ulg-2)=='e') + { + temp = name.substring(0,ulg-2); + if (Unit.table.containsKey(temp)) + return Unit.table.get(temp); + + //----------------------------------------------------------- + // Removing the suffix 'es' did not help. It could still be + // a plural form ending on 'ies'. Try this. + //----------------------------------------------------------- + if (ulg>4 && name.charAt(ulg-3)=='i') + { + temp = name.substring(0,ulg-3) + "y"; + if (Unit.table.containsKey(temp)) + return Unit.table.get(temp); + } + } + } + + return null; + } + + + //===================================================================== + // hasSubscript + //===================================================================== + /** + * Chcks if given name ends with a 'subscript'. + * A subscript starts with '_' and is followed by a sequence + * of digits, point, and/or comma. + * + * @param name the name. + * @return true if the name ends with a subscript, false otherwise. + */ + public static boolean hasSubscript(final String name) + { + int i = name.lastIndexOf('_'); + if (i<0 || i == name.length()-1) return false; + for (int j = i+1;j. +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.89.J01. +// 120208 Created. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import static net.sourceforge.unitsinjava.UnitList.RoundMode.FLOATING_POINT; +import static net.sourceforge.unitsinjava.UnitList.RoundMode.INTEGER; +import static net.sourceforge.unitsinjava.UnitList.RoundMode.SEPARATED_FRACTION; + +import java.util.Vector; +import java.util.Locale; + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class UnitList +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * Represents a semicolon-separated list of unit expressions. + *
+ * Contains methods for evaluating and checking the expression + * and conversion of given Value to sum of units. + */ +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + + public class UnitList +{ + //------------------------------------------------------------------- + /** Number of unit expressions. */ + //------------------------------------------------------------------- + public int n; + + //------------------------------------------------------------------- + /** The expressions, trimmed. */ + //------------------------------------------------------------------- + public String[] unit; + + //------------------------------------------------------------------- + /** Evaluated and reduced expressions, checked for compatibility. */ + //------------------------------------------------------------------- + public Value[] value; + + public double[] result; + + //------------------------------------------------------------------- + /** Indicates handling of the last expression: + *
0 = round to integer; + *
1 = show as integer.fraction; + *
2 = split into integer and fraction. */ + //------------------------------------------------------------------- + public enum RoundMode { INTEGER, FLOATING_POINT, SEPARATED_FRACTION } + public RoundMode roundMode = FLOATING_POINT; + + public double rounded; + public double roundAmount; + + + //===================================================================== + // Constructor + //===================================================================== + /** + * Constructs a UnitList object from a given string. + * Evaluates and reduces the expressions. + * Throws EvalError if expressions cannot be evaluated + * or are not compatible. + * + * @param unitlist a semicolon-separated list of unit expressions. + */ + public UnitList(final String unitlist) + { + //--------------------------------------------------------------- + // Decide handling of the last element. + //--------------------------------------------------------------- + String list = unitlist.trim(); + int lg = list.length(); + + if (list.endsWith(";;")) + { + roundMode = INTEGER; + list = list.substring(0,lg-2); + lg -= 2; + } + + else if (list.endsWith(";")) + { + roundMode = SEPARATED_FRACTION; + list = list.substring(0,lg-1); + lg --; + } + + if (Env.round) roundMode = INTEGER; // Option '-r' overrides ';' + + + //--------------------------------------------------------------- + // Split string into expressions. + //--------------------------------------------------------------- + Vector items = new Vector<>(); + int i = 0; + while(i<=lg) + { + int j = list.indexOf(';',i); + if (j<0) j = lg; + items.add(list.substring(i,j).trim()); + i = j+1; + } + + n = items.size(); + + unit = new String[n]; + items.toArray(unit); + + //--------------------------------------------------------------- + // Evaluate the expressions. + //--------------------------------------------------------------- + value = new Value[n]; + + for (i=0;i0;i--) + { + double nextUnit = value[i-1].factor / value[i].factor; + nextUnit = Double.parseDouble(String.format(Locale.US,"%.8G",nextUnit)); + if (result[i]==nextUnit) + { + result[i] = 0; + result[i-1] += 1; + } + } + + //--------------------------------------------------------------- + // Print the result. + //--------------------------------------------------------------- + StringBuilder sb = new StringBuilder(); + + if (Env.verbose>0) + { + //------------------------------------------------------------- + // Standard or verbose + //------------------------------------------------------------- + sb.append("\t"); + + if (Env.verbose==2) sb.append(fromExpr + " = "); + + String sep = ""; + for (int i=0;i0) + sb.append(" (rounded up to nearest " + unit[n-1] + ")"); + if (roundAmount<0) + sb.append(" (rounded down to nearest " + unit[n-1] + ")"); + } + + else + { + //------------------------------------------------------------- + // Terse + //------------------------------------------------------------- + String sep = ""; + for (int i=0;i0) + sb.append(";-"); + if (roundAmount<0) + sb.append(";+"); + } + + Env.out.println(sb.toString()); + return true; + } + + + //===================================================================== + // showUnit + //===================================================================== + /** + * Constructs representation of result value and the expression + * received in unit list, to be shown as part of unit sum. + * (A free interpretation of the original 'showunitname'.) + * + * @param value a value resulting from the conversion. + * @param unit the corresponding expression from the unit list. + * @return the constructed representation. + */ + public static String showUnit(double value, final String unit) + { + //---------------------------------------------------------------- + // The processing depends on the form of 'unit'. + // Case: 'unit' is an expression containing arithmetic operators. + //---------------------------------------------------------------- + int p = Util.indexOf("+-",unit,0); + if (p!=unit.length()) + { + if (value==1) + return "(" + unit + ")"; + else + return Util.shownumber(value) + " (" + unit + ")"; + } + + //---------------------------------------------------------------- + // Case: 'unit' begins with '1|integer', where integer<10000, + // and 'value' is an integer<10000, replace 1 by 'value'. + //---------------------------------------------------------------- + if (value==(int)value + && value<10000 + && unit.length()>2 + && unit.startsWith("1|") + ) + { + int i=2; + while(i=0) + i++; + + if (i>1 && i<6) + return Util.shownumber(value) + unit.substring(1); + } + + + //---------------------------------------------------------------- + // Case: 'unit' begins with a number. + //---------------------------------------------------------------- + if ("0123456789.,".indexOf(unit.charAt(0))>=0) + { + if (value==1) + return unit; + else + return Util.shownumber(value) + " * " + unit; + } + + //---------------------------------------------------------------- + // Case: other. + //---------------------------------------------------------------- + return Util.shownumber(value) + " " + unit; + } + +} + diff --git a/app/src/main/java/net/sourceforge/unitsinjava/UnitsFile.java b/app/src/main/java/net/sourceforge/unitsinjava/UnitsFile.java new file mode 100644 index 00000000..dd8c123d --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/UnitsFile.java @@ -0,0 +1,290 @@ +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2009, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2011, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J05. +// 050203 MAXINCLUDE moved to Env object. +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.89.J01. +// 120121 Changed name from 'File' to 'UnitsFile' +// to eliminate conflicts with Java class 'File'. +// 120123 Modified to read the file as UTF8-encoded. +// Modified 'open' in 'StandAcc' and 'AppletAcc' +// to return InputStream instead of BufferedReader. +// Append name of include file to 'Env.filenames'. +// 120201 Restructured to use File Parser. +// 120326 'FileAcc' moved from Env to UntisFile. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.nio.charset.Charset; +import java.net.URL; + + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// Class UnitsFile +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * Represents a unit definition file. + */ + + public class UnitsFile +{ + //------------------------------------------------------------------- + // File access method. + //------------------------------------------------------------------- + public static FileAcc fileAcc; + + //------------------------------------------------------------------- + // File name. + //------------------------------------------------------------------- + String name; + + //------------------------------------------------------------------- + // String-mapped contents of the file. + // It is used to show source in browser window. + //------------------------------------------------------------------- + String contents; + + //------------------------------------------------------------------- + // Character encoding assumed for the file. + //------------------------------------------------------------------- + private static final Charset cs = Charset.forName("UTF-8"); + + + //===================================================================== + // Constructor + //===================================================================== + /** + * Constructs UnitsFile object for file with specified name. + * Does not find or open the file. + * + * @param name file name + */ + UnitsFile(final String name) + { this.name = name; } + + + //===================================================================== + // readunits + //===================================================================== + /** + * Reads definitions from the file. + * Creates objects for the Entities thus defined. + * Saves String-mapped copy of the file in 'contents'. + * Returns 'false' if the file was not found + * or reading error occurred. + * + * @param depth include depth. + * @return true if file successfully processed; false otherwise. + */ + boolean readunits(int depth) + { + //--------------------------------------------------------------- + // Current line number. Line numbers start with 1. + //--------------------------------------------------------------- + int linenum = 1; + + //--------------------------------------------------------------- + // Line number of a complete definition. + // If the definition extends into continuation lines, + // it is number of the first line. + //--------------------------------------------------------------- + int linestart = 0; + + //--------------------------------------------------------------- + // Positions in file. They are indices in 'contents'. + //--------------------------------------------------------------- + int pos = 0; // Position in file + int startpos = 0; // Starting position of current line + + //--------------------------------------------------------------- + // Buffer to accumulate the contents. + //--------------------------------------------------------------- + StringBuilder fileBuf = new StringBuilder(); + + //--------------------------------------------------------------- + // Open the file. + //--------------------------------------------------------------- + InputStream is = fileAcc.open(name); + if (is==null) return false; + InputStreamReader isr = new InputStreamReader(is,cs); + BufferedReader reader = new BufferedReader(isr); + + //--------------------------------------------------------------- + // Instantiate parser with memoization = 1. + //--------------------------------------------------------------- + FileParser parser = new FileParser(); + FileSemantics sem = parser.semantics(); + parser.setMemo(1); + + //--------------------------------------------------------------- + // Process definitions. + //--------------------------------------------------------------- + processLoop: + while(true) + { + //------------------------------------------------------------- + // Get one complete definition line into 'lineBuf'. + // It may include continuation lines. + //------------------------------------------------------------- + StringBuilder lineBuf = new StringBuilder(); + + linestart = linenum; // Note starting line number + startpos = pos; // ..and position in file. + + boolean continued = false; + + lineLoop: + while (true) + { + String line; + + //----------------------------------------------------------- + // Get line from file, exit 'readline' on error. + //----------------------------------------------------------- + try + { line = reader.readLine(); } + catch (IOException e) + { + Env.out.println + ("Error in reading line " + linenum + + " from file '" + name +"': " + e.getMessage()); + return false; + } + + //----------------------------------------------------------- + // End of file reached. Exit 'processLoop'. + //----------------------------------------------------------- + if (line==null) + { + if (continued) // If continuation was expected + Env.out.println + ("The last line of '" + name + + "' is missing its continuation and is ignored."); + break processLoop; + } + + //----------------------------------------------------------- + // We have a line. Append it to 'fileBuf' with '\n' added. + //----------------------------------------------------------- + fileBuf.append(line).append("\n"); + pos += (line.length()+1); // Current position is after '\n'. + linenum++; // Next line number. + + //----------------------------------------------------------- + // Append the line to 'lineBuf'. + //----------------------------------------------------------- + lineBuf.append(line); + + //----------------------------------------------------------- + // If the line ends with '\', replace '\' with a blank + // in 'lineBuf' and note that continuation is expected. + //----------------------------------------------------------- + if (line.endsWith("\\")) + { + lineBuf.setCharAt(lineBuf.length()-1,' '); + continued = true; + } + //----------------------------------------------------------- + // Otherwise exit lineLoop. + //----------------------------------------------------------- + else + break lineLoop; + + } // end lineLoop + + //------------------------------------------------------------- + // StringBuilder 'lineBuf' contains now a complete line. + // Get rid of the comment, if any. + //------------------------------------------------------------- + int i = lineBuf.indexOf("#"); + if (i>=0) lineBuf.delete(i,lineBuf.length()); + String line = lineBuf.toString(); + + //------------------------------------------------------------- + // Process the line. + //------------------------------------------------------------- + SourceString src = new SourceString(line); + sem.loc = new Location(this,linestart,startpos,pos-1); + sem.depth = depth; + parser.parse(src); + + } // end processLoop + + //--------------------------------------------------------------- + // Close the file. + //--------------------------------------------------------------- + try + { reader.close(); } + catch (IOException e) + { Env.out.println(e.getMessage()); } + + //--------------------------------------------------------------- + // Save file contents. + //--------------------------------------------------------------- + contents = fileBuf.toString(); + + return true; + } + + + //HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + // + // Class FileAcc + // + //HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + /** + * Access to files. + * Different subclasses of FileAcc specify different ways to open + * the unit definition files in different environments. + * An object of the proper subclass for current environment + * is instantiated and assigned to 'fileAcc'. + *
    + *
  • UnitsWindow and convert plugs an instance of StandAcc into 'fileAcc'. + *
  • applet plugs an instance of AppletAcc into 'fileAcc'. + *
+ */ + abstract public static class FileAcc + { public abstract InputStream open(final String name); } +} \ No newline at end of file diff --git a/app/src/main/java/net/sourceforge/unitsinjava/Util.java b/app/src/main/java/net/sourceforge/unitsinjava/Util.java new file mode 100644 index 00000000..f6c4b002 --- /dev/null +++ b/app/src/main/java/net/sourceforge/unitsinjava/Util.java @@ -0,0 +1,132 @@ +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2009, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2011, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J07 +// 050315 Changed package name to 'units'. +// +// Version 1.88.J03 +// 110623 shownumber: ensured correct decimal rounding. +// (Java cast from double to float truncated binary +// representation and produced incorrect rounding.) +// +// Version 1.88.J04 +// 110814 Corrected bug in shownumber: added 'Locale.US' argument +// in String.format. (Depending on locale, String.format +// produced decimal comma that was not recognized further on.) +// +// Version 1.89.J01 +// 120202 Removed no longer used method 'strtod' and its 'NumberMatcher'. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Locale; + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// Class Util +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * Holds common methods used by other classes. + */ + + public class Util +{ + //===================================================================== + // indexOf + //===================================================================== + /** + * Finds occurrence of one of given characters in a string. + *
+ * Finds the first occurrence in String s, + * at or after index start, + * of any of the characters contained in chars. + * + * @param chars characters to look for. + * @param s String to search. + * @param start starting index for the search. + * @return index of the found occurrence, + * or length of s if none found. + */ + public static int indexOf(final String chars, final String s, int start) + { + for (int i=start;i=0) + return i; + return s.length(); + } + + + //===================================================================== + // shownumber + //===================================================================== + /** + * Converts double number to a printable representation. + * + * @param d number to be converted. + * @return String representation of d. + */ + public static String shownumber(double d) + { + if (Double.isInfinite(d) || Double.isNaN(d)) + return Double.toString(d); + + if (d==(int)d) + return Integer.toString((int)d); + + StringBuilder s = new StringBuilder(String.format(Locale.US,"%.8G",d)); + int p = s.indexOf("."); // Position of decimal point + int e = s.indexOf("E"); // Positon of 'E' or -1 if none + if (e>0) // If 'E' present remove '+' and leading '0' + { + if (s.charAt(e+1)=='+') s.deleteCharAt(e+1); + if (s.charAt(e+1)=='0') s.deleteCharAt(e+1); + if (s.charAt(e+1)=='-' && (s.charAt(e+2)=='0')) s.deleteCharAt(e+2); + } + else + e = s.length(); + + // e is now position of the end of mantissa + 1. + // Remove trailing zeros from mantissa. + while(e>p+1 && s.charAt(e-1)=='0') + { + s.deleteCharAt(e-1); + e--; + } + + // If mantissa ends now with decimal point, remove the point. + if (e==p+1) s.deleteCharAt(p); + + return s.toString(); + } +} diff --git a/src/net/sourceforge/unitsinjava/Value.java b/app/src/main/java/net/sourceforge/unitsinjava/Value.java similarity index 77% rename from src/net/sourceforge/unitsinjava/Value.java rename to app/src/main/java/net/sourceforge/unitsinjava/Value.java index 059a247f..6afbea9f 100644 --- a/src/net/sourceforge/unitsinjava/Value.java +++ b/app/src/main/java/net/sourceforge/unitsinjava/Value.java @@ -1,720 +1,777 @@ -//========================================================================= -// -// Part of units package -- a Java version of GNU Units program. -// -// Units is a program for unit conversion originally written in C -// by Adrian Mariano (adrian@cam.cornell.edu.). -// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, -// 2005, 2006, 2007 by Free Software Foundation, Inc. -// -// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, -// 2009 by Roman R Redziejowski (roman.redz@tele2.se). -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -//------------------------------------------------------------------------- -// -// Change log -// -// 050315 Version 1.84.J07. Changed package name to "units". -// 061230 Version 1.86.J01. -// Modified 'convert' methods to apply new options. -// 091025 Version 1.87.J01. -// Replaced 'Parser.Exception' by 'EvalError'. -// Used the new Parser in 'parse'. -// 091027 Corrected handling of non-integer power of negative -// numbers in 'power' and 'root' (Math.pow produces NaN). -// 091031 Moved definition of Ignore to Factor. -// -//========================================================================= - -package net.sourceforge.unitsinjava; - - -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -// -// class Value -// -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH -/** - * A number multiplied by a dimension. - *
- * The number is called factor. - * The dimension is a combination of units, represented as a quotient - * of numerator and denominator: - * - *
   Value = factor * (numerator / denominator)
- * - * The numerator ane denominator are products of units. - * Each is represented by a Product object. - *

- * A Value is reduced if all units appearing in its - * numerator and denominator are primitive, and these products do not - * contain common factors. - * A Value represents a number if its numerator and denominator - * are both empty. - *

- * The class has methods for arithmetic operations on Values, - * reduction to primitive units, copying, and printing out Values. - * In addition to constructors, there are static methods to construct - * Values from unit names and unit expressions. - */ -//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH - - public class Value -{ - //------------------------------------------------------------------- - // Components of a Value - //------------------------------------------------------------------- - public double factor; - public Product numerator; - public Product denominator; - - //------------------------------------------------------------------- - /** Semantics of unit expressions. - * A static object containing semantic procedures for parser - * of unit expressions. It is used by the two 'parse' methods. */ - //------------------------------------------------------------------- - private static Semantics unitSem = new Semantics(); - - - //===================================================================== - // Default constructor - //===================================================================== - /** Constructs a Value representing dimensionless number 1. - *
(Originally 'initializeunit'.) - */ - public Value() - { - factor = 1.0; - numerator = new Product(); - denominator = new Product(); - } - - - //===================================================================== - /** Constructs copy of a given Value. - * @param v a Value to be copied. */ - //===================================================================== - public Value(final Value v) - { - factor = v.factor; - numerator = new Product(v.numerator); - denominator = new Product(v.denominator); - } - - - //===================================================================== - /** Makes this Value a copy of given Value. - * @param v the Value to be copied. - * @return this Value - now a copy of 'v'. */ - //===================================================================== - Value copyFrom(final Value v) - { - factor = v.factor; - numerator = new Product(v.numerator); - denominator = new Product(v.denominator); - return this; - } - - - //===================================================================== - /** Constructs a Value from unit expression; - * throws exception on error. - *
- * EvalError is thrown if the Value cannot be constructed - * because of incorrect syntax, unknown unit name, etc.. - * The exception contains a complete error message. - *
(Originally 'parseunit'.) - * @param s a unit expression. - * @return Value represented by the expression. */ - //===================================================================== - public static Value parse(final String s) - { - Parser parser = new Parser(); // Instantiate Parser + Semantics - Semantics sem = parser.semantics(); // Access Semantics - SourceString src = new SourceString(s); // Wrap 's' for parser - parser.parse(src); // Parse 's' - EvalError on failure - return sem.result; // Obtain result from Semantics - } - - - //===================================================================== - /** Constructs a Value from unit expression, - * substituting given Value for a parameter; - * throws exception on error. - *
- * The unit expression 's' may contain the string given as 'parm' - * at a place that syntactically corresponds to a unit name. - * This string is then treated as name of Value given as 'parmValue', - * rather than a unit name. - *
- * EvalError is thrown if the Value cannot be constructed - * because of incorrect syntax, unknown unit name, etc.. - * The exception contains a complete error message. - * @param s a unit expression. - * @param parm parameter name. - * @param parmValue Value to be substituted for 'parm'. - * @return Value represented by the expression. */ - //===================================================================== - public static Value parse(final String s, final String parm, final Value parmValue) - { - Parser parser = new Parser(); // Instantiate Parser + Semantics - Semantics sem = parser.semantics(); // Access Semantics - sem.parm = parm; // Identify parameter to replace - sem.parmValue = parmValue; // Supply parameter value - SourceString src = new SourceString(s); // Wrap 's' for parser - parser.parse(src); // Parse 's' - EvalError on failure - return sem.result; // Obtain result from Semantics - } - - - //===================================================================== - /** Constructs a completely reduced Value from unit expression; - * writes mesage on error. - *
- * If the Value cannot be constructed because of incorrect syntax, - * unknown unit name, etc., writes an error message and returns null. - *
(Originally 'processunit'.) - * @param s a unit expression. - * @return Value represented by the expression, - * or null if the Value could not be constructed. */ - //===================================================================== - public static Value fromString(final String s) - { - try - { - Value v = parse(s); - v.completereduce(); - return v; - } - catch (EvalError e) - { - Env.err.println(e.getMessage()); - return null; - } - } - - - //===================================================================== - /** Constructs a Value from a string that may be name of a unit - * or a prefix, or a prefixed unit name, possibly in plural from. - * Throws exception if the string is none of them. - * @param s possible name of a unit, prefix, or prefixed unit. - * @return Value represented by 's'. */ - //===================================================================== - public static Value fromName(final String s) - { - Factor[] pu = Factor.split(s); - if (pu==null) - throw new EvalError("Unit '" + s + "' is unknown."); - - Value v = new Value(); - - if (pu[0]!=null) - v.numerator.add(pu[0]); - - if (pu[1]!=null) - v.numerator.add(pu[1]); - - return v; - } - - - //===================================================================== - /** Constructs printable string representing this Value. - * @return this Value as printable string. */ - //===================================================================== - public String asString() - { - StringBuffer sb = new StringBuffer(); - - sb.append(Util.shownumber(factor)).append(numerator.asString()); - - if (denominator.size()>0) - sb.append(" /").append(denominator.asString()); - - return sb.toString(); - } - - - //===================================================================== - /** Prints out this Value. - *
(Originally 'showunit'.) */ - //===================================================================== - void show() - { Env.out.println(asString()); } - - - //===================================================================== - /** Checks if this Value is compatible with another Value. - *
- * Two Values are compatible if they have compatible - * numerators and denominators. - *
(Originally 'compareunits'.) - * @param v Value to be checked against. - * @return true if the Values are compatible, or - * false otherwise. */ - //===================================================================== - public boolean isCompatibleWith(final Value v, Factor.Ignore ignore) - { - return numerator.isCompatibleWith(v.numerator,ignore) - && denominator.isCompatibleWith(v.denominator,ignore); - } - - - //===================================================================== - /** Reduces this Value and checks if it represents a number. - *
- * A Value represents a number if it is dimensionless, that is, - * its numerator and denominator are both empty. - * @return true if the Value represents a number, or - * false otherwise. */ - //===================================================================== - boolean isNumber() - { - completereduce(); - return numerator.size()==0 && denominator.size()==0; - } - - - //===================================================================== - /** Adds given Value to this Value. - *
(Originally 'addunit'.) - * @param v Value to be added. */ - //===================================================================== - void add(final Value v) - { - completereduce(); - v.completereduce(); - if (!isCompatibleWith(v,Factor.Ignore.NONE)) - throw new EvalError("Sum of non-conformable values:\n\t" - + asString() + "\n\t" + v.asString() + "."); - factor += v.factor; - } - - - //===================================================================== - /** Multiplies this Value by a given Value. - *
(Originally 'multunit'.) - * @param v Value to multiply by. */ - //===================================================================== - void mult(final Value v) - { - factor *= v.factor; - numerator.add(v.numerator); - denominator.add(v.denominator); - } - - - //===================================================================== - /** Divide this Value by a given Value. - *
(Originally 'divunit'.) - * @param v Value to divide by. */ - //===================================================================== - void div(final Value v) - { - if (v.factor==0) - throw new EvalError("Division of " + asString() - + " by zero (" + v.asString() + ")."); - factor /= v.factor; - denominator.add(v.numerator); - numerator.add(v.denominator); - } - - - //===================================================================== - /** Inverts this Value. - *
(Originally 'invertunit'.) */ - //===================================================================== - void invert() - { - if (factor==0) - throw new EvalError("Division by zero (" + asString() + ")."); - factor = 1.0/factor; - Product num = numerator; - numerator = denominator; - denominator = num; - } - - - //===================================================================== - /** Raises this Value to power specified by another Value. - * The Value supplied as exponent must represent a number. - * If that number is not an integer or a fraction 1/integer, - * this Value must represent a number. - *
(Originally 'unitpower'.) - * @param v the exponent. */ - //===================================================================== - void power(final Value v) - { - //--------------------------------------------------------------- - // Exponent must a number. - //--------------------------------------------------------------- - if (!v.isNumber()) - throw new EvalError("Non-numeric exponent, " + v.asString() - + ", of " + asString() + "."); - - //--------------------------------------------------------------- - // Get numeric value of the exponent. - //--------------------------------------------------------------- - double p = v.factor; - - //--------------------------------------------------------------- - // Apply absolute value of the exponent. - //--------------------------------------------------------------- - if (Math.floor(p)==p) // integer exponent - power((int)Math.abs(p)); - - else - if (Math.floor(1.0/p)==1.0/p) // fractional exponent - root((int)Math.abs(1.0/p)); - - else // exponent neither n nor 1/n - { // .. for integer n.. - if (!isNumber()) - throw new EvalError("Non-numeric base, " + asString() - + ", for exponent " + v.asString() + "."); - - Double f = Math.pow(factor,Math.abs(p)); - - if (Double.isNaN(f)) - throw new EvalError("The result of " + factor + "^" + p - + " is undefined."); - factor = f; - } - - //--------------------------------------------------------------- - // Invert if exponent was negative. - //--------------------------------------------------------------- - if (p<0) invert(); - } - - - //===================================================================== - /** Raises this Value to integer power n>=0. - *
(Originally 'expunit'). - * @param n the exponent. */ - //===================================================================== - void power(int n) - { - if (n<0) - throw new Error("Program error: exponent " + n + "."); - - Product num = new Product(); - Product den = new Product(); - double fac = 1.0; - - for (int i=0;i(Originally 'rootunit'.) - * @param n the exponent. */ - //===================================================================== - void root(int n) - { - if (n==0 || (n%2==0 && factor<0)) - throw new EvalError("Illegal n-th root of " + asString() - + ", n=" + n + "."); - - completereduce(); - Product num = numerator.root(n); - Product den = denominator.root(n); - if (num==null || den==null) - { - String nth = n==2? "square" : (n==3? "cube" : n + "-th"); - throw new EvalError(asString() + " is not a " + nth + " root."); - } - - numerator = num; - denominator = den; - - // Math.pow does not work for negative base and non-integer exponent, - // so negative 'factor' must be treated separately. - - if (factor>=0) - factor = Math.pow(factor,1.0/(double)n); - else - factor = -Math.pow(-factor,1.0/(double)n); - } - - - //===================================================================== - /** Removes factors that appear in both the numerator and denominator. - *
(Originally 'cancelunit'.) */ - //===================================================================== - void cancel() - { - int den = 0; - int num = 0; - - while (numflip = false) or denominator - * (flip = true). - * @return true if reduction was performed, or - * false if there is nothing more to reduce. */ - //===================================================================== - boolean reduceproduct(boolean flip) - { - boolean didsomething = false; - Product prod = (flip? denominator : numerator); - if (flip) - denominator = new Product(); - else - numerator = new Product(); - Product newprod = (flip? denominator : numerator); - - //--------------------------------------------------------------- - // Process all factors of 'prod' - //--------------------------------------------------------------- - for (int f=0;f=0) - sep=" *"; - if (doingrec) - { - if (fromExpr.indexOf('/')>=0) - { - left="1 / ("; - right=")"; - } - else - left="1 / "; - } - } - - //--------------------------------------------------------------- - // Print the first line of output. - //--------------------------------------------------------------- - if (Env.verbose==2) - Env.out.print("\t" + left + fromExpr + right + " = "); - else if (Env.verbose==1) - Env.out.print("\t* "); - - Env.out.print(Util.shownumber(fromValue.factor / toValue.factor)); - - if (Env.verbose==2) - Env.out.print(sep + " " + toExpr); - - - //--------------------------------------------------------------- - // Print the second line of output. - //--------------------------------------------------------------- - if (!Env.oneline) - { - if (Env.verbose==2) - Env.out.print("\n\t" + left + fromExpr + right + " = (1 / "); - else if (Env.verbose==1) - Env.out.print("\n\t/ "); - else - Env.out.print("\n"); - - Env.out.print(Util.shownumber(toValue.factor / fromValue.factor)); - - if (Env.verbose==2) - Env.out.print(")" + sep + " " + toExpr); - } - Env.out.println(""); - return true; - } - - //===================================================================== - // convert to Function - //===================================================================== - /** - * Shows result of conversion of unit expression to function. - * - * @param fromExpr 'from' expression. - * @param fromValue 'from' expression converted to completely reduced Value. - * @param fun 'to' function. - */ - public static boolean convert - ( String fromExpr, Value fromValue, Function fun) - { - try - { - fun.applyInverseTo(fromValue); - fromValue.completereduce(); - } - catch(EvalError e) - { - Env.out.println(e.getMessage()); - return false; - } - - if (Env.verbose==2) - Env.out.print("\t" + fromExpr + " = " + fun.name + "("); - else - if (Env.verbose==1) Env.out.print("\t"); - Env.out.print(fromValue.asString()); - if (Env.verbose==2) - Env.out.print(")"); - Env.out.print("\n"); - return true; - } - - -} +//========================================================================= +// +// Part of units package -- a Java version of GNU Units program. +// +// Units is a program for unit conversion originally written in C +// by Adrian Mariano (adrian@cam.cornell.edu.). +// Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003, 2004, +// 2005, 2006, 2007, 2009, 2011 by Free Software Foundation, Inc. +// +// Java version Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, +// 2009, 2011, 2012 by Roman R Redziejowski (www.romanredz.se). +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//------------------------------------------------------------------------- +// +// Change log +// +// Version 1.84.J07. +// 050315 Changed package name to 'units'. +// +// Version 1.86.J01. +// 061230 Modified 'convert' methods to apply new options. +// +// Version 1.87.J01. +// 091025 Replaced 'Parser.Exception' by 'EvalError'. +// Used the new Parser in 'parse'. +// 091027 Corrected handling of non-integer power of negative +// numbers in 'power' and 'root' (Math.pow produces NaN). +// 091031 Moved definition of Ignore to Factor. +// +// Version 1.88.J02. +// 110228 Corrected messages in 'root': 'xx is not a square' +// instead of 'xx is not a square root' etc. +// 110317 In 'power': corrected 'new Error' to 'new EvalError'. +// 110326 Removed unused variable 'unitSem'. +// +// Version 1.89.J01. +// 120209 Name of method 'isCompatibleWith' in Product was changed to +// 'hasSameFactorsAs'; updated it in 'isCompatibleWith' here. +// 120228 Replaced use of 'Env.err' by 'Env.out'. +// 120302 Do not show factor 1 in 'as String'. +// 120312 Moved method 'convert to Function' to DefinedFunction. +// +//========================================================================= + +package net.sourceforge.unitsinjava; + + +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +// +// class Value +// +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +/** + * Represents a number multiplied by a dimension. + * + *

+ * The number is called factor. + * The dimension is a combination of units, represented as a quotient + * of numerator and denominator: + * + *

+ *

   Value = factor * (numerator / denominator)
+ * + * The numerator and denominator are products of units. + * Each is represented by a Product object. + *

+ * A Value is reduced if all units appearing in its + * numerator and denominator are primitive, and these products do not + * contain common factors. + * A Value represents a number if its numerator and denominator + * are both empty. + *

+ * The class has methods for arithmetic operations on Values, + * reduction to primitive units, copying, and printing out Values. + * In addition to constructors, there are static methods to construct + * Values from unit names and unit expressions. + */ +//HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + + public class Value +{ + //------------------------------------------------------------------- + // Components of a Value + //------------------------------------------------------------------- + public double factor; + public Product numerator; + public Product denominator; + + //===================================================================== + // Constructor + //===================================================================== + /** + * Constructs a Value representing number 1. + *
(Originally 'initializeunit'.) + */ + public Value() + { + factor = 1.0; + numerator = new Product(); + denominator = new Product(); + } + + + //===================================================================== + // Copy constructor + //===================================================================== + /** + * Constructs copy of a given Value. + * + * @param v the Value to be copied. + */ + public Value(final Value v) + { + factor = v.factor; + numerator = new Product(v.numerator); + denominator = new Product(v.denominator); + } + + + //===================================================================== + // copyFrom + //===================================================================== + /** + * Makes this Value a copy of given Value. + * + * @param v the Value to be copied. + * @return this Value - now a copy of 'v'. + */ + Value copyFrom(final Value v) + { + factor = v.factor; + numerator = new Product(v.numerator); + denominator = new Product(v.denominator); + return this; + } + + + //===================================================================== + // parse + //===================================================================== + /** + * Constructs a Value from unit expression; + * throws exception on error. + *
+ * Throws EvalError if the Value cannot be constructed + * because of incorrect syntax, unknown unit name, + * computation errors, etc.. + * The exception contains a complete error message. + *
(Originally 'parseunit'.) + * + * @param s a unit expression. + * @return Value represented by the expression. + */ + public static Value parse(final String s) + { + Parser parser = new Parser(); // Instantiate Parser + Semantics + Semantics sem = parser.semantics(); // Access Semantics + SourceString src = new SourceString(s); // Wrap 's' for parser + parser.parse(src); // Parse 's' - EvalError on failure + return sem.result; // Obtain result from Semantics + } + + + //===================================================================== + // parse with parameter substitution + //===================================================================== + /** + * Constructs a Value from unit expression, + * substituting given Value for a parameter; + * throws exception on error. + *
+ * The unit expression 's' may contain the string given as 'parm' + * at a place that syntactically corresponds to a unit name. + * This string is then treated as name of Value given as 'parmValue', + * rather than a unit name. + *
+ * Throws EvalError if the Value cannot be constructed + * because of incorrect syntax, unknown unit name, + * computation errors, etc.. + * The exception contains a complete error message. + * + * @param s a unit expression. + * @param parm parameter name. + * @param parmValue Value to be substituted for 'parm'. + * @return Value represented by the expression. + */ + public static Value parse(final String s, final String parm, final Value parmValue) + { + Parser parser = new Parser(); // Instantiate Parser + Semantics + Semantics sem = parser.semantics(); // Access Semantics + sem.parm = parm; // Identify parameter to replace + sem.parmValue = parmValue; // Supply parameter value + SourceString src = new SourceString(s); // Wrap 's' for parser + parser.parse(src); // Parse 's' - EvalError on failure + return sem.result; // Obtain result from Semantics + } + + + //===================================================================== + // fromString + //===================================================================== + /** + * Constructs a completely reduced Value from unit expression; + * writes mesage on error. + *
+ * If the Value cannot be constructed because of incorrect syntax, + * unknown unit name, etc., writes an error message and returns null. + *
(Originally 'processunit'.) + * + * @param s a unit expression. + * @return Value represented by the expression, + * or null if the Value could not be constructed. + */ + public static Value fromString(final String s) + { + try + { + Value v = parse(s); + v.completereduce(); + return v; + } + catch (EvalError e) + { + Env.out.println(e.getMessage()); + return null; + } + } + + + //===================================================================== + // fromName + //===================================================================== + /** + * Constructs a Value from a string that may be name of a unit + * or a prefix, or a prefixed unit name, possibly in plural from. + * Throws EvalError if the string is none of them. + * The exception contains a complete error message. + * + * @param s possible name of a unit, prefix, or prefixed unit. + * @return Value represented by 's'. + */ + public static Value fromName(final String s) + { + Factor[] pu = Factor.split(s); + if (pu==null) + throw new EvalError("Unit '" + s + "' is unknown."); + + Value v = new Value(); + + if (pu[0]!=null) + v.numerator.add(pu[0]); + + if (pu[1]!=null) + v.numerator.add(pu[1]); + + return v; + } + + + //===================================================================== + // asString + //===================================================================== + /** + * Constructs printable string representing this Value. + * + * @return this Value as printable string. + */ + public String asString() + { + StringBuffer sb = new StringBuffer(); + + if (factor!=1) + { + sb.append(Util.shownumber(factor)); + sb.append(numerator.asString()); + } + + else // skip factor and initial blank + sb.append(numerator.asString().substring(1)); + + if (denominator.size()>0) + sb.append(" /").append(denominator.asString()); + + return sb.toString(); + } + + + //===================================================================== + // show + //===================================================================== + /** + * Prints out this Value. + *
(Originally 'showunit'.) + */ + void show() + { Env.out.println(asString()); } + + + //===================================================================== + // isCompatibleWith + //===================================================================== + /** Checks if this Value is compatible with another Value. + *
+ * The two Values must be reduced. They are compatible if they have + * compatible numerators and denominators. + *
(Originally 'compareunits'.) + * + * @param v Value to be checked against. + * @return true if the Values are compatible, or + * false otherwise. + */ + public boolean isCompatibleWith(final Value v, Ignore ignore) + { + return numerator.hasSameFactorsAs(v.numerator,ignore) + && denominator.hasSameFactorsAs(v.denominator,ignore); + } + + + //===================================================================== + // isNumber + //===================================================================== + /** Reduces this Value and checks if it represents a number. + *
+ * A Value represents a number if it is dimensionless, that is, + * its numerator and denominator are both empty. + * + * @return true if the Value represents a number, or + * false otherwise. + */ + boolean isNumber() + { + completereduce(); + return numerator.size()==0 && denominator.size()==0; + } + + + //===================================================================== + // add + //===================================================================== + /** + * Adds given Value to this Value. + *
(Originally 'addunit'.) + * + * @param v Value to be added. + */ + void add(final Value v) + { + completereduce(); + v.completereduce(); + if (!isCompatibleWith(v,Ignore.NONE)) + throw new EvalError("Sum of non-conformable values:\n\t" + + asString() + "\n\t" + v.asString() + "."); + factor += v.factor; + } + + + //===================================================================== + // mult + //===================================================================== + /** + * Multiplies this Value by a given Value. + *
(Originally 'multunit'.) + * + * @param v Value to multiply by. + */ + void mult(final Value v) + { + factor *= v.factor; + numerator.add(v.numerator); + denominator.add(v.denominator); + } + + + //===================================================================== + // div + //===================================================================== + /** + * Divide this Value by a given Value. + *
(Originally 'divunit'.) + * + * @param v Value to divide by. + */ + void div(final Value v) + { + if (v.factor==0) + throw new EvalError("Division of " + asString() + + " by zero (" + v.asString() + ")."); + factor /= v.factor; + denominator.add(v.numerator); + numerator.add(v.denominator); + } + + + //===================================================================== + // invert + //===================================================================== + /** + * Inverts this Value. + *
(Originally 'invertunit'.) + */ + void invert() + { + if (factor==0) + throw new EvalError("Division by zero (" + asString() + ")."); + factor = 1.0/factor; + Product num = numerator; + numerator = denominator; + denominator = num; + } + + + //===================================================================== + // power (Value exponent) + //===================================================================== + /** + * Raises this Value to power specified by another Value. + * The exponent must represent a number. + * If that number is not an integer or a fraction 1/integer, + * this Value must represent a number. + *
(Originally 'unitpower'.) + * + * @param v the exponent. + */ + void power(final Value v) + { + //--------------------------------------------------------------- + // Exponent must a number. + //--------------------------------------------------------------- + if (!v.isNumber()) + throw new EvalError("Non-numeric exponent, " + v.asString() + + ", of " + asString() + "."); + + //--------------------------------------------------------------- + // Get numeric value of the exponent. + //--------------------------------------------------------------- + double p = v.factor; + + //--------------------------------------------------------------- + // Apply absolute value of the exponent. + //--------------------------------------------------------------- + if (Math.floor(p)==p) // integer exponent + power((int)Math.abs(p)); + + else + if (Math.floor(1.0/p)==1.0/p) // fractional exponent + root((int)Math.abs(1.0/p)); + + else // exponent neither n nor 1/n + { // .. for integer n.. + if (!isNumber()) + throw new EvalError("Non-numeric base, " + asString() + + ", for exponent " + v.asString() + "."); + + Double f = Math.pow(factor,Math.abs(p)); + + if (Double.isNaN(f)) + throw new EvalError("The result of " + factor + "^" + p + + " is undefined."); + factor = f; + } + + //--------------------------------------------------------------- + // Invert if exponent was negative. + //--------------------------------------------------------------- + if (p<0) invert(); + } + + + //===================================================================== + // power (int exponent) + //===================================================================== + /** + * Raises this Value to integer power n>=0. + *
(Originally 'expunit'). + * + * @param n the exponent. + */ + void power(int n) + { + if (n<0) + throw new EvalError("Program error: exponent " + n + "."); + + Product num = new Product(); + Product den = new Product(); + double fac = 1.0; + + for (int i=0;i(Originally 'rootunit'.) + * + * @param n the exponent. + */ + void root(int n) + { + if (n==0 || (n%2==0 && factor<0)) + throw new EvalError("Illegal n-th root of " + asString() + + ", n=" + n + "."); + + completereduce(); + Product num = numerator.root(n); + Product den = denominator.root(n); + if (num==null || den==null) + { + String nth = n==2? "a square." : (n==3? "a cube." : "an " + n + "-th power."); + throw new EvalError(asString() + " is not " + nth); + } + + numerator = num; + denominator = den; + + // Math.pow does not work for negative base and non-integer exponent, + // so negative 'factor' must be treated separately. + + if (factor>=0) + factor = Math.pow(factor,1.0/(double)n); + else + factor = -Math.pow(-factor,1.0/(double)n); + } + + + //===================================================================== + // cancel + //===================================================================== + /** + * Removes factors that appear in both the numerator and denominator. + *
(Originally 'cancelunit'.) + */ + void cancel() + { + int den = 0; + int num = 0; + + while (numflip = false) or denominator + * (flip = true). + * @return true if reduction was performed, or + * false if there is nothing more to reduce. + */ + boolean reduceproduct(boolean flip) + { + boolean didsomething = false; + Product prod = (flip? denominator : numerator); + if (flip) + denominator = new Product(); + else + numerator = new Product(); + Product newprod = (flip? denominator : numerator); + + //--------------------------------------------------------------- + // Process all factors of 'prod' + //--------------------------------------------------------------- + for (int f=0;ftrue if conversion was successful, + * false otherwise. + */ + public static boolean convert + ( String fromExpr, Value fromValue, + String toExpr, Value toValue) + { + Value invfrom = new Value(); // inverse of fromValue, if needed + boolean doingrec; // reciprocal conversion? + + doingrec = false; + fromExpr = fromExpr.trim(); + toExpr = toExpr.trim(); + + //--------------------------------------------------------------- + // If 'toValue' and 'fromValue' are not compatible, + // we may be doing reciprocal conversion. + //--------------------------------------------------------------- + if (!fromValue.isCompatibleWith(toValue,Ignore.DIMLESS)) + { + //------------------------------------------------------------- + // Construct inverse of 'fromValue' in 'invfrom'. + //------------------------------------------------------------- + invfrom.factor = 1/fromValue.factor; + invfrom.numerator = fromValue.denominator; + invfrom.denominator = fromValue.numerator; + + //------------------------------------------------------------- + // If reciprocal conversion not wanted, or inverse of 'fromValue' + // is not compatible with 'toValue', we have conformability error. + //------------------------------------------------------------- + if (Env.strict || !toValue.isCompatibleWith(invfrom,Ignore.DIMLESS)) + { + Env.out.println("Conformability error"); + Env.out.print(Env.verbose==2? "\t" + fromExpr + " = " : Env.verbose==1? "\t" : ""); + fromValue.show(); + Env.out.print(Env.verbose==2? "\t" + toExpr + " = " : Env.verbose==1? "\t" : ""); + toValue.show(); + return false; + } + + //------------------------------------------------------------- + // We arrive here to do a reciprocal conversion. + //------------------------------------------------------------- + Env.out.println("\treciprocal conversion"); + fromValue = invfrom; + doingrec = true; + } + + //--------------------------------------------------------------- + // We arrive here to do conversion, and 'fromValue' is compatible + // with 'toValue'. (If conversion is reciprocal, 'fromValue' is already + // inverted, and 'doingrec' is true.) + // Print the first line of output. + //--------------------------------------------------------------- + String sep = ""; + String right = ""; + String left = ""; + + if (Env.verbose==2) + { + if ("0123456789".indexOf(toExpr.charAt(0))>=0) + sep=" *"; + if (doingrec) + { + if (fromExpr.indexOf('/')>=0) + { + left="1 / ("; + right=")"; + } + else + left="1 / "; + } + } + + //--------------------------------------------------------------- + // Print the first line of output. + //--------------------------------------------------------------- + if (Env.verbose==2) + Env.out.print("\t" + left + fromExpr + right + " = "); + else if (Env.verbose==1) + Env.out.print("\t* "); + + Env.out.print(Util.shownumber(fromValue.factor / toValue.factor)); + + if (Env.verbose==2) + Env.out.print(sep + " " + toExpr); + + + //--------------------------------------------------------------- + // Print the second line of output. + //--------------------------------------------------------------- + if (!Env.oneline) + { + if (Env.verbose==2) + Env.out.print("\n\t" + left + fromExpr + right + " = (1 / "); + else if (Env.verbose==1) + Env.out.print("\n\t/ "); + else + Env.out.print("\n"); + + Env.out.print(Util.shownumber(toValue.factor / fromValue.factor)); + + if (Env.verbose==2) + Env.out.print(")" + sep + " " + toExpr); + } + Env.out.println(""); + return true; + } +} diff --git a/app/src/main/res/anim/history_hide.xml b/app/src/main/res/anim/history_hide.xml new file mode 100644 index 00000000..93246565 --- /dev/null +++ b/app/src/main/res/anim/history_hide.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/anim/history_show.xml b/app/src/main/res/anim/history_show.xml new file mode 100644 index 00000000..f22de702 --- /dev/null +++ b/app/src/main/res/anim/history_show.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable-hdpi/backspace_image.png b/app/src/main/res/drawable-hdpi/backspace_image.png new file mode 100644 index 00000000..a64e8df4 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/backspace_image.png differ diff --git a/app/src/main/res/drawable-hdpi/button_ellipsis.9.png b/app/src/main/res/drawable-hdpi/button_ellipsis.9.png new file mode 100644 index 00000000..807baf1d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/button_ellipsis.9.png differ diff --git a/app/src/main/res/drawable-hdpi/clear_history.png b/app/src/main/res/drawable-hdpi/clear_history.png new file mode 100644 index 00000000..94fd247c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/clear_history.png differ diff --git a/res/drawable-hdpi/edit_text_default.9.png b/app/src/main/res/drawable-hdpi/edit_text_default.9.png similarity index 100% rename from res/drawable-hdpi/edit_text_default.9.png rename to app/src/main/res/drawable-hdpi/edit_text_default.9.png diff --git a/res/drawable-hdpi/edit_text_focused.9.png b/app/src/main/res/drawable-hdpi/edit_text_focused.9.png similarity index 100% rename from res/drawable-hdpi/edit_text_focused.9.png rename to app/src/main/res/drawable-hdpi/edit_text_focused.9.png diff --git a/app/src/main/res/drawable-hdpi/ic_delete.9.png b/app/src/main/res/drawable-hdpi/ic_delete.9.png new file mode 100644 index 00000000..0a17ab3f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_delete.9.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_menu_import_export.png b/app/src/main/res/drawable-hdpi/ic_menu_import_export.png new file mode 100644 index 00000000..bfb8a782 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_menu_import_export.png differ diff --git a/app/src/main/res/drawable-mdpi/button_ellipsis.9.png b/app/src/main/res/drawable-mdpi/button_ellipsis.9.png new file mode 100644 index 00000000..39c94195 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/button_ellipsis.9.png differ diff --git a/app/src/main/res/drawable-mdpi/clear_history.png b/app/src/main/res/drawable-mdpi/clear_history.png new file mode 100644 index 00000000..31438473 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/clear_history.png differ diff --git a/app/src/main/res/drawable-mdpi/edit_text_default.9.png b/app/src/main/res/drawable-mdpi/edit_text_default.9.png new file mode 100644 index 00000000..2e62f16e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/edit_text_default.9.png differ diff --git a/app/src/main/res/drawable-mdpi/edit_text_focused.9.png b/app/src/main/res/drawable-mdpi/edit_text_focused.9.png new file mode 100644 index 00000000..2482b858 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/edit_text_focused.9.png differ diff --git a/res/drawable-mdpi/ic_delete.9.png b/app/src/main/res/drawable-mdpi/ic_delete.9.png similarity index 100% rename from res/drawable-mdpi/ic_delete.9.png rename to app/src/main/res/drawable-mdpi/ic_delete.9.png diff --git a/res/drawable-mdpi/ic_menu_import_export.png b/app/src/main/res/drawable-mdpi/ic_menu_import_export.png similarity index 100% rename from res/drawable-mdpi/ic_menu_import_export.png rename to app/src/main/res/drawable-mdpi/ic_menu_import_export.png diff --git a/app/src/main/res/drawable-xhdpi/button_ellipsis.9.png b/app/src/main/res/drawable-xhdpi/button_ellipsis.9.png new file mode 100644 index 00000000..f1f6e314 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/button_ellipsis.9.png differ diff --git a/app/src/main/res/drawable-xxhdpi/button_ellipsis.9.png b/app/src/main/res/drawable-xxhdpi/button_ellipsis.9.png new file mode 100644 index 00000000..ed02c2b2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/button_ellipsis.9.png differ diff --git a/app/src/main/res/drawable/backspace.xml b/app/src/main/res/drawable/backspace.xml new file mode 100644 index 00000000..d0682332 --- /dev/null +++ b/app/src/main/res/drawable/backspace.xml @@ -0,0 +1,4 @@ + + + + diff --git a/res/drawable/blue_button.xml b/app/src/main/res/drawable/blue_button.xml similarity index 76% rename from res/drawable/blue_button.xml rename to app/src/main/res/drawable/blue_button.xml index f4d6059b..6043564f 100644 --- a/res/drawable/blue_button.xml +++ b/app/src/main/res/drawable/blue_button.xml @@ -1,5 +1,4 @@ - - - + diff --git a/res/drawable/button.xml b/app/src/main/res/drawable/button.xml similarity index 76% rename from res/drawable/button.xml rename to app/src/main/res/drawable/button.xml index e5112ee1..01990ec5 100644 --- a/res/drawable/button.xml +++ b/app/src/main/res/drawable/button.xml @@ -1,5 +1,4 @@ - - - + diff --git a/app/src/main/res/drawable/edit_text.xml b/app/src/main/res/drawable/edit_text.xml new file mode 100644 index 00000000..f67dfa4d --- /dev/null +++ b/app/src/main/res/drawable/edit_text.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..ff5da3f1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..9f8cf946 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + diff --git a/res/drawable/red_button.xml b/app/src/main/res/drawable/red_button.xml similarity index 76% rename from res/drawable/red_button.xml rename to app/src/main/res/drawable/red_button.xml index 8246a105..48b205ee 100644 --- a/res/drawable/red_button.xml +++ b/app/src/main/res/drawable/red_button.xml @@ -1,5 +1,4 @@ - - - + diff --git a/res/drawable/yellow_button.xml b/app/src/main/res/drawable/yellow_button.xml similarity index 76% rename from res/drawable/yellow_button.xml rename to app/src/main/res/drawable/yellow_button.xml index e39e62d9..81b77a8a 100644 --- a/res/drawable/yellow_button.xml +++ b/app/src/main/res/drawable/yellow_button.xml @@ -1,5 +1,4 @@ - - - + diff --git a/app/src/main/res/layout-land/main.xml b/app/src/main/res/layout-land/main.xml new file mode 100644 index 00000000..649a5057 --- /dev/null +++ b/app/src/main/res/layout-land/main.xml @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-land/numpad_2.xml b/app/src/main/res/layout-land/numpad_2.xml new file mode 100644 index 00000000..9297cf7c --- /dev/null +++ b/app/src/main/res/layout-land/numpad_2.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/history.xml b/app/src/main/res/layout/history.xml new file mode 100644 index 00000000..cc8a1d18 --- /dev/null +++ b/app/src/main/res/layout/history.xml @@ -0,0 +1,25 @@ + + + + + +