diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/AnnotationJsonXmlMappingJava003Snippet.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/AnnotationJsonXmlMappingJava003Snippet.java new file mode 100644 index 00000000000..c5f3df1927d --- /dev/null +++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/AnnotationJsonXmlMappingJava003Snippet.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package com.codenameone.developerguide.snippets.generated; + +import com.codename1.gpu.*; +import com.codename1.ui.*; +import com.codename1.ui.animations.*; +import com.codename1.ui.events.*; +import com.codename1.ui.geom.*; +import com.codename1.ui.layouts.*; +import com.codename1.ui.list.*; +import com.codename1.ui.plaf.*; +import com.codename1.ui.util.*; +import com.codename1.components.*; +import com.codename1.charts.models.*; +import com.codename1.charts.renderers.*; +import com.codename1.charts.views.*; +import com.codename1.capture.*; +import com.codename1.io.*; +import com.codename1.l10n.*; +import com.codename1.location.*; +import com.codename1.maps.*; +import com.codename1.media.*; +import com.codename1.messaging.*; +import com.codename1.payment.*; +import com.codename1.processing.*; +import com.codename1.properties.*; +import com.codename1.push.*; +import com.codename1.security.*; +import com.codename1.social.*; +import com.codename1.ui.spinner.*; +import java.io.*; +import com.codename1.mapping.*; +import com.codename1.xml.*; +import java.util.*; +import com.codename1.annotations.*; +import com.codename1.properties.*; + +class AnnotationJsonXmlMappingJava003Snippet { + + // tag::annotation-json-xml-mapping-java-003[] + // A type from a third-party jar the build cannot annotate. + static class LatLon { + double lat; + double lon; + } + + static class LatLonMapper implements Mapper { + @Override + public Class type() { + return LatLon.class; + } + + @Override + public Map toMap(LatLon instance) { + Map m = new LinkedHashMap(); + m.put("lat", Double.valueOf(instance.lat)); + m.put("lon", Double.valueOf(instance.lon)); + return m; + } + + @Override + public LatLon fromMap(Map map) { + LatLon out = new LatLon(); + out.lat = readDouble(map.get("lat")); + out.lon = readDouble(map.get("lon")); + return out; + } + + @Override + public String xmlRootName() { + return "latLon"; + } + + @Override + public void writeXml(LatLon instance, Element root) { + root.setAttribute("lat", String.valueOf(instance.lat)); + root.setAttribute("lon", String.valueOf(instance.lon)); + } + + @Override + public LatLon readXml(Element root) { + LatLon out = new LatLon(); + out.lat = Double.parseDouble(root.getAttribute("lat")); + out.lon = Double.parseDouble(root.getAttribute("lon")); + return out; + } + + // JSONParser hands back a Double for every number, but a map that came + // from somewhere else may hold any Number. Read through the interface + // rather than casting to Double: a failed cast does not throw on iOS, + // so the catch you would write for it never runs. + private double readDouble(Object value) { + return value instanceof Number ? ((Number) value).doubleValue() : 0; + } + } + + void registerMappers() { + Mappers.register(new LatLonMapper()); + } + // end::annotation-json-xml-mapping-java-003[] +} diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/MiscellaneousFeaturesJava010Snippet.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/MiscellaneousFeaturesJava010Snippet.java new file mode 100644 index 00000000000..d0a152df40e --- /dev/null +++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/MiscellaneousFeaturesJava010Snippet.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ + +package com.codenameone.developerguide.snippets.generated; + +import com.codename1.gpu.*; +import com.codename1.ui.*; +import com.codename1.ui.animations.*; +import com.codename1.ui.events.*; +import com.codename1.ui.geom.*; +import com.codename1.ui.layouts.*; +import com.codename1.ui.list.*; +import com.codename1.ui.plaf.*; +import com.codename1.ui.util.*; +import com.codename1.components.*; +import com.codename1.charts.models.*; +import com.codename1.charts.renderers.*; +import com.codename1.charts.views.*; +import com.codename1.capture.*; +import com.codename1.io.*; +import com.codename1.l10n.*; +import com.codename1.location.*; +import com.codename1.maps.*; +import com.codename1.media.*; +import com.codename1.messaging.*; +import com.codename1.payment.*; +import com.codename1.processing.*; +import com.codename1.properties.*; +import com.codename1.push.*; +import com.codename1.security.*; +import com.codename1.social.*; +import com.codename1.ui.spinner.*; +import java.io.*; +import java.util.*; + + +class MiscellaneousFeaturesJava010Snippet { + + // tag::miscellaneous-features-java-010[] + void ensureLocationUsageDescription() { + // Both calls only work in the simulator, and setProjectBuildHint throws + // anywhere else -- so this belongs behind an isSimulator() check, run + // once during development rather than on every launch. + if (!Display.getInstance().isSimulator()) { + return; + } + + Map hints = Display.getInstance().getProjectBuildHints(); + if (hints == null) { + // No codename1_settings.properties beside the running project, or + // it could not be read. Nothing to check against, and nothing to + // write into. + return; + } + + String description = hints.get("ios.locationUsageDescription"); + if (description == null || description.length() == 0) { + Display.getInstance().setProjectBuildHint("ios.locationUsageDescription", + "Used to show nearby results on the map"); + } + } + // end::miscellaneous-features-java-010[] +} diff --git a/docs/developer-guide/Annotation-JSON-XML-Mapping.asciidoc b/docs/developer-guide/Annotation-JSON-XML-Mapping.asciidoc index a2a49966320..46f5e04af83 100644 --- a/docs/developer-guide/Annotation-JSON-XML-Mapping.asciidoc +++ b/docs/developer-guide/Annotation-JSON-XML-Mapping.asciidoc @@ -119,6 +119,11 @@ persisted across builds. Sometimes a class lives in a third-party JAR the build can't annotate. Hand-write a `Mapper` and register it at startup: +[source,java] +---- +include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/AnnotationJsonXmlMappingJava003Snippet.java[tag=annotation-json-xml-mapping-java-003,indent=0] +---- + Hand-written mappers take precedence over generated ones for the same class. diff --git a/docs/developer-guide/Miscellaneous-Features.asciidoc b/docs/developer-guide/Miscellaneous-Features.asciidoc index df22cfdf57f..b44ce004fad 100644 --- a/docs/developer-guide/Miscellaneous-Features.asciidoc +++ b/docs/developer-guide/Miscellaneous-Features.asciidoc @@ -814,6 +814,11 @@ A good example for a common problem developers face is location code that doesn' To solve this sort of used case you have two APIs in `Display`: +[source,java] +---- +include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/MiscellaneousFeaturesJava010Snippet.java[tag=miscellaneous-features-java-010,indent=0] +---- + Both of these allow you to detect if a build hint is set and if not (or if it's set incorrectly) set its value... diff --git a/docs/developer-guide/SVG-Transcoder.asciidoc b/docs/developer-guide/SVG-Transcoder.asciidoc index e65b7088333..b9baa27fae3 100644 --- a/docs/developer-guide/SVG-Transcoder.asciidoc +++ b/docs/developer-guide/SVG-Transcoder.asciidoc @@ -152,8 +152,23 @@ no SVGs gets no weaving. === Calling the registry yourself If you don't use `theme.css` but still want a transcoded SVG, construct -the generated class directly: +the generated class directly. It lands in +`com.codename1.generated.svg` and is named after the file, so +`src/main/svg/logo.svg` becomes `com.codename1.generated.svg.Logo`. +The class exists only after a build has run the transcoder, which is why +this listing isn't one of the compiled examples: +[listing] +---- +import com.codename1.generated.svg.Logo; +import com.codename1.ui.Display; + +Logo logo = new Logo(); // declared size at DENSITY_MEDIUM +Logo dense = new Logo(Display.DENSITY_HIGH); // read as a higher-density design +Logo sized = new Logo(12f, 12f); // 12mm square, converted per device + +myButton.setIcon(sized); +---- Constructors come in three flavours, matching the three CSS sizing mechanisms above. The two-`float` constructor takes millimeters; that's diff --git a/docs/developer-guide/security.asciidoc b/docs/developer-guide/security.asciidoc index 1ff252d89ab..53efd1e99c0 100644 --- a/docs/developer-guide/security.asciidoc +++ b/docs/developer-guide/security.asciidoc @@ -35,8 +35,7 @@ Notice that this is a temporary roadblock as any savvy hacker would compile the NOTE: This isn't encoding or encryption — it's a simple obfuscation of the data. -There are two simple methods in the `Util` class: - +The `Util` class has two static methods for it: `xorEncode(String)` turns a string into the obfuscated form, and `xorDecode(String)` turns it back. They use a simple xor based obfuscation to make a String less readable. For example, if you've code like this: @@ -90,8 +89,31 @@ This works through a new mechanism in storage where you can replace the storage include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/SecurityJava030Snippet.java[tag=security-java-030,indent=0] ---- -You can leverage that knowledge to change the encryption password on the encryption storage using pseudo-code like this: +You can leverage that knowledge to change the encryption password on the encryption storage. The key is installed for the whole of `Storage`, so every entry has to move together: read them all under the old key, install the new one, write them all back. `EncryptedStorage` comes from the bouncy castle cn1lib, so this listing isn't part of the compiled examples: + +[listing] +---- +EncryptedStorage.install(oldKey); +Map all = new LinkedHashMap<>(); +for (String name : Storage.getInstance().listEntries()) { + try (InputStream is = Storage.getInstance().createInputStream(name)) { + all.put(name, Util.readInputStream(is)); + } +} + +EncryptedStorage.install(newKey); +for (Map.Entry e : all.entrySet()) { + try (OutputStream o = Storage.getInstance().createOutputStream(e.getKey())) { + o.write(e.getValue()); + } +} +---- + +The `try`-with-resources is deliberate: `Util.cleanup` swallows a close failure, and on the write side a swallowed close means an entry was never finalized under the new key. Let it throw. + +WARNING: This isn't crash-safe, and it can't be made crash-safe from the outside. If the process dies part way through the second loop, some entries are under each key and neither opens the whole store. `Storage` has no rename, so there is no way to stage the converted copies and swap them in atomically, and the recovery data lives only in that in-memory map. +Which is the real argument for not rotating the storage key at all. Encrypt `Storage` with a random key your app generates once, keep that key wrapped under the password-derived one in a single record outside `Storage`, and a password change re-wraps that one small record instead of rewriting every entry. Nothing else moves, so there is no half-converted state to recover from. If you do rewrite the store in place, do it once at startup with nothing else touching `Storage`, and treat the possibility of an interrupted run as something your app has to survive rather than something this loop prevents. NOTE: It isn't a good idea to replace storage objects when an app is running so this is purely for this special case... diff --git a/scripts/developer-guide/missing-code-blocks-baseline.txt b/scripts/developer-guide/missing-code-blocks-baseline.txt index 371d76282ef..5dbbf872b7b 100644 --- a/scripts/developer-guide/missing-code-blocks-baseline.txt +++ b/scripts/developer-guide/missing-code-blocks-baseline.txt @@ -1,12 +1,9 @@ # Prose that promises a code block where none follows. # A ratchet: entries may be removed as holes are filled, never added. # Regenerate with check-missing-code-blocks.py --write-baseline. -Annotation-JSON-XML-Mapping.asciidoc Hand-write a `Mapper` and register it at startup: Authentication-And-Identity.asciidoc Firebase Auth isn't an OIDC provider -- it issues Google-Identity-Toolkit-style tokens via REST endpoints. `com.codename1.social.FirebaseAuth` wraps those endpoints: Maven-Creating-CN1Libs.adoc Now try it out. Try adding the following code to your application project's main class (or anywhere in the application project, for that matter): Maven-Creating-CN1Libs.adoc The simulator dispatches every action on the Codename One EDT through `Display.callSerially`, so your method can call `Display.getInstance()`, `Form.show()`, `Dialog.show()`, `ToastBar.showInfoMessage()` and any other CN1 API. Reflection uses the same classloader that loaded `Display`, so cn1lib internals (including package-private classes) resolve normally: -Miscellaneous-Features.asciidoc To solve this sort of used case you have two APIs in `Display`: -SVG-Transcoder.asciidoc the generated class directly: The-Components-Of-Codename-One.asciidoc Call the builder from a Maven plugin, an Ant task or a one-shot `main`: The-Components-Of-Codename-One.asciidoc This code should output "The result was 7" to the console. It's fully asynchronous, so you can include this code anywhere without worrying about it "bogging down" your code. The full signature of this form of the https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html#execute(java.lang.String,com.codename1.util.SuccessCallback)[execute()] method is: io.asciidoc In the above code you do the following: @@ -16,5 +13,3 @@ io.asciidoc There are many methods of interest to keep an eye for: io.asciidoc database by name: performance.asciidoc In the new Contacts demo you have a share button for each contact, the code for constructing a `ShareButton` looks like this: performance.asciidoc These icons are in a shared resource file that you load and don't cache. The initial workaround was to cache this resource but a better solution was to convert this code: -security.asciidoc There are two simple methods in the `Util` class: -security.asciidoc You can leverage that knowledge to change the encryption password on the encryption storage using pseudo-code like this: